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