1 #include "ExternalSensor.hpp" 2 #include "Utils.hpp" 3 #include "VariantVisitors.hpp" 4 5 #include <boost/algorithm/string/replace.hpp> 6 #include <boost/container/flat_map.hpp> 7 #include <boost/container/flat_set.hpp> 8 #include <sdbusplus/asio/connection.hpp> 9 #include <sdbusplus/asio/object_server.hpp> 10 #include <sdbusplus/bus/match.hpp> 11 12 #include <array> 13 #include <filesystem> 14 #include <fstream> 15 #include <functional> 16 #include <memory> 17 #include <regex> 18 #include <stdexcept> 19 #include <string> 20 #include <utility> 21 #include <variant> 22 #include <vector> 23 24 // Copied from HwmonTempSensor and inspired by 25 // https://gerrit.openbmc-project.xyz/c/openbmc/dbus-sensors/+/35476 26 27 // The ExternalSensor is a sensor whose value is intended to be writable 28 // by something external to the BMC, so that the host (or something else) 29 // can write to it, perhaps by using an IPMI or Redfish connection. 30 31 // Unlike most other sensors, an external sensor does not correspond 32 // to a hwmon file or any other kernel/hardware interface, 33 // so, after initialization, this module does not have much to do, 34 // but it handles reinitialization and thresholds, similar to the others. 35 // The main work of this module is to provide backing storage for a 36 // sensor that exists only virtually, and to provide an optional 37 // timeout service for detecting loss of timely updates. 38 39 // As there is no corresponding driver or hardware to support, 40 // all configuration of this sensor comes from the JSON parameters: 41 // MinValue, MaxValue, Timeout, PowerState, Units, Name 42 43 // The purpose of "Units" is to specify the physical characteristic 44 // the external sensor is measuring, because with an external sensor 45 // there is no other way to tell, and it will be used for the object path 46 // here: /xyz/openbmc_project/sensors/<Units>/<Name> 47 48 // For more information, see external-sensor.md design document: 49 // https://gerrit.openbmc-project.xyz/c/openbmc/docs/+/41452 50 // https://github.com/openbmc/docs/tree/master/designs/ 51 52 static constexpr bool debug = false; 53 54 static const char* sensorType = "ExternalSensor"; 55 56 void updateReaper(boost::container::flat_map< 57 std::string, std::shared_ptr<ExternalSensor>>& sensors, 58 boost::asio::steady_timer& timer, 59 const std::chrono::steady_clock::time_point& now) 60 { 61 // First pass, reap all stale sensors 62 for (const auto& [name, sensor] : sensors) 63 { 64 if (!sensor) 65 { 66 continue; 67 } 68 69 if (!sensor->isAliveAndPerishable()) 70 { 71 continue; 72 } 73 74 if (!sensor->isAliveAndFresh(now)) 75 { 76 // Mark sensor as dead, no longer alive 77 sensor->writeInvalidate(); 78 } 79 } 80 81 std::chrono::steady_clock::duration nextCheck; 82 bool needCheck = false; 83 84 // Second pass, determine timer interval to next check 85 for (const auto& [name, sensor] : sensors) 86 { 87 if (!sensor) 88 { 89 continue; 90 } 91 92 if (!sensor->isAliveAndPerishable()) 93 { 94 continue; 95 } 96 97 auto expiration = sensor->ageRemaining(now); 98 99 if (needCheck) 100 { 101 nextCheck = std::min(nextCheck, expiration); 102 } 103 else 104 { 105 // Initialization 106 nextCheck = expiration; 107 needCheck = true; 108 } 109 } 110 111 if (!needCheck) 112 { 113 if constexpr (debug) 114 { 115 std::cerr << "Next ExternalSensor timer idle\n"; 116 } 117 118 return; 119 } 120 121 timer.expires_at(now + nextCheck); 122 123 timer.async_wait([&sensors, &timer](const boost::system::error_code& err) { 124 if (err != boost::system::errc::success) 125 { 126 // Cancellation is normal, as timer is dynamically rescheduled 127 if (err != boost::asio::error::operation_aborted) 128 { 129 std::cerr << "ExternalSensor timer scheduling problem: " 130 << err.message() << "\n"; 131 } 132 return; 133 } 134 135 updateReaper(sensors, timer, std::chrono::steady_clock::now()); 136 }); 137 138 if constexpr (debug) 139 { 140 std::cerr << "Next ExternalSensor timer " 141 << std::chrono::duration_cast<std::chrono::microseconds>( 142 nextCheck) 143 .count() 144 << " us\n"; 145 } 146 } 147 148 void createSensors( 149 sdbusplus::asio::object_server& objectServer, 150 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>& 151 sensors, 152 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 153 const std::shared_ptr<boost::container::flat_set<std::string>>& 154 sensorsChanged, 155 boost::asio::steady_timer& reaperTimer) 156 { 157 if constexpr (debug) 158 { 159 std::cerr << "ExternalSensor considering creating sensors\n"; 160 } 161 162 auto getter = std::make_shared<GetSensorConfiguration>( 163 dbusConnection, 164 [&objectServer, &sensors, &dbusConnection, sensorsChanged, 165 &reaperTimer](const ManagedObjectType& sensorConfigurations) { 166 bool firstScan = (sensorsChanged == nullptr); 167 168 for (const std::pair<sdbusplus::message::object_path, SensorData>& 169 sensor : sensorConfigurations) 170 { 171 const std::string& interfacePath = sensor.first.str; 172 const SensorData& sensorData = sensor.second; 173 174 auto sensorBase = sensorData.find(configInterfaceName(sensorType)); 175 if (sensorBase == sensorData.end()) 176 { 177 std::cerr << "Base configuration not found for " 178 << interfacePath << "\n"; 179 continue; 180 } 181 182 const SensorBaseConfiguration& baseConfiguration = *sensorBase; 183 const SensorBaseConfigMap& baseConfigMap = baseConfiguration.second; 184 185 // MinValue and MinValue are mandatory numeric parameters 186 auto minFound = baseConfigMap.find("MinValue"); 187 if (minFound == baseConfigMap.end()) 188 { 189 std::cerr << "MinValue parameter not found for " 190 << interfacePath << "\n"; 191 continue; 192 } 193 double minValue = 194 std::visit(VariantToDoubleVisitor(), minFound->second); 195 if (!std::isfinite(minValue)) 196 { 197 std::cerr << "MinValue parameter not parsed for " 198 << interfacePath << "\n"; 199 continue; 200 } 201 202 auto maxFound = baseConfigMap.find("MaxValue"); 203 if (maxFound == baseConfigMap.end()) 204 { 205 std::cerr << "MaxValue parameter not found for " 206 << interfacePath << "\n"; 207 continue; 208 } 209 double maxValue = 210 std::visit(VariantToDoubleVisitor(), maxFound->second); 211 if (!std::isfinite(maxValue)) 212 { 213 std::cerr << "MaxValue parameter not parsed for " 214 << interfacePath << "\n"; 215 continue; 216 } 217 218 double timeoutSecs = 0.0; 219 220 // Timeout is an optional numeric parameter 221 auto timeoutFound = baseConfigMap.find("Timeout"); 222 if (timeoutFound != baseConfigMap.end()) 223 { 224 timeoutSecs = 225 std::visit(VariantToDoubleVisitor(), timeoutFound->second); 226 } 227 if (!(std::isfinite(timeoutSecs) && (timeoutSecs >= 0.0))) 228 { 229 std::cerr << "Timeout parameter not parsed for " 230 << interfacePath << "\n"; 231 continue; 232 } 233 234 std::string sensorName; 235 std::string sensorUnits; 236 237 // Name and Units are mandatory string parameters 238 auto nameFound = baseConfigMap.find("Name"); 239 if (nameFound == baseConfigMap.end()) 240 { 241 std::cerr << "Name parameter not found for " << interfacePath 242 << "\n"; 243 continue; 244 } 245 sensorName = 246 std::visit(VariantToStringVisitor(), nameFound->second); 247 if (sensorName.empty()) 248 { 249 std::cerr << "Name parameter not parsed for " << interfacePath 250 << "\n"; 251 continue; 252 } 253 254 auto unitsFound = baseConfigMap.find("Units"); 255 if (unitsFound == baseConfigMap.end()) 256 { 257 std::cerr << "Units parameter not found for " << interfacePath 258 << "\n"; 259 continue; 260 } 261 sensorUnits = 262 std::visit(VariantToStringVisitor(), unitsFound->second); 263 if (sensorUnits.empty()) 264 { 265 std::cerr << "Units parameter not parsed for " << interfacePath 266 << "\n"; 267 continue; 268 } 269 270 // on rescans, only update sensors we were signaled by 271 auto findSensor = sensors.find(sensorName); 272 if (!firstScan && (findSensor != sensors.end())) 273 { 274 std::string suffixName = "/"; 275 suffixName += findSensor->second->name; 276 bool found = false; 277 for (auto it = sensorsChanged->begin(); 278 it != sensorsChanged->end(); it++) 279 { 280 std::string suffixIt = "/"; 281 suffixIt += *it; 282 if (suffixIt.ends_with(suffixName)) 283 { 284 sensorsChanged->erase(it); 285 findSensor->second = nullptr; 286 found = true; 287 if constexpr (debug) 288 { 289 std::cerr << "ExternalSensor " << sensorName 290 << " change found\n"; 291 } 292 break; 293 } 294 } 295 if (!found) 296 { 297 continue; 298 } 299 } 300 301 std::vector<thresholds::Threshold> sensorThresholds; 302 if (!parseThresholdsFromConfig(sensorData, sensorThresholds)) 303 { 304 std::cerr << "error populating thresholds for " << sensorName 305 << "\n"; 306 } 307 308 PowerState readState = getPowerState(baseConfigMap); 309 310 auto& sensorEntry = sensors[sensorName]; 311 sensorEntry = nullptr; 312 313 sensorEntry = std::make_shared<ExternalSensor>( 314 sensorType, objectServer, dbusConnection, sensorName, 315 sensorUnits, std::move(sensorThresholds), interfacePath, 316 maxValue, minValue, timeoutSecs, readState); 317 sensorEntry->initWriteHook( 318 [&sensors, &reaperTimer]( 319 const std::chrono::steady_clock::time_point& now) { 320 updateReaper(sensors, reaperTimer, now); 321 }); 322 323 if constexpr (debug) 324 { 325 std::cerr << "ExternalSensor " << sensorName << " created\n"; 326 } 327 } 328 }); 329 330 getter->getConfiguration(std::vector<std::string>{sensorType}); 331 } 332 333 int main() 334 { 335 if constexpr (debug) 336 { 337 std::cerr << "ExternalSensor service starting up\n"; 338 } 339 340 boost::asio::io_service io; 341 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); 342 systemBus->request_name("xyz.openbmc_project.ExternalSensor"); 343 sdbusplus::asio::object_server objectServer(systemBus); 344 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>> 345 sensors; 346 auto sensorsChanged = 347 std::make_shared<boost::container::flat_set<std::string>>(); 348 boost::asio::steady_timer reaperTimer(io); 349 350 io.post([&objectServer, &sensors, &systemBus, &reaperTimer]() { 351 createSensors(objectServer, sensors, systemBus, nullptr, reaperTimer); 352 }); 353 354 boost::asio::steady_timer filterTimer(io); 355 std::function<void(sdbusplus::message_t&)> eventHandler = 356 [&objectServer, &sensors, &systemBus, &sensorsChanged, &filterTimer, 357 &reaperTimer](sdbusplus::message_t& message) mutable { 358 if (message.is_method_error()) 359 { 360 std::cerr << "callback method error\n"; 361 return; 362 } 363 364 const auto* messagePath = message.get_path(); 365 sensorsChanged->insert(messagePath); 366 if constexpr (debug) 367 { 368 std::cerr << "ExternalSensor change event received: " << messagePath 369 << "\n"; 370 } 371 372 // this implicitly cancels the timer 373 filterTimer.expires_from_now(std::chrono::seconds(1)); 374 375 filterTimer.async_wait( 376 [&objectServer, &sensors, &systemBus, &sensorsChanged, 377 &reaperTimer](const boost::system::error_code& ec) mutable { 378 if (ec != boost::system::errc::success) 379 { 380 if (ec != boost::asio::error::operation_aborted) 381 { 382 std::cerr << "callback error: " << ec.message() << "\n"; 383 } 384 return; 385 } 386 387 createSensors(objectServer, sensors, systemBus, sensorsChanged, 388 reaperTimer); 389 }); 390 }; 391 392 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches = 393 setupPropertiesChangedMatches( 394 *systemBus, std::to_array<const char*>({sensorType}), eventHandler); 395 396 if constexpr (debug) 397 { 398 std::cerr << "ExternalSensor service entering main loop\n"; 399 } 400 401 io.run(); 402 } 403