blob: adaa8e33400cfe1278e92d19da86da210cb3019f [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
Zev Weiss054aad82022-08-18 01:37:34 -070054static const char* sensorType = "ExternalSensor";
Josh Lehan2a40e932020-09-02 11:48:14 -070055
Josh Lehan72432172021-03-17 13:35:43 -070056void 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
Zev Weiss08cb50c2022-08-12 18:21:01 -070062 for (const auto& [name, sensor] : sensors)
Josh Lehan72432172021-03-17 13:35:43 -070063 {
Zev Weiss08cb50c2022-08-12 18:21:01 -070064 if (!sensor)
Josh Lehan72432172021-03-17 13:35:43 -070065 {
66 continue;
67 }
68
Zev Weiss08cb50c2022-08-12 18:21:01 -070069 if (!sensor->isAliveAndPerishable())
Josh Lehan72432172021-03-17 13:35:43 -070070 {
71 continue;
72 }
73
Zev Weiss08cb50c2022-08-12 18:21:01 -070074 if (!sensor->isAliveAndFresh(now))
Josh Lehan72432172021-03-17 13:35:43 -070075 {
76 // Mark sensor as dead, no longer alive
Zev Weiss08cb50c2022-08-12 18:21:01 -070077 sensor->writeInvalidate();
Josh Lehan72432172021-03-17 13:35:43 -070078 }
79 }
80
81 std::chrono::steady_clock::duration nextCheck;
82 bool needCheck = false;
83
84 // Second pass, determine timer interval to next check
Zev Weiss08cb50c2022-08-12 18:21:01 -070085 for (const auto& [name, sensor] : sensors)
Josh Lehan72432172021-03-17 13:35:43 -070086 {
Zev Weiss08cb50c2022-08-12 18:21:01 -070087 if (!sensor)
Josh Lehan72432172021-03-17 13:35:43 -070088 {
89 continue;
90 }
91
Zev Weiss08cb50c2022-08-12 18:21:01 -070092 if (!sensor->isAliveAndPerishable())
Josh Lehan72432172021-03-17 13:35:43 -070093 {
94 continue;
95 }
96
Zev Weiss08cb50c2022-08-12 18:21:01 -070097 auto expiration = sensor->ageRemaining(now);
Josh Lehan72432172021-03-17 13:35:43 -070098
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
Josh Lehan03627382021-03-17 13:35:43 -0700127 if (err != boost::asio::error::operation_aborted)
Josh Lehan72432172021-03-17 13:35:43 -0700128 {
129 std::cerr << "ExternalSensor timer scheduling problem: "
130 << err.message() << "\n";
131 }
132 return;
133 }
Josh Lehan03627382021-03-17 13:35:43 -0700134
Josh Lehan72432172021-03-17 13:35:43 -0700135 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
Josh Lehan2a40e932020-09-02 11:48:14 -0700148void createSensors(
Ed Tanous8a17c302021-09-02 15:07:11 -0700149 sdbusplus::asio::object_server& objectServer,
Josh Lehan2a40e932020-09-02 11:48:14 -0700150 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>>&
Josh Lehan72432172021-03-17 13:35:43 -0700154 sensorsChanged,
155 boost::asio::steady_timer& reaperTimer)
Josh Lehan2a40e932020-09-02 11:48:14 -0700156{
Josh Lehan03627382021-03-17 13:35:43 -0700157 if constexpr (debug)
158 {
159 std::cerr << "ExternalSensor considering creating sensors\n";
160 }
161
Josh Lehan2a40e932020-09-02 11:48:14 -0700162 auto getter = std::make_shared<GetSensorConfiguration>(
163 dbusConnection,
Ed Tanous8a17c302021-09-02 15:07:11 -0700164 [&objectServer, &sensors, &dbusConnection, sensorsChanged,
Josh Lehan72432172021-03-17 13:35:43 -0700165 &reaperTimer](const ManagedObjectType& sensorConfigurations) {
Ed Tanousbb679322022-05-16 16:10:00 -0700166 bool firstScan = (sensorsChanged == nullptr);
Josh Lehan2a40e932020-09-02 11:48:14 -0700167
Ed Tanousbb679322022-05-16 16:10:00 -0700168 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
Zev Weiss054aad82022-08-18 01:37:34 -0700174 auto sensorBase = sensorData.find(configInterfaceName(sensorType));
Ed Tanousbb679322022-05-16 16:10:00 -0700175 if (sensorBase == sensorData.end())
Josh Lehan2a40e932020-09-02 11:48:14 -0700176 {
Ed Tanousbb679322022-05-16 16:10:00 -0700177 std::cerr << "Base configuration not found for "
178 << interfacePath << "\n";
179 continue;
180 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700181
Ed Tanousbb679322022-05-16 16:10:00 -0700182 const SensorBaseConfiguration& baseConfiguration = *sensorBase;
183 const SensorBaseConfigMap& baseConfigMap = baseConfiguration.second;
Josh Lehan2a40e932020-09-02 11:48:14 -0700184
Ed Tanousbb679322022-05-16 16:10:00 -0700185 // 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 }
Patrick Williams779c96a2023-05-10 07:50:42 -0500193 double minValue = std::visit(VariantToDoubleVisitor(),
194 minFound->second);
Ed Tanousbb679322022-05-16 16:10:00 -0700195 if (!std::isfinite(minValue))
196 {
197 std::cerr << "MinValue parameter not parsed for "
198 << interfacePath << "\n";
199 continue;
200 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700201
Ed Tanousbb679322022-05-16 16:10:00 -0700202 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 }
Patrick Williams779c96a2023-05-10 07:50:42 -0500209 double maxValue = std::visit(VariantToDoubleVisitor(),
210 maxFound->second);
Ed Tanousbb679322022-05-16 16:10:00 -0700211 if (!std::isfinite(maxValue))
212 {
213 std::cerr << "MaxValue parameter not parsed for "
214 << interfacePath << "\n";
215 continue;
216 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700217
Ed Tanousbb679322022-05-16 16:10:00 -0700218 double timeoutSecs = 0.0;
Josh Lehan2a40e932020-09-02 11:48:14 -0700219
Ed Tanousbb679322022-05-16 16:10:00 -0700220 // Timeout is an optional numeric parameter
221 auto timeoutFound = baseConfigMap.find("Timeout");
222 if (timeoutFound != baseConfigMap.end())
223 {
Patrick Williams779c96a2023-05-10 07:50:42 -0500224 timeoutSecs = std::visit(VariantToDoubleVisitor(),
225 timeoutFound->second);
Ed Tanousbb679322022-05-16 16:10:00 -0700226 }
Patrick Williams9da019c2022-09-28 09:19:48 -0500227 if (!std::isfinite(timeoutSecs) || (timeoutSecs < 0.0))
Ed Tanousbb679322022-05-16 16:10:00 -0700228 {
229 std::cerr << "Timeout parameter not parsed for "
230 << interfacePath << "\n";
231 continue;
232 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700233
Ed Tanousbb679322022-05-16 16:10:00 -0700234 std::string sensorName;
235 std::string sensorUnits;
Josh Lehan72432172021-03-17 13:35:43 -0700236
Ed Tanousbb679322022-05-16 16:10:00 -0700237 // 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 }
Patrick Williams779c96a2023-05-10 07:50:42 -0500245 sensorName = std::visit(VariantToStringVisitor(),
246 nameFound->second);
Ed Tanousbb679322022-05-16 16:10:00 -0700247 if (sensorName.empty())
248 {
249 std::cerr << "Name parameter not parsed for " << interfacePath
250 << "\n";
251 continue;
252 }
Josh Lehan72432172021-03-17 13:35:43 -0700253
Ed Tanousbb679322022-05-16 16:10:00 -0700254 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 }
Patrick Williams779c96a2023-05-10 07:50:42 -0500261 sensorUnits = std::visit(VariantToStringVisitor(),
262 unitsFound->second);
Ed Tanousbb679322022-05-16 16:10:00 -0700263 if (sensorUnits.empty())
264 {
265 std::cerr << "Units parameter not parsed for " << interfacePath
266 << "\n";
267 continue;
268 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700269
Ed Tanousbb679322022-05-16 16:10:00 -0700270 // 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++)
Josh Lehan2a40e932020-09-02 11:48:14 -0700279 {
Ed Tanousbb679322022-05-16 16:10:00 -0700280 std::string suffixIt = "/";
281 suffixIt += *it;
Zev Weiss6c106d62022-08-17 20:50:00 -0700282 if (suffixIt.ends_with(suffixName))
Josh Lehan2a40e932020-09-02 11:48:14 -0700283 {
Ed Tanousbb679322022-05-16 16:10:00 -0700284 sensorsChanged->erase(it);
285 findSensor->second = nullptr;
286 found = true;
287 if constexpr (debug)
Josh Lehan2a40e932020-09-02 11:48:14 -0700288 {
Ed Tanousbb679322022-05-16 16:10:00 -0700289 std::cerr << "ExternalSensor " << sensorName
290 << " change found\n";
Josh Lehan2a40e932020-09-02 11:48:14 -0700291 }
Ed Tanousbb679322022-05-16 16:10:00 -0700292 break;
Josh Lehan2a40e932020-09-02 11:48:14 -0700293 }
294 }
Ed Tanousbb679322022-05-16 16:10:00 -0700295 if (!found)
Josh Lehan2a40e932020-09-02 11:48:14 -0700296 {
Ed Tanousbb679322022-05-16 16:10:00 -0700297 continue;
Josh Lehan72432172021-03-17 13:35:43 -0700298 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700299 }
Ed Tanousbb679322022-05-16 16:10:00 -0700300
301 std::vector<thresholds::Threshold> sensorThresholds;
302 if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
303 {
304 std::cerr << "error populating thresholds for " << sensorName
305 << "\n";
306 }
307
Zev Weissa4d27682022-07-19 15:30:36 -0700308 PowerState readState = getPowerState(baseConfigMap);
Ed Tanousbb679322022-05-16 16:10:00 -0700309
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 }
Patrick Williams597e8422023-10-20 11:19:01 -0500328 });
Josh Lehan2a40e932020-09-02 11:48:14 -0700329
330 getter->getConfiguration(std::vector<std::string>{sensorType});
331}
332
333int main()
334{
Josh Lehan72432172021-03-17 13:35:43 -0700335 if constexpr (debug)
336 {
337 std::cerr << "ExternalSensor service starting up\n";
338 }
339
Ed Tanous1f978632023-02-28 18:16:39 -0800340 boost::asio::io_context io;
Josh Lehan2a40e932020-09-02 11:48:14 -0700341 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
Ed Tanous14ed5e92022-07-12 15:50:23 -0700342 sdbusplus::asio::object_server objectServer(systemBus, true);
343
344 objectServer.add_manager("/xyz/openbmc_project/sensors");
Josh Lehan2a40e932020-09-02 11:48:14 -0700345 systemBus->request_name("xyz.openbmc_project.ExternalSensor");
Ed Tanous14ed5e92022-07-12 15:50:23 -0700346
Josh Lehan2a40e932020-09-02 11:48:14 -0700347 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>
348 sensors;
Josh Lehan2a40e932020-09-02 11:48:14 -0700349 auto sensorsChanged =
350 std::make_shared<boost::container::flat_set<std::string>>();
Josh Lehan72432172021-03-17 13:35:43 -0700351 boost::asio::steady_timer reaperTimer(io);
Josh Lehan2a40e932020-09-02 11:48:14 -0700352
Ed Tanous83db50c2023-03-01 10:20:24 -0800353 boost::asio::post(io,
354 [&objectServer, &sensors, &systemBus, &reaperTimer]() {
Ed Tanous8a17c302021-09-02 15:07:11 -0700355 createSensors(objectServer, sensors, systemBus, nullptr, reaperTimer);
Josh Lehan2a40e932020-09-02 11:48:14 -0700356 });
357
Ed Tanous9b4a20e2022-09-06 08:47:11 -0700358 boost::asio::steady_timer filterTimer(io);
Patrick Williams92f8f512022-07-22 19:26:55 -0500359 std::function<void(sdbusplus::message_t&)> eventHandler =
Ed Tanous8a17c302021-09-02 15:07:11 -0700360 [&objectServer, &sensors, &systemBus, &sensorsChanged, &filterTimer,
Patrick Williams92f8f512022-07-22 19:26:55 -0500361 &reaperTimer](sdbusplus::message_t& message) mutable {
Ed Tanousbb679322022-05-16 16:10:00 -0700362 if (message.is_method_error())
363 {
364 std::cerr << "callback method error\n";
365 return;
366 }
367
Ed Tanous2049bd22022-07-09 07:20:26 -0700368 const auto* messagePath = message.get_path();
Ed Tanousbb679322022-05-16 16:10:00 -0700369 sensorsChanged->insert(messagePath);
370 if constexpr (debug)
371 {
372 std::cerr << "ExternalSensor change event received: " << messagePath
373 << "\n";
374 }
375
376 // this implicitly cancels the timer
Ed Tanous83db50c2023-03-01 10:20:24 -0800377 filterTimer.expires_after(std::chrono::seconds(1));
Ed Tanousbb679322022-05-16 16:10:00 -0700378
379 filterTimer.async_wait(
380 [&objectServer, &sensors, &systemBus, &sensorsChanged,
381 &reaperTimer](const boost::system::error_code& ec) mutable {
382 if (ec != boost::system::errc::success)
Josh Lehan2a40e932020-09-02 11:48:14 -0700383 {
Ed Tanousbb679322022-05-16 16:10:00 -0700384 if (ec != boost::asio::error::operation_aborted)
385 {
386 std::cerr << "callback error: " << ec.message() << "\n";
387 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700388 return;
389 }
Josh Lehan03627382021-03-17 13:35:43 -0700390
Ed Tanousbb679322022-05-16 16:10:00 -0700391 createSensors(objectServer, sensors, systemBus, sensorsChanged,
392 reaperTimer);
393 });
394 };
Josh Lehan2a40e932020-09-02 11:48:14 -0700395
Zev Weiss214d9712022-08-12 12:54:31 -0700396 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
397 setupPropertiesChangedMatches(
398 *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
Josh Lehan2a40e932020-09-02 11:48:14 -0700399
Josh Lehan72432172021-03-17 13:35:43 -0700400 if constexpr (debug)
401 {
402 std::cerr << "ExternalSensor service entering main loop\n";
403 }
404
Josh Lehan2a40e932020-09-02 11:48:14 -0700405 io.run();
406}