1 /* 2 // Copyright (c) 2019 Intel Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 17 #include "NVMeBasicContext.hpp" 18 #include "NVMeContext.hpp" 19 #include "NVMeSensor.hpp" 20 #include "Thresholds.hpp" 21 #include "Utils.hpp" 22 #include "VariantVisitors.hpp" 23 24 #include <boost/asio/error.hpp> 25 #include <boost/asio/io_context.hpp> 26 #include <boost/asio/post.hpp> 27 #include <boost/asio/steady_timer.hpp> 28 #include <sdbusplus/asio/connection.hpp> 29 #include <sdbusplus/asio/object_server.hpp> 30 #include <sdbusplus/bus.hpp> 31 #include <sdbusplus/bus/match.hpp> 32 #include <sdbusplus/message.hpp> 33 #include <sdbusplus/message/native_types.hpp> 34 35 #include <algorithm> 36 #include <array> 37 #include <chrono> 38 #include <cstddef> 39 #include <cstdint> 40 #include <filesystem> 41 #include <functional> 42 #include <iostream> 43 #include <memory> 44 #include <optional> 45 #include <stdexcept> 46 #include <string> 47 #include <utility> 48 #include <variant> 49 #include <vector> 50 51 static constexpr uint8_t nvmeMiDefaultSlaveAddr = 0x6A; 52 53 static NVMEMap nvmeDeviceMap; 54 55 NVMEMap& getNVMEMap() 56 { 57 return nvmeDeviceMap; 58 } 59 60 static std::optional<int> extractBusNumber( 61 const std::string& path, const SensorBaseConfigMap& properties) 62 { 63 auto findBus = properties.find("Bus"); 64 if (findBus == properties.end()) 65 { 66 std::cerr << "could not determine bus number for " << path << "\n"; 67 return std::nullopt; 68 } 69 70 return std::visit(VariantToIntVisitor(), findBus->second); 71 } 72 73 static uint8_t extractSlaveAddr(const std::string& path, 74 const SensorBaseConfigMap& properties) 75 { 76 auto findSlaveAddr = properties.find("Address"); 77 if (findSlaveAddr == properties.end()) 78 { 79 std::cerr << "could not determine slave address for " << path << "\n" 80 << "using default as specified in nvme-mi" 81 << "\n"; 82 return nvmeMiDefaultSlaveAddr; 83 } 84 85 return std::visit(VariantToUnsignedIntVisitor(), findSlaveAddr->second); 86 } 87 88 static std::optional<std::string> extractSensorName( 89 const std::string& path, const SensorBaseConfigMap& properties) 90 { 91 auto findSensorName = properties.find("Name"); 92 if (findSensorName == properties.end()) 93 { 94 std::cerr << "could not determine configuration name for " << path 95 << "\n"; 96 return std::nullopt; 97 } 98 99 return std::get<std::string>(findSensorName->second); 100 } 101 102 static std::filesystem::path deriveRootBusPath(int busNumber) 103 { 104 return "/sys/bus/i2c/devices/i2c-" + std::to_string(busNumber) + 105 "/mux_device"; 106 } 107 108 static std::optional<int> deriveRootBus(std::optional<int> busNumber) 109 { 110 if (!busNumber) 111 { 112 return std::nullopt; 113 } 114 115 std::filesystem::path muxPath = deriveRootBusPath(*busNumber); 116 117 if (!std::filesystem::is_symlink(muxPath)) 118 { 119 return busNumber; 120 } 121 122 std::string rootName = std::filesystem::read_symlink(muxPath).filename(); 123 size_t dash = rootName.find('-'); 124 if (dash == std::string::npos) 125 { 126 std::cerr << "Error finding root bus for " << rootName << "\n"; 127 return std::nullopt; 128 } 129 130 return std::stoi(rootName.substr(0, dash)); 131 } 132 133 static std::shared_ptr<NVMeContext> provideRootBusContext( 134 boost::asio::io_context& io, NVMEMap& map, int rootBus) 135 { 136 auto findRoot = map.find(rootBus); 137 if (findRoot != map.end()) 138 { 139 return findRoot->second; 140 } 141 142 std::shared_ptr<NVMeContext> context = 143 std::make_shared<NVMeBasicContext>(io, rootBus); 144 map[rootBus] = context; 145 146 return context; 147 } 148 149 static void handleSensorConfigurations( 150 boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer, 151 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 152 const ManagedObjectType& sensorConfigurations) 153 { 154 // todo: it'd be better to only update the ones we care about 155 for (const auto& [_, nvmeContextPtr] : nvmeDeviceMap) 156 { 157 if (nvmeContextPtr) 158 { 159 nvmeContextPtr->close(); 160 } 161 } 162 nvmeDeviceMap.clear(); 163 164 // iterate through all found configurations 165 for (const auto& [interfacePath, sensorData] : sensorConfigurations) 166 { 167 // find base configuration 168 auto sensorBase = 169 sensorData.find(configInterfaceName(NVMeSensor::sensorType)); 170 if (sensorBase == sensorData.end()) 171 { 172 continue; 173 } 174 175 const SensorBaseConfigMap& sensorConfig = sensorBase->second; 176 std::optional<int> busNumber = 177 extractBusNumber(interfacePath, sensorConfig); 178 std::optional<std::string> sensorName = 179 extractSensorName(interfacePath, sensorConfig); 180 uint8_t slaveAddr = extractSlaveAddr(interfacePath, sensorConfig); 181 std::optional<int> rootBus = deriveRootBus(busNumber); 182 183 if (!(busNumber && sensorName && rootBus)) 184 { 185 continue; 186 } 187 188 std::vector<thresholds::Threshold> sensorThresholds; 189 if (!parseThresholdsFromConfig(sensorData, sensorThresholds)) 190 { 191 std::cerr << "error populating thresholds for " << *sensorName 192 << "\n"; 193 } 194 195 try 196 { 197 // May throw for an invalid rootBus 198 std::shared_ptr<NVMeContext> context = 199 provideRootBusContext(io, nvmeDeviceMap, *rootBus); 200 201 // Construct the sensor after grabbing the context so we don't 202 // glitch D-Bus May throw for an invalid busNumber 203 std::shared_ptr<NVMeSensor> sensorPtr = 204 std::make_shared<NVMeSensor>( 205 objectServer, io, dbusConnection, *sensorName, 206 std::move(sensorThresholds), interfacePath, *busNumber, 207 slaveAddr); 208 209 context->addSensor(sensorPtr); 210 } 211 catch (const std::invalid_argument& ex) 212 { 213 std::cerr << "Failed to add sensor for " 214 << std::string(interfacePath) << ": " << ex.what() 215 << "\n"; 216 } 217 } 218 for (const auto& [_, context] : nvmeDeviceMap) 219 { 220 context->pollNVMeDevices(); 221 } 222 } 223 224 void createSensors(boost::asio::io_context& io, 225 sdbusplus::asio::object_server& objectServer, 226 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection) 227 { 228 auto getter = std::make_shared<GetSensorConfiguration>( 229 dbusConnection, [&io, &objectServer, &dbusConnection]( 230 const ManagedObjectType& sensorConfigurations) { 231 handleSensorConfigurations(io, objectServer, dbusConnection, 232 sensorConfigurations); 233 }); 234 getter->getConfiguration(std::vector<std::string>{NVMeSensor::sensorType}); 235 } 236 237 static void interfaceRemoved(sdbusplus::message_t& message, NVMEMap& contexts) 238 { 239 if (message.is_method_error()) 240 { 241 std::cerr << "interfacesRemoved callback method error\n"; 242 return; 243 } 244 245 sdbusplus::message::object_path path; 246 std::vector<std::string> interfaces; 247 248 message.read(path, interfaces); 249 250 for (auto& [_, context] : contexts) 251 { 252 std::optional<std::shared_ptr<NVMeSensor>> sensor = 253 context->getSensorAtPath(path); 254 if (!sensor) 255 { 256 continue; 257 } 258 259 auto interface = std::find(interfaces.begin(), interfaces.end(), 260 (*sensor)->configInterface); 261 if (interface == interfaces.end()) 262 { 263 continue; 264 } 265 266 context->removeSensor(sensor.value()); 267 } 268 } 269 270 int main() 271 { 272 boost::asio::io_context io; 273 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); 274 systemBus->request_name("xyz.openbmc_project.NVMeSensor"); 275 sdbusplus::asio::object_server objectServer(systemBus, true); 276 objectServer.add_manager("/xyz/openbmc_project/sensors"); 277 278 boost::asio::post(io, 279 [&]() { createSensors(io, objectServer, systemBus); }); 280 281 boost::asio::steady_timer filterTimer(io); 282 std::function<void(sdbusplus::message_t&)> eventHandler = 283 [&filterTimer, &io, &objectServer, &systemBus](sdbusplus::message_t&) { 284 // this implicitly cancels the timer 285 filterTimer.expires_after(std::chrono::seconds(1)); 286 287 filterTimer.async_wait([&](const boost::system::error_code& ec) { 288 if (ec == boost::asio::error::operation_aborted) 289 { 290 return; // we're being canceled 291 } 292 293 if (ec) 294 { 295 std::cerr << "Error: " << ec.message() << "\n"; 296 return; 297 } 298 299 createSensors(io, objectServer, systemBus); 300 }); 301 }; 302 303 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches = 304 setupPropertiesChangedMatches( 305 *systemBus, std::to_array<const char*>({NVMeSensor::sensorType}), 306 eventHandler); 307 308 // Watch for entity-manager to remove configuration interfaces 309 // so the corresponding sensors can be removed. 310 auto ifaceRemovedMatch = std::make_unique<sdbusplus::bus::match_t>( 311 static_cast<sdbusplus::bus_t&>(*systemBus), 312 "type='signal',member='InterfacesRemoved',arg0path='" + 313 std::string(inventoryPath) + "/'", 314 [](sdbusplus::message_t& msg) { 315 interfaceRemoved(msg, nvmeDeviceMap); 316 }); 317 318 setupManufacturingModeMatch(*systemBus); 319 io.run(); 320 } 321