|  | /* | 
|  | // 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 <phosphor-logging/lg2.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 <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()) | 
|  | { | 
|  | lg2::error("could not determine bus number for '{PATH}'", "PATH", path); | 
|  | 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()) | 
|  | { | 
|  | lg2::error( | 
|  | "could not determine slave address for '{PATH} 'using default as " | 
|  | "specified in nvme-mi", | 
|  | "PATH", path); | 
|  | 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()) | 
|  | { | 
|  | lg2::error("could not determine configuration name for '{PATH}'", | 
|  | "PATH", path); | 
|  | 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) | 
|  | { | 
|  | lg2::error("Error finding root bus for '{NAME}'", "NAME", rootName); | 
|  | 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)) | 
|  | { | 
|  | lg2::error("error populating thresholds for '{NAME}'", "NAME", | 
|  | *sensorName); | 
|  | } | 
|  |  | 
|  | 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) | 
|  | { | 
|  | lg2::error("Failed to add sensor for '{PATH}': '{ERROR}'", "PATH", | 
|  | interfacePath.str, "ERROR", ex); | 
|  | } | 
|  | } | 
|  | 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()) | 
|  | { | 
|  | lg2::error("interfacesRemoved callback method error"); | 
|  | 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) | 
|  | { | 
|  | lg2::error("Error: '{ERROR_MESSAGE}'", "ERROR_MESSAGE", | 
|  | ec.message()); | 
|  | 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(); | 
|  | } |