blob: e04fc78a4d7a8eb753b275a7110f7b9dd3d5f2d8 [file] [log] [blame]
Josh Lehan2a40e932020-09-02 11:48:14 -07001#include "ExternalSensor.hpp"
2#include "Utils.hpp"
3#include "VariantVisitors.hpp"
4
Josh Lehan2a40e932020-09-02 11:48:14 -07005#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)
Josh Lehan72432172021-03-17 13:35:43 -070029// can write to it, perhaps by using an IPMI or Redfish connection.
Josh Lehan2a40e932020-09-02 11:48:14 -070030
31// Unlike most other sensors, an external sensor does not correspond
Josh Lehan72432172021-03-17 13:35:43 -070032// to a hwmon file or any other kernel/hardware interface,
Josh Lehan2a40e932020-09-02 11:48:14 -070033// so, after initialization, this module does not have much to do,
34// but it handles reinitialization and thresholds, similar to the others.
Josh Lehan72432172021-03-17 13:35:43 -070035// 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.
Josh Lehan2a40e932020-09-02 11:48:14 -070038
39// As there is no corresponding driver or hardware to support,
40// all configuration of this sensor comes from the JSON parameters:
Josh Lehan72432172021-03-17 13:35:43 -070041// MinValue, MaxValue, Timeout, PowerState, Units, Name
Josh Lehan2a40e932020-09-02 11:48:14 -070042
Josh Lehan72432172021-03-17 13:35:43 -070043// The purpose of "Units" is to specify the physical characteristic
Josh Lehan2a40e932020-09-02 11:48:14 -070044// 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
Josh Lehan72432172021-03-17 13:35:43 -070046// 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/
Josh Lehan2a40e932020-09-02 11:48:14 -070051
Ed Tanous8a57ec02020-10-09 12:46:52 -070052static constexpr bool debug = false;
Josh Lehan2a40e932020-09-02 11:48:14 -070053
54static const char* sensorType =
55 "xyz.openbmc_project.Configuration.ExternalSensor";
56
Josh Lehan72432172021-03-17 13:35:43 -070057void updateReaper(boost::container::flat_map<
58 std::string, std::shared_ptr<ExternalSensor>>& sensors,
59 boost::asio::steady_timer& timer,
60 const std::chrono::steady_clock::time_point& now)
61{
62 // First pass, reap all stale sensors
Zev Weiss08cb50c2022-08-12 18:21:01 -070063 for (const auto& [name, sensor] : sensors)
Josh Lehan72432172021-03-17 13:35:43 -070064 {
Zev Weiss08cb50c2022-08-12 18:21:01 -070065 if (!sensor)
Josh Lehan72432172021-03-17 13:35:43 -070066 {
67 continue;
68 }
69
Zev Weiss08cb50c2022-08-12 18:21:01 -070070 if (!sensor->isAliveAndPerishable())
Josh Lehan72432172021-03-17 13:35:43 -070071 {
72 continue;
73 }
74
Zev Weiss08cb50c2022-08-12 18:21:01 -070075 if (!sensor->isAliveAndFresh(now))
Josh Lehan72432172021-03-17 13:35:43 -070076 {
77 // Mark sensor as dead, no longer alive
Zev Weiss08cb50c2022-08-12 18:21:01 -070078 sensor->writeInvalidate();
Josh Lehan72432172021-03-17 13:35:43 -070079 }
80 }
81
82 std::chrono::steady_clock::duration nextCheck;
83 bool needCheck = false;
84
85 // Second pass, determine timer interval to next check
Zev Weiss08cb50c2022-08-12 18:21:01 -070086 for (const auto& [name, sensor] : sensors)
Josh Lehan72432172021-03-17 13:35:43 -070087 {
Zev Weiss08cb50c2022-08-12 18:21:01 -070088 if (!sensor)
Josh Lehan72432172021-03-17 13:35:43 -070089 {
90 continue;
91 }
92
Zev Weiss08cb50c2022-08-12 18:21:01 -070093 if (!sensor->isAliveAndPerishable())
Josh Lehan72432172021-03-17 13:35:43 -070094 {
95 continue;
96 }
97
Zev Weiss08cb50c2022-08-12 18:21:01 -070098 auto expiration = sensor->ageRemaining(now);
Josh Lehan72432172021-03-17 13:35:43 -070099
100 if (needCheck)
101 {
102 nextCheck = std::min(nextCheck, expiration);
103 }
104 else
105 {
106 // Initialization
107 nextCheck = expiration;
108 needCheck = true;
109 }
110 }
111
112 if (!needCheck)
113 {
114 if constexpr (debug)
115 {
116 std::cerr << "Next ExternalSensor timer idle\n";
117 }
118
119 return;
120 }
121
122 timer.expires_at(now + nextCheck);
123
124 timer.async_wait([&sensors, &timer](const boost::system::error_code& err) {
125 if (err != boost::system::errc::success)
126 {
127 // Cancellation is normal, as timer is dynamically rescheduled
Josh Lehan03627382021-03-17 13:35:43 -0700128 if (err != boost::asio::error::operation_aborted)
Josh Lehan72432172021-03-17 13:35:43 -0700129 {
130 std::cerr << "ExternalSensor timer scheduling problem: "
131 << err.message() << "\n";
132 }
133 return;
134 }
Josh Lehan03627382021-03-17 13:35:43 -0700135
Josh Lehan72432172021-03-17 13:35:43 -0700136 updateReaper(sensors, timer, std::chrono::steady_clock::now());
137 });
138
139 if constexpr (debug)
140 {
141 std::cerr << "Next ExternalSensor timer "
142 << std::chrono::duration_cast<std::chrono::microseconds>(
143 nextCheck)
144 .count()
145 << " us\n";
146 }
147}
148
Josh Lehan2a40e932020-09-02 11:48:14 -0700149void createSensors(
Ed Tanous8a17c302021-09-02 15:07:11 -0700150 sdbusplus::asio::object_server& objectServer,
Josh Lehan2a40e932020-09-02 11:48:14 -0700151 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>&
152 sensors,
153 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
154 const std::shared_ptr<boost::container::flat_set<std::string>>&
Josh Lehan72432172021-03-17 13:35:43 -0700155 sensorsChanged,
156 boost::asio::steady_timer& reaperTimer)
Josh Lehan2a40e932020-09-02 11:48:14 -0700157{
Josh Lehan03627382021-03-17 13:35:43 -0700158 if constexpr (debug)
159 {
160 std::cerr << "ExternalSensor considering creating sensors\n";
161 }
162
Josh Lehan2a40e932020-09-02 11:48:14 -0700163 auto getter = std::make_shared<GetSensorConfiguration>(
164 dbusConnection,
Ed Tanous8a17c302021-09-02 15:07:11 -0700165 [&objectServer, &sensors, &dbusConnection, sensorsChanged,
Josh Lehan72432172021-03-17 13:35:43 -0700166 &reaperTimer](const ManagedObjectType& sensorConfigurations) {
Ed Tanousbb679322022-05-16 16:10:00 -0700167 bool firstScan = (sensorsChanged == nullptr);
Josh Lehan2a40e932020-09-02 11:48:14 -0700168
Ed Tanousbb679322022-05-16 16:10:00 -0700169 for (const std::pair<sdbusplus::message::object_path, SensorData>&
170 sensor : sensorConfigurations)
171 {
172 const std::string& interfacePath = sensor.first.str;
173 const SensorData& sensorData = sensor.second;
174
175 auto sensorBase = sensorData.find(sensorType);
176 if (sensorBase == sensorData.end())
Josh Lehan2a40e932020-09-02 11:48:14 -0700177 {
Ed Tanousbb679322022-05-16 16:10:00 -0700178 std::cerr << "Base configuration not found for "
179 << interfacePath << "\n";
180 continue;
181 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700182
Ed Tanousbb679322022-05-16 16:10:00 -0700183 const SensorBaseConfiguration& baseConfiguration = *sensorBase;
184 const SensorBaseConfigMap& baseConfigMap = baseConfiguration.second;
Josh Lehan2a40e932020-09-02 11:48:14 -0700185
Ed Tanousbb679322022-05-16 16:10:00 -0700186 // MinValue and MinValue are mandatory numeric parameters
187 auto minFound = baseConfigMap.find("MinValue");
188 if (minFound == baseConfigMap.end())
189 {
190 std::cerr << "MinValue parameter not found for "
191 << interfacePath << "\n";
192 continue;
193 }
194 double minValue =
195 std::visit(VariantToDoubleVisitor(), minFound->second);
196 if (!std::isfinite(minValue))
197 {
198 std::cerr << "MinValue parameter not parsed for "
199 << interfacePath << "\n";
200 continue;
201 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700202
Ed Tanousbb679322022-05-16 16:10:00 -0700203 auto maxFound = baseConfigMap.find("MaxValue");
204 if (maxFound == baseConfigMap.end())
205 {
206 std::cerr << "MaxValue parameter not found for "
207 << interfacePath << "\n";
208 continue;
209 }
210 double maxValue =
211 std::visit(VariantToDoubleVisitor(), maxFound->second);
212 if (!std::isfinite(maxValue))
213 {
214 std::cerr << "MaxValue parameter not parsed for "
215 << interfacePath << "\n";
216 continue;
217 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700218
Ed Tanousbb679322022-05-16 16:10:00 -0700219 double timeoutSecs = 0.0;
Josh Lehan2a40e932020-09-02 11:48:14 -0700220
Ed Tanousbb679322022-05-16 16:10:00 -0700221 // Timeout is an optional numeric parameter
222 auto timeoutFound = baseConfigMap.find("Timeout");
223 if (timeoutFound != baseConfigMap.end())
224 {
225 timeoutSecs =
226 std::visit(VariantToDoubleVisitor(), timeoutFound->second);
227 }
228 if (!(std::isfinite(timeoutSecs) && (timeoutSecs >= 0.0)))
229 {
230 std::cerr << "Timeout parameter not parsed for "
231 << interfacePath << "\n";
232 continue;
233 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700234
Ed Tanousbb679322022-05-16 16:10:00 -0700235 std::string sensorName;
236 std::string sensorUnits;
Josh Lehan72432172021-03-17 13:35:43 -0700237
Ed Tanousbb679322022-05-16 16:10:00 -0700238 // Name and Units are mandatory string parameters
239 auto nameFound = baseConfigMap.find("Name");
240 if (nameFound == baseConfigMap.end())
241 {
242 std::cerr << "Name parameter not found for " << interfacePath
243 << "\n";
244 continue;
245 }
246 sensorName =
247 std::visit(VariantToStringVisitor(), nameFound->second);
248 if (sensorName.empty())
249 {
250 std::cerr << "Name parameter not parsed for " << interfacePath
251 << "\n";
252 continue;
253 }
Josh Lehan72432172021-03-17 13:35:43 -0700254
Ed Tanousbb679322022-05-16 16:10:00 -0700255 auto unitsFound = baseConfigMap.find("Units");
256 if (unitsFound == baseConfigMap.end())
257 {
258 std::cerr << "Units parameter not found for " << interfacePath
259 << "\n";
260 continue;
261 }
262 sensorUnits =
263 std::visit(VariantToStringVisitor(), unitsFound->second);
264 if (sensorUnits.empty())
265 {
266 std::cerr << "Units parameter not parsed for " << interfacePath
267 << "\n";
268 continue;
269 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700270
Ed Tanousbb679322022-05-16 16:10:00 -0700271 // on rescans, only update sensors we were signaled by
272 auto findSensor = sensors.find(sensorName);
273 if (!firstScan && (findSensor != sensors.end()))
274 {
275 std::string suffixName = "/";
276 suffixName += findSensor->second->name;
277 bool found = false;
278 for (auto it = sensorsChanged->begin();
279 it != sensorsChanged->end(); it++)
Josh Lehan2a40e932020-09-02 11:48:14 -0700280 {
Ed Tanousbb679322022-05-16 16:10:00 -0700281 std::string suffixIt = "/";
282 suffixIt += *it;
Zev Weiss6c106d62022-08-17 20:50:00 -0700283 if (suffixIt.ends_with(suffixName))
Josh Lehan2a40e932020-09-02 11:48:14 -0700284 {
Ed Tanousbb679322022-05-16 16:10:00 -0700285 sensorsChanged->erase(it);
286 findSensor->second = nullptr;
287 found = true;
288 if constexpr (debug)
Josh Lehan2a40e932020-09-02 11:48:14 -0700289 {
Ed Tanousbb679322022-05-16 16:10:00 -0700290 std::cerr << "ExternalSensor " << sensorName
291 << " change found\n";
Josh Lehan2a40e932020-09-02 11:48:14 -0700292 }
Ed Tanousbb679322022-05-16 16:10:00 -0700293 break;
Josh Lehan2a40e932020-09-02 11:48:14 -0700294 }
295 }
Ed Tanousbb679322022-05-16 16:10:00 -0700296 if (!found)
Josh Lehan2a40e932020-09-02 11:48:14 -0700297 {
Ed Tanousbb679322022-05-16 16:10:00 -0700298 continue;
Josh Lehan72432172021-03-17 13:35:43 -0700299 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700300 }
Ed Tanousbb679322022-05-16 16:10:00 -0700301
302 std::vector<thresholds::Threshold> sensorThresholds;
303 if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
304 {
305 std::cerr << "error populating thresholds for " << sensorName
306 << "\n";
307 }
308
Zev Weissa4d27682022-07-19 15:30:36 -0700309 PowerState readState = getPowerState(baseConfigMap);
Ed Tanousbb679322022-05-16 16:10:00 -0700310
311 auto& sensorEntry = sensors[sensorName];
312 sensorEntry = nullptr;
313
314 sensorEntry = std::make_shared<ExternalSensor>(
315 sensorType, objectServer, dbusConnection, sensorName,
316 sensorUnits, std::move(sensorThresholds), interfacePath,
317 maxValue, minValue, timeoutSecs, readState);
318 sensorEntry->initWriteHook(
319 [&sensors, &reaperTimer](
320 const std::chrono::steady_clock::time_point& now) {
321 updateReaper(sensors, reaperTimer, now);
322 });
323
324 if constexpr (debug)
325 {
326 std::cerr << "ExternalSensor " << sensorName << " created\n";
327 }
328 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700329 });
330
331 getter->getConfiguration(std::vector<std::string>{sensorType});
332}
333
334int main()
335{
Josh Lehan72432172021-03-17 13:35:43 -0700336 if constexpr (debug)
337 {
338 std::cerr << "ExternalSensor service starting up\n";
339 }
340
Josh Lehan2a40e932020-09-02 11:48:14 -0700341 boost::asio::io_service io;
342 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
343 systemBus->request_name("xyz.openbmc_project.ExternalSensor");
344 sdbusplus::asio::object_server objectServer(systemBus);
345 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>
346 sensors;
Josh Lehan2a40e932020-09-02 11:48:14 -0700347 auto sensorsChanged =
348 std::make_shared<boost::container::flat_set<std::string>>();
Josh Lehan72432172021-03-17 13:35:43 -0700349 boost::asio::steady_timer reaperTimer(io);
Josh Lehan2a40e932020-09-02 11:48:14 -0700350
Ed Tanous8a17c302021-09-02 15:07:11 -0700351 io.post([&objectServer, &sensors, &systemBus, &reaperTimer]() {
352 createSensors(objectServer, sensors, systemBus, nullptr, reaperTimer);
Josh Lehan2a40e932020-09-02 11:48:14 -0700353 });
354
355 boost::asio::deadline_timer filterTimer(io);
Patrick Williams92f8f512022-07-22 19:26:55 -0500356 std::function<void(sdbusplus::message_t&)> eventHandler =
Ed Tanous8a17c302021-09-02 15:07:11 -0700357 [&objectServer, &sensors, &systemBus, &sensorsChanged, &filterTimer,
Patrick Williams92f8f512022-07-22 19:26:55 -0500358 &reaperTimer](sdbusplus::message_t& message) mutable {
Ed Tanousbb679322022-05-16 16:10:00 -0700359 if (message.is_method_error())
360 {
361 std::cerr << "callback method error\n";
362 return;
363 }
364
Ed Tanous2049bd22022-07-09 07:20:26 -0700365 const auto* messagePath = message.get_path();
Ed Tanousbb679322022-05-16 16:10:00 -0700366 sensorsChanged->insert(messagePath);
367 if constexpr (debug)
368 {
369 std::cerr << "ExternalSensor change event received: " << messagePath
370 << "\n";
371 }
372
373 // this implicitly cancels the timer
374 filterTimer.expires_from_now(boost::posix_time::seconds(1));
375
376 filterTimer.async_wait(
377 [&objectServer, &sensors, &systemBus, &sensorsChanged,
378 &reaperTimer](const boost::system::error_code& ec) mutable {
379 if (ec != boost::system::errc::success)
Josh Lehan2a40e932020-09-02 11:48:14 -0700380 {
Ed Tanousbb679322022-05-16 16:10:00 -0700381 if (ec != boost::asio::error::operation_aborted)
382 {
383 std::cerr << "callback error: " << ec.message() << "\n";
384 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700385 return;
386 }
Josh Lehan03627382021-03-17 13:35:43 -0700387
Ed Tanousbb679322022-05-16 16:10:00 -0700388 createSensors(objectServer, sensors, systemBus, sensorsChanged,
389 reaperTimer);
390 });
391 };
Josh Lehan2a40e932020-09-02 11:48:14 -0700392
Zev Weiss214d9712022-08-12 12:54:31 -0700393 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
394 setupPropertiesChangedMatches(
395 *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
Josh Lehan2a40e932020-09-02 11:48:14 -0700396
Josh Lehan72432172021-03-17 13:35:43 -0700397 if constexpr (debug)
398 {
399 std::cerr << "ExternalSensor service entering main loop\n";
400 }
401
Josh Lehan2a40e932020-09-02 11:48:14 -0700402 io.run();
403}