| /* |
| // Copyright (c) 2019 Intel Corporation |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| */ |
| |
| #include "NVMeBasicContext.hpp" |
| #include "NVMeContext.hpp" |
| #include "NVMeSensor.hpp" |
| #include "Thresholds.hpp" |
| #include "Utils.hpp" |
| #include "VariantVisitors.hpp" |
| |
| #include <boost/asio/error.hpp> |
| #include <boost/asio/io_context.hpp> |
| #include <boost/asio/post.hpp> |
| #include <boost/asio/steady_timer.hpp> |
| #include <sdbusplus/asio/connection.hpp> |
| #include <sdbusplus/asio/object_server.hpp> |
| #include <sdbusplus/bus.hpp> |
| #include <sdbusplus/bus/match.hpp> |
| #include <sdbusplus/message.hpp> |
| #include <sdbusplus/message/native_types.hpp> |
| |
| #include <algorithm> |
| #include <array> |
| #include <chrono> |
| #include <cstddef> |
| #include <cstdint> |
| #include <filesystem> |
| #include <functional> |
| #include <iostream> |
| #include <memory> |
| #include <optional> |
| #include <stdexcept> |
| #include <string> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| static constexpr uint8_t nvmeMiDefaultSlaveAddr = 0x6A; |
| |
| static NVMEMap nvmeDeviceMap; |
| |
| NVMEMap& getNVMEMap() |
| { |
| return nvmeDeviceMap; |
| } |
| |
| static std::optional<int> extractBusNumber( |
| const std::string& path, const SensorBaseConfigMap& properties) |
| { |
| auto findBus = properties.find("Bus"); |
| if (findBus == properties.end()) |
| { |
| std::cerr << "could not determine bus number for " << path << "\n"; |
| return std::nullopt; |
| } |
| |
| return std::visit(VariantToIntVisitor(), findBus->second); |
| } |
| |
| static uint8_t extractSlaveAddr(const std::string& path, |
| const SensorBaseConfigMap& properties) |
| { |
| auto findSlaveAddr = properties.find("Address"); |
| if (findSlaveAddr == properties.end()) |
| { |
| std::cerr << "could not determine slave address for " << path << "\n" |
| << "using default as specified in nvme-mi" |
| << "\n"; |
| return nvmeMiDefaultSlaveAddr; |
| } |
| |
| return std::visit(VariantToUnsignedIntVisitor(), findSlaveAddr->second); |
| } |
| |
| static std::optional<std::string> extractSensorName( |
| const std::string& path, const SensorBaseConfigMap& properties) |
| { |
| auto findSensorName = properties.find("Name"); |
| if (findSensorName == properties.end()) |
| { |
| std::cerr << "could not determine configuration name for " << path |
| << "\n"; |
| return std::nullopt; |
| } |
| |
| return std::get<std::string>(findSensorName->second); |
| } |
| |
| static std::filesystem::path deriveRootBusPath(int busNumber) |
| { |
| return "/sys/bus/i2c/devices/i2c-" + std::to_string(busNumber) + |
| "/mux_device"; |
| } |
| |
| static std::optional<int> deriveRootBus(std::optional<int> busNumber) |
| { |
| if (!busNumber) |
| { |
| return std::nullopt; |
| } |
| |
| std::filesystem::path muxPath = deriveRootBusPath(*busNumber); |
| |
| if (!std::filesystem::is_symlink(muxPath)) |
| { |
| return busNumber; |
| } |
| |
| std::string rootName = std::filesystem::read_symlink(muxPath).filename(); |
| size_t dash = rootName.find('-'); |
| if (dash == std::string::npos) |
| { |
| std::cerr << "Error finding root bus for " << rootName << "\n"; |
| return std::nullopt; |
| } |
| |
| return std::stoi(rootName.substr(0, dash)); |
| } |
| |
| static std::shared_ptr<NVMeContext> provideRootBusContext( |
| boost::asio::io_context& io, NVMEMap& map, int rootBus) |
| { |
| auto findRoot = map.find(rootBus); |
| if (findRoot != map.end()) |
| { |
| return findRoot->second; |
| } |
| |
| std::shared_ptr<NVMeContext> context = |
| std::make_shared<NVMeBasicContext>(io, rootBus); |
| map[rootBus] = context; |
| |
| return context; |
| } |
| |
| static void handleSensorConfigurations( |
| boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, |
| std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, |
| const ManagedObjectType& sensorConfigurations) |
| { |
| // todo: it'd be better to only update the ones we care about |
| for (const auto& [_, nvmeContextPtr] : nvmeDeviceMap) |
| { |
| if (nvmeContextPtr) |
| { |
| nvmeContextPtr->close(); |
| } |
| } |
| nvmeDeviceMap.clear(); |
| |
| // iterate through all found configurations |
| for (const auto& [interfacePath, sensorData] : sensorConfigurations) |
| { |
| // find base configuration |
| auto sensorBase = |
| sensorData.find(configInterfaceName(NVMeSensor::sensorType)); |
| if (sensorBase == sensorData.end()) |
| { |
| continue; |
| } |
| |
| const SensorBaseConfigMap& sensorConfig = sensorBase->second; |
| std::optional<int> busNumber = |
| extractBusNumber(interfacePath, sensorConfig); |
| std::optional<std::string> sensorName = |
| extractSensorName(interfacePath, sensorConfig); |
| uint8_t slaveAddr = extractSlaveAddr(interfacePath, sensorConfig); |
| std::optional<int> rootBus = deriveRootBus(busNumber); |
| |
| if (!(busNumber && sensorName && rootBus)) |
| { |
| continue; |
| } |
| |
| std::vector<thresholds::Threshold> sensorThresholds; |
| if (!parseThresholdsFromConfig(sensorData, sensorThresholds)) |
| { |
| std::cerr << "error populating thresholds for " << *sensorName |
| << "\n"; |
| } |
| |
| try |
| { |
| // May throw for an invalid rootBus |
| std::shared_ptr<NVMeContext> context = |
| provideRootBusContext(io, nvmeDeviceMap, *rootBus); |
| |
| // Construct the sensor after grabbing the context so we don't |
| // glitch D-Bus May throw for an invalid busNumber |
| std::shared_ptr<NVMeSensor> sensorPtr = |
| std::make_shared<NVMeSensor>( |
| objectServer, io, dbusConnection, *sensorName, |
| std::move(sensorThresholds), interfacePath, *busNumber, |
| slaveAddr); |
| |
| context->addSensor(sensorPtr); |
| } |
| catch (const std::invalid_argument& ex) |
| { |
| std::cerr << "Failed to add sensor for " |
| << std::string(interfacePath) << ": " << ex.what() |
| << "\n"; |
| } |
| } |
| for (const auto& [_, context] : nvmeDeviceMap) |
| { |
| context->pollNVMeDevices(); |
| } |
| } |
| |
| void createSensors(boost::asio::io_context& io, |
| sdbusplus::asio::object_server& objectServer, |
| std::shared_ptr<sdbusplus::asio::connection>& dbusConnection) |
| { |
| auto getter = std::make_shared<GetSensorConfiguration>( |
| dbusConnection, [&io, &objectServer, &dbusConnection]( |
| const ManagedObjectType& sensorConfigurations) { |
| handleSensorConfigurations(io, objectServer, dbusConnection, |
| sensorConfigurations); |
| }); |
| getter->getConfiguration(std::vector<std::string>{NVMeSensor::sensorType}); |
| } |
| |
| static void interfaceRemoved(sdbusplus::message_t& message, NVMEMap& contexts) |
| { |
| if (message.is_method_error()) |
| { |
| std::cerr << "interfacesRemoved callback method error\n"; |
| return; |
| } |
| |
| sdbusplus::message::object_path path; |
| std::vector<std::string> interfaces; |
| |
| message.read(path, interfaces); |
| |
| for (auto& [_, context] : contexts) |
| { |
| std::optional<std::shared_ptr<NVMeSensor>> sensor = |
| context->getSensorAtPath(path); |
| if (!sensor) |
| { |
| continue; |
| } |
| |
| auto interface = std::find(interfaces.begin(), interfaces.end(), |
| (*sensor)->configInterface); |
| if (interface == interfaces.end()) |
| { |
| continue; |
| } |
| |
| context->removeSensor(sensor.value()); |
| } |
| } |
| |
| int main() |
| { |
| boost::asio::io_context io; |
| auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); |
| systemBus->request_name("xyz.openbmc_project.NVMeSensor"); |
| sdbusplus::asio::object_server objectServer(systemBus, true); |
| objectServer.add_manager("/xyz/openbmc_project/sensors"); |
| |
| boost::asio::post(io, |
| [&]() { createSensors(io, objectServer, systemBus); }); |
| |
| boost::asio::steady_timer filterTimer(io); |
| std::function<void(sdbusplus::message_t&)> eventHandler = |
| [&filterTimer, &io, &objectServer, &systemBus](sdbusplus::message_t&) { |
| // this implicitly cancels the timer |
| filterTimer.expires_after(std::chrono::seconds(1)); |
| |
| filterTimer.async_wait([&](const boost::system::error_code& ec) { |
| if (ec == boost::asio::error::operation_aborted) |
| { |
| return; // we're being canceled |
| } |
| |
| if (ec) |
| { |
| std::cerr << "Error: " << ec.message() << "\n"; |
| return; |
| } |
| |
| createSensors(io, objectServer, systemBus); |
| }); |
| }; |
| |
| std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches = |
| setupPropertiesChangedMatches( |
| *systemBus, std::to_array<const char*>({NVMeSensor::sensorType}), |
| eventHandler); |
| |
| // Watch for entity-manager to remove configuration interfaces |
| // so the corresponding sensors can be removed. |
| auto ifaceRemovedMatch = std::make_unique<sdbusplus::bus::match_t>( |
| static_cast<sdbusplus::bus_t&>(*systemBus), |
| "type='signal',member='InterfacesRemoved',arg0path='" + |
| std::string(inventoryPath) + "/'", |
| [](sdbusplus::message_t& msg) { |
| interfaceRemoved(msg, nvmeDeviceMap); |
| }); |
| |
| setupManufacturingModeMatch(*systemBus); |
| io.run(); |
| } |