blob: c8b21b4b0d4c745d086a19b2cf95ae2af83da784 [file] [log] [blame]
Josh Lehan2a40e932020-09-02 11:48:14 -07001#include "ExternalSensor.hpp"
Ed Tanouseacbfdd2024-04-04 12:00:24 -07002#include "Thresholds.hpp"
Josh Lehan2a40e932020-09-02 11:48:14 -07003#include "Utils.hpp"
4#include "VariantVisitors.hpp"
5
Ed Tanouseacbfdd2024-04-04 12:00:24 -07006#include <boost/asio/error.hpp>
7#include <boost/asio/io_context.hpp>
8#include <boost/asio/post.hpp>
9#include <boost/asio/steady_timer.hpp>
Josh Lehan2a40e932020-09-02 11:48:14 -070010#include <boost/container/flat_map.hpp>
11#include <boost/container/flat_set.hpp>
George Liud630b3a2025-02-20 11:02:49 +080012#include <phosphor-logging/lg2.hpp>
Josh Lehan2a40e932020-09-02 11:48:14 -070013#include <sdbusplus/asio/connection.hpp>
14#include <sdbusplus/asio/object_server.hpp>
15#include <sdbusplus/bus/match.hpp>
Ed Tanouseacbfdd2024-04-04 12:00:24 -070016#include <sdbusplus/message.hpp>
17#include <sdbusplus/message/native_types.hpp>
Josh Lehan2a40e932020-09-02 11:48:14 -070018
Ed Tanouseacbfdd2024-04-04 12:00:24 -070019#include <algorithm>
Josh Lehan2a40e932020-09-02 11:48:14 -070020#include <array>
Ed Tanouseacbfdd2024-04-04 12:00:24 -070021#include <chrono>
22#include <cmath>
Josh Lehan2a40e932020-09-02 11:48:14 -070023#include <functional>
24#include <memory>
Josh Lehan2a40e932020-09-02 11:48:14 -070025#include <string>
26#include <utility>
27#include <variant>
28#include <vector>
29
30// Copied from HwmonTempSensor and inspired by
31// https://gerrit.openbmc-project.xyz/c/openbmc/dbus-sensors/+/35476
32
33// The ExternalSensor is a sensor whose value is intended to be writable
34// by something external to the BMC, so that the host (or something else)
Josh Lehan72432172021-03-17 13:35:43 -070035// can write to it, perhaps by using an IPMI or Redfish connection.
Josh Lehan2a40e932020-09-02 11:48:14 -070036
37// Unlike most other sensors, an external sensor does not correspond
Josh Lehan72432172021-03-17 13:35:43 -070038// to a hwmon file or any other kernel/hardware interface,
Josh Lehan2a40e932020-09-02 11:48:14 -070039// so, after initialization, this module does not have much to do,
40// but it handles reinitialization and thresholds, similar to the others.
Josh Lehan72432172021-03-17 13:35:43 -070041// The main work of this module is to provide backing storage for a
42// sensor that exists only virtually, and to provide an optional
43// timeout service for detecting loss of timely updates.
Josh Lehan2a40e932020-09-02 11:48:14 -070044
45// As there is no corresponding driver or hardware to support,
46// all configuration of this sensor comes from the JSON parameters:
Josh Lehan72432172021-03-17 13:35:43 -070047// MinValue, MaxValue, Timeout, PowerState, Units, Name
Josh Lehan2a40e932020-09-02 11:48:14 -070048
Josh Lehan72432172021-03-17 13:35:43 -070049// The purpose of "Units" is to specify the physical characteristic
Josh Lehan2a40e932020-09-02 11:48:14 -070050// the external sensor is measuring, because with an external sensor
51// there is no other way to tell, and it will be used for the object path
Josh Lehan72432172021-03-17 13:35:43 -070052// here: /xyz/openbmc_project/sensors/<Units>/<Name>
53
54// For more information, see external-sensor.md design document:
55// https://gerrit.openbmc-project.xyz/c/openbmc/docs/+/41452
56// https://github.com/openbmc/docs/tree/master/designs/
Josh Lehan2a40e932020-09-02 11:48:14 -070057
Ed Tanous8a57ec02020-10-09 12:46:52 -070058static constexpr bool debug = false;
Josh Lehan2a40e932020-09-02 11:48:14 -070059
Zev Weiss054aad82022-08-18 01:37:34 -070060static const char* sensorType = "ExternalSensor";
Josh Lehan2a40e932020-09-02 11:48:14 -070061
Patrick Williams2aaf7172024-08-16 15:20:40 -040062void updateReaper(
63 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>&
64 sensors,
65 boost::asio::steady_timer& timer,
66 const std::chrono::steady_clock::time_point& now)
Josh Lehan72432172021-03-17 13:35:43 -070067{
68 // First pass, reap all stale sensors
Zev Weiss08cb50c2022-08-12 18:21:01 -070069 for (const auto& [name, sensor] : sensors)
Josh Lehan72432172021-03-17 13:35:43 -070070 {
Zev Weiss08cb50c2022-08-12 18:21:01 -070071 if (!sensor)
Josh Lehan72432172021-03-17 13:35:43 -070072 {
73 continue;
74 }
75
Zev Weiss08cb50c2022-08-12 18:21:01 -070076 if (!sensor->isAliveAndPerishable())
Josh Lehan72432172021-03-17 13:35:43 -070077 {
78 continue;
79 }
80
Zev Weiss08cb50c2022-08-12 18:21:01 -070081 if (!sensor->isAliveAndFresh(now))
Josh Lehan72432172021-03-17 13:35:43 -070082 {
83 // Mark sensor as dead, no longer alive
Zev Weiss08cb50c2022-08-12 18:21:01 -070084 sensor->writeInvalidate();
Josh Lehan72432172021-03-17 13:35:43 -070085 }
86 }
87
88 std::chrono::steady_clock::duration nextCheck;
89 bool needCheck = false;
90
91 // Second pass, determine timer interval to next check
Zev Weiss08cb50c2022-08-12 18:21:01 -070092 for (const auto& [name, sensor] : sensors)
Josh Lehan72432172021-03-17 13:35:43 -070093 {
Zev Weiss08cb50c2022-08-12 18:21:01 -070094 if (!sensor)
Josh Lehan72432172021-03-17 13:35:43 -070095 {
96 continue;
97 }
98
Zev Weiss08cb50c2022-08-12 18:21:01 -070099 if (!sensor->isAliveAndPerishable())
Josh Lehan72432172021-03-17 13:35:43 -0700100 {
101 continue;
102 }
103
Zev Weiss08cb50c2022-08-12 18:21:01 -0700104 auto expiration = sensor->ageRemaining(now);
Josh Lehan72432172021-03-17 13:35:43 -0700105
106 if (needCheck)
107 {
108 nextCheck = std::min(nextCheck, expiration);
109 }
110 else
111 {
112 // Initialization
113 nextCheck = expiration;
114 needCheck = true;
115 }
116 }
117
118 if (!needCheck)
119 {
120 if constexpr (debug)
121 {
George Liud630b3a2025-02-20 11:02:49 +0800122 lg2::error("Next ExternalSensor timer idle");
Josh Lehan72432172021-03-17 13:35:43 -0700123 }
124
125 return;
126 }
127
128 timer.expires_at(now + nextCheck);
129
130 timer.async_wait([&sensors, &timer](const boost::system::error_code& err) {
131 if (err != boost::system::errc::success)
132 {
133 // Cancellation is normal, as timer is dynamically rescheduled
Josh Lehan03627382021-03-17 13:35:43 -0700134 if (err != boost::asio::error::operation_aborted)
Josh Lehan72432172021-03-17 13:35:43 -0700135 {
George Liud630b3a2025-02-20 11:02:49 +0800136 lg2::error(
137 "ExternalSensor timer scheduling problem: {ERROR_MESSAGE}",
138 "ERROR_MESSAGE", err.message());
Josh Lehan72432172021-03-17 13:35:43 -0700139 }
140 return;
141 }
Josh Lehan03627382021-03-17 13:35:43 -0700142
Josh Lehan72432172021-03-17 13:35:43 -0700143 updateReaper(sensors, timer, std::chrono::steady_clock::now());
144 });
145
146 if constexpr (debug)
147 {
George Liud630b3a2025-02-20 11:02:49 +0800148 lg2::error(
149 "Next ExternalSensor timer '{VALUE}' us", "VALUE",
150 std::chrono::duration_cast<std::chrono::microseconds>(nextCheck)
151 .count());
Josh Lehan72432172021-03-17 13:35:43 -0700152 }
153}
154
Josh Lehan2a40e932020-09-02 11:48:14 -0700155void createSensors(
Ed Tanous8a17c302021-09-02 15:07:11 -0700156 sdbusplus::asio::object_server& objectServer,
Josh Lehan2a40e932020-09-02 11:48:14 -0700157 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>&
158 sensors,
159 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
160 const std::shared_ptr<boost::container::flat_set<std::string>>&
Josh Lehan72432172021-03-17 13:35:43 -0700161 sensorsChanged,
162 boost::asio::steady_timer& reaperTimer)
Josh Lehan2a40e932020-09-02 11:48:14 -0700163{
Josh Lehan03627382021-03-17 13:35:43 -0700164 if constexpr (debug)
165 {
George Liud630b3a2025-02-20 11:02:49 +0800166 lg2::error("ExternalSensor considering creating sensors");
Josh Lehan03627382021-03-17 13:35:43 -0700167 }
168
Josh Lehan2a40e932020-09-02 11:48:14 -0700169 auto getter = std::make_shared<GetSensorConfiguration>(
170 dbusConnection,
Ed Tanous8a17c302021-09-02 15:07:11 -0700171 [&objectServer, &sensors, &dbusConnection, sensorsChanged,
Josh Lehan72432172021-03-17 13:35:43 -0700172 &reaperTimer](const ManagedObjectType& sensorConfigurations) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400173 bool firstScan = (sensorsChanged == nullptr);
Josh Lehan2a40e932020-09-02 11:48:14 -0700174
Patrick Williams2aaf7172024-08-16 15:20:40 -0400175 for (const std::pair<sdbusplus::message::object_path, SensorData>&
176 sensor : sensorConfigurations)
177 {
178 const std::string& interfacePath = sensor.first.str;
179 const SensorData& sensorData = sensor.second;
Ed Tanousbb679322022-05-16 16:10:00 -0700180
Patrick Williams2aaf7172024-08-16 15:20:40 -0400181 auto sensorBase =
182 sensorData.find(configInterfaceName(sensorType));
183 if (sensorBase == sensorData.end())
Josh Lehan2a40e932020-09-02 11:48:14 -0700184 {
George Liud630b3a2025-02-20 11:02:49 +0800185 lg2::error("Base configuration not found for '{PATH}'",
186 "PATH", interfacePath);
Ed Tanousbb679322022-05-16 16:10:00 -0700187 continue;
Josh Lehan72432172021-03-17 13:35:43 -0700188 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400189
190 const SensorBaseConfiguration& baseConfiguration = *sensorBase;
191 const SensorBaseConfigMap& baseConfigMap =
192 baseConfiguration.second;
193
194 // MinValue and MinValue are mandatory numeric parameters
195 auto minFound = baseConfigMap.find("MinValue");
196 if (minFound == baseConfigMap.end())
197 {
George Liud630b3a2025-02-20 11:02:49 +0800198 lg2::error("MinValue parameter not found for '{PATH}'",
199 "PATH", interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400200 continue;
201 }
202 double minValue =
203 std::visit(VariantToDoubleVisitor(), minFound->second);
204 if (!std::isfinite(minValue))
205 {
George Liud630b3a2025-02-20 11:02:49 +0800206 lg2::error("MinValue parameter not parsed for '{PATH}'",
207 "PATH", interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400208 continue;
209 }
210
211 auto maxFound = baseConfigMap.find("MaxValue");
212 if (maxFound == baseConfigMap.end())
213 {
George Liud630b3a2025-02-20 11:02:49 +0800214 lg2::error("MaxValue parameter not found for '{PATH}'",
215 "PATH", interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400216 continue;
217 }
218 double maxValue =
219 std::visit(VariantToDoubleVisitor(), maxFound->second);
220 if (!std::isfinite(maxValue))
221 {
George Liud630b3a2025-02-20 11:02:49 +0800222 lg2::error("MaxValue parameter not parsed for '{PATH}'",
223 "PATH", interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400224 continue;
225 }
226
227 double timeoutSecs = 0.0;
228
229 // Timeout is an optional numeric parameter
230 auto timeoutFound = baseConfigMap.find("Timeout");
231 if (timeoutFound != baseConfigMap.end())
232 {
233 timeoutSecs = std::visit(VariantToDoubleVisitor(),
234 timeoutFound->second);
235 }
236 if (!std::isfinite(timeoutSecs) || (timeoutSecs < 0.0))
237 {
George Liud630b3a2025-02-20 11:02:49 +0800238 lg2::error("Timeout parameter not parsed for '{PATH}'",
239 "PATH", interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400240 continue;
241 }
242
243 std::string sensorName;
244 std::string sensorUnits;
245
246 // Name and Units are mandatory string parameters
247 auto nameFound = baseConfigMap.find("Name");
248 if (nameFound == baseConfigMap.end())
249 {
George Liud630b3a2025-02-20 11:02:49 +0800250 lg2::error("Name parameter not found for '{PATH}'", "PATH",
251 interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400252 continue;
253 }
254 sensorName =
255 std::visit(VariantToStringVisitor(), nameFound->second);
256 if (sensorName.empty())
257 {
George Liud630b3a2025-02-20 11:02:49 +0800258 lg2::error("Name parameter not parsed for '{PATH}'", "PATH",
259 interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400260 continue;
261 }
262
263 auto unitsFound = baseConfigMap.find("Units");
264 if (unitsFound == baseConfigMap.end())
265 {
George Liud630b3a2025-02-20 11:02:49 +0800266 lg2::error("Units parameter not found for '{PATH}'", "PATH",
267 interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400268 continue;
269 }
270 sensorUnits =
271 std::visit(VariantToStringVisitor(), unitsFound->second);
272 if (sensorUnits.empty())
273 {
George Liud630b3a2025-02-20 11:02:49 +0800274 lg2::error("Units parameter not parsed for '{PATH}'",
275 "PATH", interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400276 continue;
277 }
278
279 // on rescans, only update sensors we were signaled by
280 auto findSensor = sensors.find(sensorName);
281 if (!firstScan && (findSensor != sensors.end()))
282 {
283 std::string suffixName = "/";
284 suffixName += findSensor->second->name;
285 bool found = false;
286 for (auto it = sensorsChanged->begin();
287 it != sensorsChanged->end(); it++)
288 {
289 std::string suffixIt = "/";
290 suffixIt += *it;
291 if (suffixIt.ends_with(suffixName))
292 {
293 sensorsChanged->erase(it);
294 findSensor->second = nullptr;
295 found = true;
296 if constexpr (debug)
297 {
George Liud630b3a2025-02-20 11:02:49 +0800298 lg2::error(
299 "ExternalSensor '{NAME}' change found",
300 "NAME", sensorName);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400301 }
302 break;
303 }
304 }
305 if (!found)
306 {
307 continue;
308 }
309 }
310
311 std::vector<thresholds::Threshold> sensorThresholds;
312 if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
313 {
George Liud630b3a2025-02-20 11:02:49 +0800314 lg2::error("error populating thresholds for '{NAME}'",
315 "NAME", sensorName);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400316 }
317
318 PowerState readState = getPowerState(baseConfigMap);
319
320 auto& sensorEntry = sensors[sensorName];
321 sensorEntry = nullptr;
322
323 sensorEntry = std::make_shared<ExternalSensor>(
324 sensorType, objectServer, dbusConnection, sensorName,
325 sensorUnits, std::move(sensorThresholds), interfacePath,
326 maxValue, minValue, timeoutSecs, readState);
327 sensorEntry->initWriteHook(
328 [&sensors, &reaperTimer](
329 const std::chrono::steady_clock::time_point& now) {
330 updateReaper(sensors, reaperTimer, now);
331 });
332
333 if constexpr (debug)
334 {
George Liud630b3a2025-02-20 11:02:49 +0800335 lg2::error("ExternalSensor '{NAME}' created", "NAME",
336 sensorName);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400337 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700338 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400339 });
Josh Lehan2a40e932020-09-02 11:48:14 -0700340
341 getter->getConfiguration(std::vector<std::string>{sensorType});
342}
343
344int main()
345{
Josh Lehan72432172021-03-17 13:35:43 -0700346 if constexpr (debug)
347 {
George Liud630b3a2025-02-20 11:02:49 +0800348 lg2::error("ExternalSensor service starting up");
Josh Lehan72432172021-03-17 13:35:43 -0700349 }
350
Ed Tanous1f978632023-02-28 18:16:39 -0800351 boost::asio::io_context io;
Josh Lehan2a40e932020-09-02 11:48:14 -0700352 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
Ed Tanous14ed5e92022-07-12 15:50:23 -0700353 sdbusplus::asio::object_server objectServer(systemBus, true);
354
355 objectServer.add_manager("/xyz/openbmc_project/sensors");
Josh Lehan2a40e932020-09-02 11:48:14 -0700356 systemBus->request_name("xyz.openbmc_project.ExternalSensor");
Ed Tanous14ed5e92022-07-12 15:50:23 -0700357
Josh Lehan2a40e932020-09-02 11:48:14 -0700358 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>
359 sensors;
Josh Lehan2a40e932020-09-02 11:48:14 -0700360 auto sensorsChanged =
361 std::make_shared<boost::container::flat_set<std::string>>();
Josh Lehan72432172021-03-17 13:35:43 -0700362 boost::asio::steady_timer reaperTimer(io);
Josh Lehan2a40e932020-09-02 11:48:14 -0700363
Patrick Williams2aaf7172024-08-16 15:20:40 -0400364 boost::asio::post(io, [&objectServer, &sensors, &systemBus,
365 &reaperTimer]() {
Ed Tanous8a17c302021-09-02 15:07:11 -0700366 createSensors(objectServer, sensors, systemBus, nullptr, reaperTimer);
Josh Lehan2a40e932020-09-02 11:48:14 -0700367 });
368
Ed Tanous9b4a20e2022-09-06 08:47:11 -0700369 boost::asio::steady_timer filterTimer(io);
Patrick Williams92f8f512022-07-22 19:26:55 -0500370 std::function<void(sdbusplus::message_t&)> eventHandler =
Ed Tanous8a17c302021-09-02 15:07:11 -0700371 [&objectServer, &sensors, &systemBus, &sensorsChanged, &filterTimer,
Patrick Williams92f8f512022-07-22 19:26:55 -0500372 &reaperTimer](sdbusplus::message_t& message) mutable {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400373 if (message.is_method_error())
Josh Lehan2a40e932020-09-02 11:48:14 -0700374 {
George Liud630b3a2025-02-20 11:02:49 +0800375 lg2::error("callback method error");
Josh Lehan2a40e932020-09-02 11:48:14 -0700376 return;
377 }
Josh Lehan03627382021-03-17 13:35:43 -0700378
Patrick Williams2aaf7172024-08-16 15:20:40 -0400379 const auto* messagePath = message.get_path();
380 sensorsChanged->insert(messagePath);
381 if constexpr (debug)
382 {
George Liud630b3a2025-02-20 11:02:49 +0800383 lg2::error("ExternalSensor change event received: '{PATH}'",
384 "PATH", messagePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400385 }
386
387 // this implicitly cancels the timer
388 filterTimer.expires_after(std::chrono::seconds(1));
389
390 filterTimer.async_wait(
391 [&objectServer, &sensors, &systemBus, &sensorsChanged,
392 &reaperTimer](const boost::system::error_code& ec) mutable {
393 if (ec != boost::system::errc::success)
394 {
395 if (ec != boost::asio::error::operation_aborted)
396 {
George Liud630b3a2025-02-20 11:02:49 +0800397 lg2::error("callback error: '{ERROR_MESSAGE}'",
398 "ERROR_MESSAGE", ec.message());
Patrick Williams2aaf7172024-08-16 15:20:40 -0400399 }
400 return;
401 }
402
403 createSensors(objectServer, sensors, systemBus,
404 sensorsChanged, reaperTimer);
405 });
406 };
Josh Lehan2a40e932020-09-02 11:48:14 -0700407
Zev Weiss214d9712022-08-12 12:54:31 -0700408 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
409 setupPropertiesChangedMatches(
410 *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
Josh Lehan2a40e932020-09-02 11:48:14 -0700411
Josh Lehan72432172021-03-17 13:35:43 -0700412 if constexpr (debug)
413 {
George Liud630b3a2025-02-20 11:02:49 +0800414 lg2::error("ExternalSensor service entering main loop");
Josh Lehan72432172021-03-17 13:35:43 -0700415 }
416
Josh Lehan2a40e932020-09-02 11:48:14 -0700417 io.run();
418}