blob: 413a112a5d5f06133f7a1be9822fec436af7323c [file] [log] [blame]
Josh Lehan2a40e932020-09-02 11:48:14 -07001#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)
Josh Lehan72432172021-03-17 13:35:43 -070030// can write to it, perhaps by using an IPMI or Redfish connection.
Josh Lehan2a40e932020-09-02 11:48:14 -070031
32// Unlike most other sensors, an external sensor does not correspond
Josh Lehan72432172021-03-17 13:35:43 -070033// to a hwmon file or any other kernel/hardware interface,
Josh Lehan2a40e932020-09-02 11:48:14 -070034// so, after initialization, this module does not have much to do,
35// but it handles reinitialization and thresholds, similar to the others.
Josh Lehan72432172021-03-17 13:35:43 -070036// 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.
Josh Lehan2a40e932020-09-02 11:48:14 -070039
40// As there is no corresponding driver or hardware to support,
41// all configuration of this sensor comes from the JSON parameters:
Josh Lehan72432172021-03-17 13:35:43 -070042// MinValue, MaxValue, Timeout, PowerState, Units, Name
Josh Lehan2a40e932020-09-02 11:48:14 -070043
Josh Lehan72432172021-03-17 13:35:43 -070044// The purpose of "Units" is to specify the physical characteristic
Josh Lehan2a40e932020-09-02 11:48:14 -070045// 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
Josh Lehan72432172021-03-17 13:35:43 -070047// 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/
Josh Lehan2a40e932020-09-02 11:48:14 -070052
Ed Tanous8a57ec02020-10-09 12:46:52 -070053static constexpr bool debug = false;
Josh Lehan2a40e932020-09-02 11:48:14 -070054
55static const char* sensorType =
56 "xyz.openbmc_project.Configuration.ExternalSensor";
57
Josh Lehan72432172021-03-17 13:35:43 -070058void 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
Zev Weiss08cb50c2022-08-12 18:21:01 -070064 for (const auto& [name, sensor] : sensors)
Josh Lehan72432172021-03-17 13:35:43 -070065 {
Zev Weiss08cb50c2022-08-12 18:21:01 -070066 if (!sensor)
Josh Lehan72432172021-03-17 13:35:43 -070067 {
68 continue;
69 }
70
Zev Weiss08cb50c2022-08-12 18:21:01 -070071 if (!sensor->isAliveAndPerishable())
Josh Lehan72432172021-03-17 13:35:43 -070072 {
73 continue;
74 }
75
Zev Weiss08cb50c2022-08-12 18:21:01 -070076 if (!sensor->isAliveAndFresh(now))
Josh Lehan72432172021-03-17 13:35:43 -070077 {
78 // Mark sensor as dead, no longer alive
Zev Weiss08cb50c2022-08-12 18:21:01 -070079 sensor->writeInvalidate();
Josh Lehan72432172021-03-17 13:35:43 -070080 }
81 }
82
83 std::chrono::steady_clock::duration nextCheck;
84 bool needCheck = false;
85
86 // Second pass, determine timer interval to next check
Zev Weiss08cb50c2022-08-12 18:21:01 -070087 for (const auto& [name, sensor] : sensors)
Josh Lehan72432172021-03-17 13:35:43 -070088 {
Zev Weiss08cb50c2022-08-12 18:21:01 -070089 if (!sensor)
Josh Lehan72432172021-03-17 13:35:43 -070090 {
91 continue;
92 }
93
Zev Weiss08cb50c2022-08-12 18:21:01 -070094 if (!sensor->isAliveAndPerishable())
Josh Lehan72432172021-03-17 13:35:43 -070095 {
96 continue;
97 }
98
Zev Weiss08cb50c2022-08-12 18:21:01 -070099 auto expiration = sensor->ageRemaining(now);
Josh Lehan72432172021-03-17 13:35:43 -0700100
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
Josh Lehan03627382021-03-17 13:35:43 -0700129 if (err != boost::asio::error::operation_aborted)
Josh Lehan72432172021-03-17 13:35:43 -0700130 {
131 std::cerr << "ExternalSensor timer scheduling problem: "
132 << err.message() << "\n";
133 }
134 return;
135 }
Josh Lehan03627382021-03-17 13:35:43 -0700136
Josh Lehan72432172021-03-17 13:35:43 -0700137 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
Josh Lehan2a40e932020-09-02 11:48:14 -0700150void createSensors(
Ed Tanous8a17c302021-09-02 15:07:11 -0700151 sdbusplus::asio::object_server& objectServer,
Josh Lehan2a40e932020-09-02 11:48:14 -0700152 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>>&
Josh Lehan72432172021-03-17 13:35:43 -0700156 sensorsChanged,
157 boost::asio::steady_timer& reaperTimer)
Josh Lehan2a40e932020-09-02 11:48:14 -0700158{
Josh Lehan03627382021-03-17 13:35:43 -0700159 if constexpr (debug)
160 {
161 std::cerr << "ExternalSensor considering creating sensors\n";
162 }
163
Josh Lehan2a40e932020-09-02 11:48:14 -0700164 auto getter = std::make_shared<GetSensorConfiguration>(
165 dbusConnection,
Ed Tanous8a17c302021-09-02 15:07:11 -0700166 [&objectServer, &sensors, &dbusConnection, sensorsChanged,
Josh Lehan72432172021-03-17 13:35:43 -0700167 &reaperTimer](const ManagedObjectType& sensorConfigurations) {
Ed Tanousbb679322022-05-16 16:10:00 -0700168 bool firstScan = (sensorsChanged == nullptr);
Josh Lehan2a40e932020-09-02 11:48:14 -0700169
Ed Tanousbb679322022-05-16 16:10:00 -0700170 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())
Josh Lehan2a40e932020-09-02 11:48:14 -0700178 {
Ed Tanousbb679322022-05-16 16:10:00 -0700179 std::cerr << "Base configuration not found for "
180 << interfacePath << "\n";
181 continue;
182 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700183
Ed Tanousbb679322022-05-16 16:10:00 -0700184 const SensorBaseConfiguration& baseConfiguration = *sensorBase;
185 const SensorBaseConfigMap& baseConfigMap = baseConfiguration.second;
Josh Lehan2a40e932020-09-02 11:48:14 -0700186
Ed Tanousbb679322022-05-16 16:10:00 -0700187 // 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 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700203
Ed Tanousbb679322022-05-16 16:10:00 -0700204 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 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700219
Ed Tanousbb679322022-05-16 16:10:00 -0700220 double timeoutSecs = 0.0;
Josh Lehan2a40e932020-09-02 11:48:14 -0700221
Ed Tanousbb679322022-05-16 16:10:00 -0700222 // 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 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700235
Ed Tanousbb679322022-05-16 16:10:00 -0700236 std::string sensorName;
237 std::string sensorUnits;
Josh Lehan72432172021-03-17 13:35:43 -0700238
Ed Tanousbb679322022-05-16 16:10:00 -0700239 // 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 }
Josh Lehan72432172021-03-17 13:35:43 -0700255
Ed Tanousbb679322022-05-16 16:10:00 -0700256 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 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700271
Ed Tanousbb679322022-05-16 16:10:00 -0700272 // 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++)
Josh Lehan2a40e932020-09-02 11:48:14 -0700281 {
Ed Tanousbb679322022-05-16 16:10:00 -0700282 std::string suffixIt = "/";
283 suffixIt += *it;
284 if (boost::ends_with(suffixIt, suffixName))
Josh Lehan2a40e932020-09-02 11:48:14 -0700285 {
Ed Tanousbb679322022-05-16 16:10:00 -0700286 sensorsChanged->erase(it);
287 findSensor->second = nullptr;
288 found = true;
289 if constexpr (debug)
Josh Lehan2a40e932020-09-02 11:48:14 -0700290 {
Ed Tanousbb679322022-05-16 16:10:00 -0700291 std::cerr << "ExternalSensor " << sensorName
292 << " change found\n";
Josh Lehan2a40e932020-09-02 11:48:14 -0700293 }
Ed Tanousbb679322022-05-16 16:10:00 -0700294 break;
Josh Lehan2a40e932020-09-02 11:48:14 -0700295 }
296 }
Ed Tanousbb679322022-05-16 16:10:00 -0700297 if (!found)
Josh Lehan2a40e932020-09-02 11:48:14 -0700298 {
Ed Tanousbb679322022-05-16 16:10:00 -0700299 continue;
Josh Lehan72432172021-03-17 13:35:43 -0700300 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700301 }
Ed Tanousbb679322022-05-16 16:10:00 -0700302
303 std::vector<thresholds::Threshold> sensorThresholds;
304 if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
305 {
306 std::cerr << "error populating thresholds for " << sensorName
307 << "\n";
308 }
309
Zev Weissa4d27682022-07-19 15:30:36 -0700310 PowerState readState = getPowerState(baseConfigMap);
Ed Tanousbb679322022-05-16 16:10:00 -0700311
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 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700330 });
331
332 getter->getConfiguration(std::vector<std::string>{sensorType});
333}
334
335int main()
336{
Josh Lehan72432172021-03-17 13:35:43 -0700337 if constexpr (debug)
338 {
339 std::cerr << "ExternalSensor service starting up\n";
340 }
341
Josh Lehan2a40e932020-09-02 11:48:14 -0700342 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;
Josh Lehan2a40e932020-09-02 11:48:14 -0700348 auto sensorsChanged =
349 std::make_shared<boost::container::flat_set<std::string>>();
Josh Lehan72432172021-03-17 13:35:43 -0700350 boost::asio::steady_timer reaperTimer(io);
Josh Lehan2a40e932020-09-02 11:48:14 -0700351
Ed Tanous8a17c302021-09-02 15:07:11 -0700352 io.post([&objectServer, &sensors, &systemBus, &reaperTimer]() {
353 createSensors(objectServer, sensors, systemBus, nullptr, reaperTimer);
Josh Lehan2a40e932020-09-02 11:48:14 -0700354 });
355
356 boost::asio::deadline_timer filterTimer(io);
Patrick Williams92f8f512022-07-22 19:26:55 -0500357 std::function<void(sdbusplus::message_t&)> eventHandler =
Ed Tanous8a17c302021-09-02 15:07:11 -0700358 [&objectServer, &sensors, &systemBus, &sensorsChanged, &filterTimer,
Patrick Williams92f8f512022-07-22 19:26:55 -0500359 &reaperTimer](sdbusplus::message_t& message) mutable {
Ed Tanousbb679322022-05-16 16:10:00 -0700360 if (message.is_method_error())
361 {
362 std::cerr << "callback method error\n";
363 return;
364 }
365
Ed Tanous2049bd22022-07-09 07:20:26 -0700366 const auto* messagePath = message.get_path();
Ed Tanousbb679322022-05-16 16:10:00 -0700367 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)
Josh Lehan2a40e932020-09-02 11:48:14 -0700381 {
Ed Tanousbb679322022-05-16 16:10:00 -0700382 if (ec != boost::asio::error::operation_aborted)
383 {
384 std::cerr << "callback error: " << ec.message() << "\n";
385 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700386 return;
387 }
Josh Lehan03627382021-03-17 13:35:43 -0700388
Ed Tanousbb679322022-05-16 16:10:00 -0700389 createSensors(objectServer, sensors, systemBus, sensorsChanged,
390 reaperTimer);
391 });
392 };
Josh Lehan2a40e932020-09-02 11:48:14 -0700393
Zev Weiss214d9712022-08-12 12:54:31 -0700394 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
395 setupPropertiesChangedMatches(
396 *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
Josh Lehan2a40e932020-09-02 11:48:14 -0700397
Josh Lehan72432172021-03-17 13:35:43 -0700398 if constexpr (debug)
399 {
400 std::cerr << "ExternalSensor service entering main loop\n";
401 }
402
Josh Lehan2a40e932020-09-02 11:48:14 -0700403 io.run();
404}