12a40e939SJosh Lehan #include "ExternalSensor.hpp" 22a40e939SJosh Lehan #include "Utils.hpp" 32a40e939SJosh Lehan #include "VariantVisitors.hpp" 42a40e939SJosh Lehan 52a40e939SJosh Lehan #include <boost/algorithm/string/replace.hpp> 62a40e939SJosh Lehan #include <boost/container/flat_map.hpp> 72a40e939SJosh Lehan #include <boost/container/flat_set.hpp> 82a40e939SJosh Lehan #include <sdbusplus/asio/connection.hpp> 92a40e939SJosh Lehan #include <sdbusplus/asio/object_server.hpp> 102a40e939SJosh Lehan #include <sdbusplus/bus/match.hpp> 112a40e939SJosh Lehan 122a40e939SJosh Lehan #include <array> 132a40e939SJosh Lehan #include <filesystem> 142a40e939SJosh Lehan #include <fstream> 152a40e939SJosh Lehan #include <functional> 162a40e939SJosh Lehan #include <memory> 172a40e939SJosh Lehan #include <regex> 182a40e939SJosh Lehan #include <stdexcept> 192a40e939SJosh Lehan #include <string> 202a40e939SJosh Lehan #include <utility> 212a40e939SJosh Lehan #include <variant> 222a40e939SJosh Lehan #include <vector> 232a40e939SJosh Lehan 242a40e939SJosh Lehan // Copied from HwmonTempSensor and inspired by 252a40e939SJosh Lehan // https://gerrit.openbmc-project.xyz/c/openbmc/dbus-sensors/+/35476 262a40e939SJosh Lehan 272a40e939SJosh Lehan // The ExternalSensor is a sensor whose value is intended to be writable 282a40e939SJosh Lehan // by something external to the BMC, so that the host (or something else) 297243217bSJosh Lehan // can write to it, perhaps by using an IPMI or Redfish connection. 302a40e939SJosh Lehan 312a40e939SJosh Lehan // Unlike most other sensors, an external sensor does not correspond 327243217bSJosh Lehan // to a hwmon file or any other kernel/hardware interface, 332a40e939SJosh Lehan // so, after initialization, this module does not have much to do, 342a40e939SJosh Lehan // but it handles reinitialization and thresholds, similar to the others. 357243217bSJosh Lehan // The main work of this module is to provide backing storage for a 367243217bSJosh Lehan // sensor that exists only virtually, and to provide an optional 377243217bSJosh Lehan // timeout service for detecting loss of timely updates. 382a40e939SJosh Lehan 392a40e939SJosh Lehan // As there is no corresponding driver or hardware to support, 402a40e939SJosh Lehan // all configuration of this sensor comes from the JSON parameters: 417243217bSJosh Lehan // MinValue, MaxValue, Timeout, PowerState, Units, Name 422a40e939SJosh Lehan 437243217bSJosh Lehan // The purpose of "Units" is to specify the physical characteristic 442a40e939SJosh Lehan // the external sensor is measuring, because with an external sensor 452a40e939SJosh Lehan // there is no other way to tell, and it will be used for the object path 467243217bSJosh Lehan // here: /xyz/openbmc_project/sensors/<Units>/<Name> 477243217bSJosh Lehan 487243217bSJosh Lehan // For more information, see external-sensor.md design document: 497243217bSJosh Lehan // https://gerrit.openbmc-project.xyz/c/openbmc/docs/+/41452 507243217bSJosh Lehan // https://github.com/openbmc/docs/tree/master/designs/ 512a40e939SJosh Lehan 528a57ec09SEd Tanous static constexpr bool debug = false; 532a40e939SJosh Lehan 54054aad8fSZev Weiss static const char* sensorType = "ExternalSensor"; 552a40e939SJosh Lehan 567243217bSJosh Lehan void updateReaper(boost::container::flat_map< 577243217bSJosh Lehan std::string, std::shared_ptr<ExternalSensor>>& sensors, 587243217bSJosh Lehan boost::asio::steady_timer& timer, 597243217bSJosh Lehan const std::chrono::steady_clock::time_point& now) 607243217bSJosh Lehan { 617243217bSJosh Lehan // First pass, reap all stale sensors 6208cb50c5SZev Weiss for (const auto& [name, sensor] : sensors) 637243217bSJosh Lehan { 6408cb50c5SZev Weiss if (!sensor) 657243217bSJosh Lehan { 667243217bSJosh Lehan continue; 677243217bSJosh Lehan } 687243217bSJosh Lehan 6908cb50c5SZev Weiss if (!sensor->isAliveAndPerishable()) 707243217bSJosh Lehan { 717243217bSJosh Lehan continue; 727243217bSJosh Lehan } 737243217bSJosh Lehan 7408cb50c5SZev Weiss if (!sensor->isAliveAndFresh(now)) 757243217bSJosh Lehan { 767243217bSJosh Lehan // Mark sensor as dead, no longer alive 7708cb50c5SZev Weiss sensor->writeInvalidate(); 787243217bSJosh Lehan } 797243217bSJosh Lehan } 807243217bSJosh Lehan 817243217bSJosh Lehan std::chrono::steady_clock::duration nextCheck; 827243217bSJosh Lehan bool needCheck = false; 837243217bSJosh Lehan 847243217bSJosh Lehan // Second pass, determine timer interval to next check 8508cb50c5SZev Weiss for (const auto& [name, sensor] : sensors) 867243217bSJosh Lehan { 8708cb50c5SZev Weiss if (!sensor) 887243217bSJosh Lehan { 897243217bSJosh Lehan continue; 907243217bSJosh Lehan } 917243217bSJosh Lehan 9208cb50c5SZev Weiss if (!sensor->isAliveAndPerishable()) 937243217bSJosh Lehan { 947243217bSJosh Lehan continue; 957243217bSJosh Lehan } 967243217bSJosh Lehan 9708cb50c5SZev Weiss auto expiration = sensor->ageRemaining(now); 987243217bSJosh Lehan 997243217bSJosh Lehan if (needCheck) 1007243217bSJosh Lehan { 1017243217bSJosh Lehan nextCheck = std::min(nextCheck, expiration); 1027243217bSJosh Lehan } 1037243217bSJosh Lehan else 1047243217bSJosh Lehan { 1057243217bSJosh Lehan // Initialization 1067243217bSJosh Lehan nextCheck = expiration; 1077243217bSJosh Lehan needCheck = true; 1087243217bSJosh Lehan } 1097243217bSJosh Lehan } 1107243217bSJosh Lehan 1117243217bSJosh Lehan if (!needCheck) 1127243217bSJosh Lehan { 1137243217bSJosh Lehan if constexpr (debug) 1147243217bSJosh Lehan { 1157243217bSJosh Lehan std::cerr << "Next ExternalSensor timer idle\n"; 1167243217bSJosh Lehan } 1177243217bSJosh Lehan 1187243217bSJosh Lehan return; 1197243217bSJosh Lehan } 1207243217bSJosh Lehan 1217243217bSJosh Lehan timer.expires_at(now + nextCheck); 1227243217bSJosh Lehan 1237243217bSJosh Lehan timer.async_wait([&sensors, &timer](const boost::system::error_code& err) { 1247243217bSJosh Lehan if (err != boost::system::errc::success) 1257243217bSJosh Lehan { 1267243217bSJosh Lehan // Cancellation is normal, as timer is dynamically rescheduled 1270362738dSJosh Lehan if (err != boost::asio::error::operation_aborted) 1287243217bSJosh Lehan { 1297243217bSJosh Lehan std::cerr << "ExternalSensor timer scheduling problem: " 1307243217bSJosh Lehan << err.message() << "\n"; 1317243217bSJosh Lehan } 1327243217bSJosh Lehan return; 1337243217bSJosh Lehan } 1340362738dSJosh Lehan 1357243217bSJosh Lehan updateReaper(sensors, timer, std::chrono::steady_clock::now()); 1367243217bSJosh Lehan }); 1377243217bSJosh Lehan 1387243217bSJosh Lehan if constexpr (debug) 1397243217bSJosh Lehan { 1407243217bSJosh Lehan std::cerr << "Next ExternalSensor timer " 1417243217bSJosh Lehan << std::chrono::duration_cast<std::chrono::microseconds>( 1427243217bSJosh Lehan nextCheck) 1437243217bSJosh Lehan .count() 1447243217bSJosh Lehan << " us\n"; 1457243217bSJosh Lehan } 1467243217bSJosh Lehan } 1477243217bSJosh Lehan 1482a40e939SJosh Lehan void createSensors( 1498a17c303SEd Tanous sdbusplus::asio::object_server& objectServer, 1502a40e939SJosh Lehan boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>& 1512a40e939SJosh Lehan sensors, 1522a40e939SJosh Lehan std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 1532a40e939SJosh Lehan const std::shared_ptr<boost::container::flat_set<std::string>>& 1547243217bSJosh Lehan sensorsChanged, 1557243217bSJosh Lehan boost::asio::steady_timer& reaperTimer) 1562a40e939SJosh Lehan { 1570362738dSJosh Lehan if constexpr (debug) 1580362738dSJosh Lehan { 1590362738dSJosh Lehan std::cerr << "ExternalSensor considering creating sensors\n"; 1600362738dSJosh Lehan } 1610362738dSJosh Lehan 1622a40e939SJosh Lehan auto getter = std::make_shared<GetSensorConfiguration>( 1632a40e939SJosh Lehan dbusConnection, 1648a17c303SEd Tanous [&objectServer, &sensors, &dbusConnection, sensorsChanged, 1657243217bSJosh Lehan &reaperTimer](const ManagedObjectType& sensorConfigurations) { 1662a40e939SJosh Lehan bool firstScan = (sensorsChanged == nullptr); 1672a40e939SJosh Lehan 1682a40e939SJosh Lehan for (const std::pair<sdbusplus::message::object_path, SensorData>& 1692a40e939SJosh Lehan sensor : sensorConfigurations) 1702a40e939SJosh Lehan { 1712a40e939SJosh Lehan const std::string& interfacePath = sensor.first.str; 1722a40e939SJosh Lehan const SensorData& sensorData = sensor.second; 1732a40e939SJosh Lehan 174054aad8fSZev Weiss auto sensorBase = sensorData.find(configInterfaceName(sensorType)); 1752a40e939SJosh Lehan if (sensorBase == sensorData.end()) 1762a40e939SJosh Lehan { 1772a40e939SJosh Lehan std::cerr << "Base configuration not found for " 1782a40e939SJosh Lehan << interfacePath << "\n"; 1792a40e939SJosh Lehan continue; 1802a40e939SJosh Lehan } 1812a40e939SJosh Lehan 1822a40e939SJosh Lehan const SensorBaseConfiguration& baseConfiguration = *sensorBase; 183bb67932aSEd Tanous const SensorBaseConfigMap& baseConfigMap = baseConfiguration.second; 1842a40e939SJosh Lehan 1852a40e939SJosh Lehan // MinValue and MinValue are mandatory numeric parameters 1862a40e939SJosh Lehan auto minFound = baseConfigMap.find("MinValue"); 1872a40e939SJosh Lehan if (minFound == baseConfigMap.end()) 1882a40e939SJosh Lehan { 1892a40e939SJosh Lehan std::cerr << "MinValue parameter not found for " 1902a40e939SJosh Lehan << interfacePath << "\n"; 1912a40e939SJosh Lehan continue; 1922a40e939SJosh Lehan } 193a771f6a7SEd Tanous double minValue = 1942a40e939SJosh Lehan std::visit(VariantToDoubleVisitor(), minFound->second); 1952a40e939SJosh Lehan if (!std::isfinite(minValue)) 1962a40e939SJosh Lehan { 1972a40e939SJosh Lehan std::cerr << "MinValue parameter not parsed for " 1982a40e939SJosh Lehan << interfacePath << "\n"; 1992a40e939SJosh Lehan continue; 2002a40e939SJosh Lehan } 2012a40e939SJosh Lehan 2022a40e939SJosh Lehan auto maxFound = baseConfigMap.find("MaxValue"); 2032a40e939SJosh Lehan if (maxFound == baseConfigMap.end()) 2042a40e939SJosh Lehan { 2052a40e939SJosh Lehan std::cerr << "MaxValue parameter not found for " 2062a40e939SJosh Lehan << interfacePath << "\n"; 2072a40e939SJosh Lehan continue; 2082a40e939SJosh Lehan } 209a771f6a7SEd Tanous double maxValue = 2102a40e939SJosh Lehan std::visit(VariantToDoubleVisitor(), maxFound->second); 2112a40e939SJosh Lehan if (!std::isfinite(maxValue)) 2122a40e939SJosh Lehan { 2132a40e939SJosh Lehan std::cerr << "MaxValue parameter not parsed for " 2142a40e939SJosh Lehan << interfacePath << "\n"; 2152a40e939SJosh Lehan continue; 2162a40e939SJosh Lehan } 2172a40e939SJosh Lehan 2187243217bSJosh Lehan double timeoutSecs = 0.0; 2192a40e939SJosh Lehan 2207243217bSJosh Lehan // Timeout is an optional numeric parameter 2217243217bSJosh Lehan auto timeoutFound = baseConfigMap.find("Timeout"); 2227243217bSJosh Lehan if (timeoutFound != baseConfigMap.end()) 2237243217bSJosh Lehan { 224bb67932aSEd Tanous timeoutSecs = 225bb67932aSEd Tanous std::visit(VariantToDoubleVisitor(), timeoutFound->second); 2267243217bSJosh Lehan } 2277243217bSJosh Lehan if (!(std::isfinite(timeoutSecs) && (timeoutSecs >= 0.0))) 2287243217bSJosh Lehan { 2297243217bSJosh Lehan std::cerr << "Timeout parameter not parsed for " 2307243217bSJosh Lehan << interfacePath << "\n"; 2317243217bSJosh Lehan continue; 2327243217bSJosh Lehan } 2337243217bSJosh Lehan 2347243217bSJosh Lehan std::string sensorName; 2357243217bSJosh Lehan std::string sensorUnits; 2367243217bSJosh Lehan 2377243217bSJosh Lehan // Name and Units are mandatory string parameters 2382a40e939SJosh Lehan auto nameFound = baseConfigMap.find("Name"); 2392a40e939SJosh Lehan if (nameFound == baseConfigMap.end()) 2402a40e939SJosh Lehan { 241bb67932aSEd Tanous std::cerr << "Name parameter not found for " << interfacePath 242bb67932aSEd Tanous << "\n"; 2432a40e939SJosh Lehan continue; 2442a40e939SJosh Lehan } 2452a40e939SJosh Lehan sensorName = 2462a40e939SJosh Lehan std::visit(VariantToStringVisitor(), nameFound->second); 2472a40e939SJosh Lehan if (sensorName.empty()) 2482a40e939SJosh Lehan { 249bb67932aSEd Tanous std::cerr << "Name parameter not parsed for " << interfacePath 250bb67932aSEd Tanous << "\n"; 2512a40e939SJosh Lehan continue; 2522a40e939SJosh Lehan } 2532a40e939SJosh Lehan 2547243217bSJosh Lehan auto unitsFound = baseConfigMap.find("Units"); 2557243217bSJosh Lehan if (unitsFound == baseConfigMap.end()) 2562a40e939SJosh Lehan { 257bb67932aSEd Tanous std::cerr << "Units parameter not found for " << interfacePath 258bb67932aSEd Tanous << "\n"; 2592a40e939SJosh Lehan continue; 2602a40e939SJosh Lehan } 2617243217bSJosh Lehan sensorUnits = 2627243217bSJosh Lehan std::visit(VariantToStringVisitor(), unitsFound->second); 2637243217bSJosh Lehan if (sensorUnits.empty()) 2642a40e939SJosh Lehan { 265bb67932aSEd Tanous std::cerr << "Units parameter not parsed for " << interfacePath 266bb67932aSEd Tanous << "\n"; 2672a40e939SJosh Lehan continue; 2682a40e939SJosh Lehan } 2692a40e939SJosh Lehan 2702a40e939SJosh Lehan // on rescans, only update sensors we were signaled by 2712a40e939SJosh Lehan auto findSensor = sensors.find(sensorName); 2722a40e939SJosh Lehan if (!firstScan && (findSensor != sensors.end())) 2732a40e939SJosh Lehan { 2742a40e939SJosh Lehan std::string suffixName = "/"; 2752a40e939SJosh Lehan suffixName += findSensor->second->name; 2762a40e939SJosh Lehan bool found = false; 2772a40e939SJosh Lehan for (auto it = sensorsChanged->begin(); 2782a40e939SJosh Lehan it != sensorsChanged->end(); it++) 2792a40e939SJosh Lehan { 2802a40e939SJosh Lehan std::string suffixIt = "/"; 2812a40e939SJosh Lehan suffixIt += *it; 2826c106d66SZev Weiss if (suffixIt.ends_with(suffixName)) 2832a40e939SJosh Lehan { 2842a40e939SJosh Lehan sensorsChanged->erase(it); 2852a40e939SJosh Lehan findSensor->second = nullptr; 2862a40e939SJosh Lehan found = true; 2870362738dSJosh Lehan if constexpr (debug) 2880362738dSJosh Lehan { 2890362738dSJosh Lehan std::cerr << "ExternalSensor " << sensorName 2900362738dSJosh Lehan << " change found\n"; 2910362738dSJosh Lehan } 2922a40e939SJosh Lehan break; 2932a40e939SJosh Lehan } 2942a40e939SJosh Lehan } 2952a40e939SJosh Lehan if (!found) 2962a40e939SJosh Lehan { 2972a40e939SJosh Lehan continue; 2982a40e939SJosh Lehan } 2992a40e939SJosh Lehan } 3002a40e939SJosh Lehan 3012a40e939SJosh Lehan std::vector<thresholds::Threshold> sensorThresholds; 3022a40e939SJosh Lehan if (!parseThresholdsFromConfig(sensorData, sensorThresholds)) 3032a40e939SJosh Lehan { 304bb67932aSEd Tanous std::cerr << "error populating thresholds for " << sensorName 305bb67932aSEd Tanous << "\n"; 3062a40e939SJosh Lehan } 3072a40e939SJosh Lehan 308a4d2768cSZev Weiss PowerState readState = getPowerState(baseConfigMap); 3092a40e939SJosh Lehan 3102a40e939SJosh Lehan auto& sensorEntry = sensors[sensorName]; 3112a40e939SJosh Lehan sensorEntry = nullptr; 3122a40e939SJosh Lehan 3132a40e939SJosh Lehan sensorEntry = std::make_shared<ExternalSensor>( 3142a40e939SJosh Lehan sensorType, objectServer, dbusConnection, sensorName, 3157243217bSJosh Lehan sensorUnits, std::move(sensorThresholds), interfacePath, 3160362738dSJosh Lehan maxValue, minValue, timeoutSecs, readState); 3170362738dSJosh Lehan sensorEntry->initWriteHook( 3187243217bSJosh Lehan [&sensors, &reaperTimer]( 3197243217bSJosh Lehan const std::chrono::steady_clock::time_point& now) { 3207243217bSJosh Lehan updateReaper(sensors, reaperTimer, now); 3217243217bSJosh Lehan }); 3227243217bSJosh Lehan 3237243217bSJosh Lehan if constexpr (debug) 3247243217bSJosh Lehan { 325bb67932aSEd Tanous std::cerr << "ExternalSensor " << sensorName << " created\n"; 3267243217bSJosh Lehan } 3272a40e939SJosh Lehan } 3282a40e939SJosh Lehan }); 3292a40e939SJosh Lehan 3302a40e939SJosh Lehan getter->getConfiguration(std::vector<std::string>{sensorType}); 3312a40e939SJosh Lehan } 3322a40e939SJosh Lehan 3332a40e939SJosh Lehan int main() 3342a40e939SJosh Lehan { 3357243217bSJosh Lehan if constexpr (debug) 3367243217bSJosh Lehan { 3377243217bSJosh Lehan std::cerr << "ExternalSensor service starting up\n"; 3387243217bSJosh Lehan } 3397243217bSJosh Lehan 3402a40e939SJosh Lehan boost::asio::io_service io; 3412a40e939SJosh Lehan auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); 3422a40e939SJosh Lehan systemBus->request_name("xyz.openbmc_project.ExternalSensor"); 3432a40e939SJosh Lehan sdbusplus::asio::object_server objectServer(systemBus); 3442a40e939SJosh Lehan boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>> 3452a40e939SJosh Lehan sensors; 3462a40e939SJosh Lehan auto sensorsChanged = 3472a40e939SJosh Lehan std::make_shared<boost::container::flat_set<std::string>>(); 3487243217bSJosh Lehan boost::asio::steady_timer reaperTimer(io); 3492a40e939SJosh Lehan 3508a17c303SEd Tanous io.post([&objectServer, &sensors, &systemBus, &reaperTimer]() { 3518a17c303SEd Tanous createSensors(objectServer, sensors, systemBus, nullptr, reaperTimer); 3522a40e939SJosh Lehan }); 3532a40e939SJosh Lehan 354*9b4a20e9SEd Tanous boost::asio::steady_timer filterTimer(io); 35592f8f515SPatrick Williams std::function<void(sdbusplus::message_t&)> eventHandler = 3568a17c303SEd Tanous [&objectServer, &sensors, &systemBus, &sensorsChanged, &filterTimer, 35792f8f515SPatrick Williams &reaperTimer](sdbusplus::message_t& message) mutable { 3582a40e939SJosh Lehan if (message.is_method_error()) 3592a40e939SJosh Lehan { 3602a40e939SJosh Lehan std::cerr << "callback method error\n"; 3612a40e939SJosh Lehan return; 3622a40e939SJosh Lehan } 3630362738dSJosh Lehan 3642049bd26SEd Tanous const auto* messagePath = message.get_path(); 3650362738dSJosh Lehan sensorsChanged->insert(messagePath); 3660362738dSJosh Lehan if constexpr (debug) 3670362738dSJosh Lehan { 368bb67932aSEd Tanous std::cerr << "ExternalSensor change event received: " << messagePath 369bb67932aSEd Tanous << "\n"; 3700362738dSJosh Lehan } 3710362738dSJosh Lehan 3722a40e939SJosh Lehan // this implicitly cancels the timer 373*9b4a20e9SEd Tanous filterTimer.expires_from_now(std::chrono::seconds(1)); 3742a40e939SJosh Lehan 3758a17c303SEd Tanous filterTimer.async_wait( 3768a17c303SEd Tanous [&objectServer, &sensors, &systemBus, &sensorsChanged, 3778a17c303SEd Tanous &reaperTimer](const boost::system::error_code& ec) mutable { 3780362738dSJosh Lehan if (ec != boost::system::errc::success) 3792a40e939SJosh Lehan { 3802a40e939SJosh Lehan if (ec != boost::asio::error::operation_aborted) 3812a40e939SJosh Lehan { 382bb67932aSEd Tanous std::cerr << "callback error: " << ec.message() << "\n"; 3832a40e939SJosh Lehan } 3842a40e939SJosh Lehan return; 3852a40e939SJosh Lehan } 3860362738dSJosh Lehan 387bb67932aSEd Tanous createSensors(objectServer, sensors, systemBus, sensorsChanged, 388bb67932aSEd Tanous reaperTimer); 3892a40e939SJosh Lehan }); 3902a40e939SJosh Lehan }; 3912a40e939SJosh Lehan 392214d9717SZev Weiss std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches = 393214d9717SZev Weiss setupPropertiesChangedMatches( 394214d9717SZev Weiss *systemBus, std::to_array<const char*>({sensorType}), eventHandler); 3952a40e939SJosh Lehan 3967243217bSJosh Lehan if constexpr (debug) 3977243217bSJosh Lehan { 3987243217bSJosh Lehan std::cerr << "ExternalSensor service entering main loop\n"; 3997243217bSJosh Lehan } 4007243217bSJosh Lehan 4012a40e939SJosh Lehan io.run(); 4022a40e939SJosh Lehan } 403