blob: 04edb4f6bc937afec2966c80772f6275af67e352 [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
Zev Weiss054aad82022-08-18 01:37:34 -070058static const char* sensorType = "ExternalSensor";
Josh Lehan2a40e932020-09-02 11:48:14 -070059
Patrick Williams2aaf7172024-08-16 15:20:40 -040060void updateReaper(
61 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>&
62 sensors,
63 boost::asio::steady_timer& timer,
64 const std::chrono::steady_clock::time_point& now)
Josh Lehan72432172021-03-17 13:35:43 -070065{
66 // First pass, reap all stale sensors
Zev Weiss08cb50c2022-08-12 18:21:01 -070067 for (const auto& [name, sensor] : sensors)
Josh Lehan72432172021-03-17 13:35:43 -070068 {
Zev Weiss08cb50c2022-08-12 18:21:01 -070069 if (!sensor)
Josh Lehan72432172021-03-17 13:35:43 -070070 {
71 continue;
72 }
73
Zev Weiss08cb50c2022-08-12 18:21:01 -070074 if (!sensor->isAliveAndPerishable())
Josh Lehan72432172021-03-17 13:35:43 -070075 {
76 continue;
77 }
78
Zev Weiss08cb50c2022-08-12 18:21:01 -070079 if (!sensor->isAliveAndFresh(now))
Josh Lehan72432172021-03-17 13:35:43 -070080 {
81 // Mark sensor as dead, no longer alive
Zev Weiss08cb50c2022-08-12 18:21:01 -070082 sensor->writeInvalidate();
Josh Lehan72432172021-03-17 13:35:43 -070083 }
84 }
85
86 std::chrono::steady_clock::duration nextCheck;
87 bool needCheck = false;
88
89 // Second pass, determine timer interval to next check
Zev Weiss08cb50c2022-08-12 18:21:01 -070090 for (const auto& [name, sensor] : sensors)
Josh Lehan72432172021-03-17 13:35:43 -070091 {
Zev Weiss08cb50c2022-08-12 18:21:01 -070092 if (!sensor)
Josh Lehan72432172021-03-17 13:35:43 -070093 {
94 continue;
95 }
96
Zev Weiss08cb50c2022-08-12 18:21:01 -070097 if (!sensor->isAliveAndPerishable())
Josh Lehan72432172021-03-17 13:35:43 -070098 {
99 continue;
100 }
101
Zev Weiss08cb50c2022-08-12 18:21:01 -0700102 auto expiration = sensor->ageRemaining(now);
Josh Lehan72432172021-03-17 13:35:43 -0700103
104 if (needCheck)
105 {
106 nextCheck = std::min(nextCheck, expiration);
107 }
108 else
109 {
110 // Initialization
111 nextCheck = expiration;
112 needCheck = true;
113 }
114 }
115
116 if (!needCheck)
117 {
Alexander Hansen89be6142025-09-18 15:36:16 +0200118 lg2::debug("Next ExternalSensor timer idle");
Josh Lehan72432172021-03-17 13:35:43 -0700119
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 {
George Liud630b3a2025-02-20 11:02:49 +0800131 lg2::error(
132 "ExternalSensor timer scheduling problem: {ERROR_MESSAGE}",
133 "ERROR_MESSAGE", err.message());
Josh Lehan72432172021-03-17 13:35:43 -0700134 }
135 return;
136 }
Josh Lehan03627382021-03-17 13:35:43 -0700137
Josh Lehan72432172021-03-17 13:35:43 -0700138 updateReaper(sensors, timer, std::chrono::steady_clock::now());
139 });
140
Alexander Hansen89be6142025-09-18 15:36:16 +0200141 lg2::debug("Next ExternalSensor timer '{VALUE}' us", "VALUE",
142 std::chrono::duration_cast<std::chrono::microseconds>(nextCheck)
143 .count());
Josh Lehan72432172021-03-17 13:35:43 -0700144}
145
Josh Lehan2a40e932020-09-02 11:48:14 -0700146void createSensors(
Ed Tanous8a17c302021-09-02 15:07:11 -0700147 sdbusplus::asio::object_server& objectServer,
Josh Lehan2a40e932020-09-02 11:48:14 -0700148 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>&
149 sensors,
150 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
151 const std::shared_ptr<boost::container::flat_set<std::string>>&
Josh Lehan72432172021-03-17 13:35:43 -0700152 sensorsChanged,
153 boost::asio::steady_timer& reaperTimer)
Josh Lehan2a40e932020-09-02 11:48:14 -0700154{
Alexander Hansen89be6142025-09-18 15:36:16 +0200155 lg2::debug("ExternalSensor considering creating sensors");
Josh Lehan03627382021-03-17 13:35:43 -0700156
Josh Lehan2a40e932020-09-02 11:48:14 -0700157 auto getter = std::make_shared<GetSensorConfiguration>(
158 dbusConnection,
Ed Tanous8a17c302021-09-02 15:07:11 -0700159 [&objectServer, &sensors, &dbusConnection, sensorsChanged,
Josh Lehan72432172021-03-17 13:35:43 -0700160 &reaperTimer](const ManagedObjectType& sensorConfigurations) {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400161 bool firstScan = (sensorsChanged == nullptr);
Josh Lehan2a40e932020-09-02 11:48:14 -0700162
Patrick Williams2aaf7172024-08-16 15:20:40 -0400163 for (const std::pair<sdbusplus::message::object_path, SensorData>&
164 sensor : sensorConfigurations)
165 {
166 const std::string& interfacePath = sensor.first.str;
167 const SensorData& sensorData = sensor.second;
Ed Tanousbb679322022-05-16 16:10:00 -0700168
Patrick Williams2aaf7172024-08-16 15:20:40 -0400169 auto sensorBase =
170 sensorData.find(configInterfaceName(sensorType));
171 if (sensorBase == sensorData.end())
Josh Lehan2a40e932020-09-02 11:48:14 -0700172 {
George Liud630b3a2025-02-20 11:02:49 +0800173 lg2::error("Base configuration not found for '{PATH}'",
174 "PATH", interfacePath);
Ed Tanousbb679322022-05-16 16:10:00 -0700175 continue;
Josh Lehan72432172021-03-17 13:35:43 -0700176 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400177
178 const SensorBaseConfiguration& baseConfiguration = *sensorBase;
179 const SensorBaseConfigMap& baseConfigMap =
180 baseConfiguration.second;
181
182 // MinValue and MinValue are mandatory numeric parameters
183 auto minFound = baseConfigMap.find("MinValue");
184 if (minFound == baseConfigMap.end())
185 {
George Liud630b3a2025-02-20 11:02:49 +0800186 lg2::error("MinValue parameter not found for '{PATH}'",
187 "PATH", interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400188 continue;
189 }
190 double minValue =
191 std::visit(VariantToDoubleVisitor(), minFound->second);
192 if (!std::isfinite(minValue))
193 {
George Liud630b3a2025-02-20 11:02:49 +0800194 lg2::error("MinValue parameter not parsed for '{PATH}'",
195 "PATH", interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400196 continue;
197 }
198
199 auto maxFound = baseConfigMap.find("MaxValue");
200 if (maxFound == baseConfigMap.end())
201 {
George Liud630b3a2025-02-20 11:02:49 +0800202 lg2::error("MaxValue parameter not found for '{PATH}'",
203 "PATH", interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400204 continue;
205 }
206 double maxValue =
207 std::visit(VariantToDoubleVisitor(), maxFound->second);
208 if (!std::isfinite(maxValue))
209 {
George Liud630b3a2025-02-20 11:02:49 +0800210 lg2::error("MaxValue parameter not parsed for '{PATH}'",
211 "PATH", interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400212 continue;
213 }
214
215 double timeoutSecs = 0.0;
216
217 // Timeout is an optional numeric parameter
218 auto timeoutFound = baseConfigMap.find("Timeout");
219 if (timeoutFound != baseConfigMap.end())
220 {
221 timeoutSecs = std::visit(VariantToDoubleVisitor(),
222 timeoutFound->second);
223 }
224 if (!std::isfinite(timeoutSecs) || (timeoutSecs < 0.0))
225 {
George Liud630b3a2025-02-20 11:02:49 +0800226 lg2::error("Timeout parameter not parsed for '{PATH}'",
227 "PATH", interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400228 continue;
229 }
230
231 std::string sensorName;
232 std::string sensorUnits;
233
234 // Name and Units are mandatory string parameters
235 auto nameFound = baseConfigMap.find("Name");
236 if (nameFound == baseConfigMap.end())
237 {
George Liud630b3a2025-02-20 11:02:49 +0800238 lg2::error("Name parameter not found for '{PATH}'", "PATH",
239 interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400240 continue;
241 }
242 sensorName =
243 std::visit(VariantToStringVisitor(), nameFound->second);
244 if (sensorName.empty())
245 {
George Liud630b3a2025-02-20 11:02:49 +0800246 lg2::error("Name parameter not parsed for '{PATH}'", "PATH",
247 interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400248 continue;
249 }
250
251 auto unitsFound = baseConfigMap.find("Units");
252 if (unitsFound == baseConfigMap.end())
253 {
George Liud630b3a2025-02-20 11:02:49 +0800254 lg2::error("Units parameter not found for '{PATH}'", "PATH",
255 interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400256 continue;
257 }
258 sensorUnits =
259 std::visit(VariantToStringVisitor(), unitsFound->second);
260 if (sensorUnits.empty())
261 {
George Liud630b3a2025-02-20 11:02:49 +0800262 lg2::error("Units parameter not parsed for '{PATH}'",
263 "PATH", interfacePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400264 continue;
265 }
266
267 // on rescans, only update sensors we were signaled by
268 auto findSensor = sensors.find(sensorName);
269 if (!firstScan && (findSensor != sensors.end()))
270 {
271 std::string suffixName = "/";
272 suffixName += findSensor->second->name;
273 bool found = false;
274 for (auto it = sensorsChanged->begin();
275 it != sensorsChanged->end(); it++)
276 {
277 std::string suffixIt = "/";
278 suffixIt += *it;
279 if (suffixIt.ends_with(suffixName))
280 {
281 sensorsChanged->erase(it);
282 findSensor->second = nullptr;
283 found = true;
Alexander Hansen89be6142025-09-18 15:36:16 +0200284 lg2::debug("ExternalSensor '{NAME}' change found",
285 "NAME", sensorName);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400286 break;
287 }
288 }
289 if (!found)
290 {
291 continue;
292 }
293 }
294
295 std::vector<thresholds::Threshold> sensorThresholds;
296 if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
297 {
George Liud630b3a2025-02-20 11:02:49 +0800298 lg2::error("error populating thresholds for '{NAME}'",
299 "NAME", sensorName);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400300 }
301
302 PowerState readState = getPowerState(baseConfigMap);
303
304 auto& sensorEntry = sensors[sensorName];
305 sensorEntry = nullptr;
306
307 sensorEntry = std::make_shared<ExternalSensor>(
308 sensorType, objectServer, dbusConnection, sensorName,
309 sensorUnits, std::move(sensorThresholds), interfacePath,
310 maxValue, minValue, timeoutSecs, readState);
311 sensorEntry->initWriteHook(
312 [&sensors, &reaperTimer](
313 const std::chrono::steady_clock::time_point& now) {
314 updateReaper(sensors, reaperTimer, now);
315 });
316
Alexander Hansen89be6142025-09-18 15:36:16 +0200317 lg2::debug("ExternalSensor '{NAME}' created", "NAME",
318 sensorName);
Josh Lehan2a40e932020-09-02 11:48:14 -0700319 }
Patrick Williams2aaf7172024-08-16 15:20:40 -0400320 });
Josh Lehan2a40e932020-09-02 11:48:14 -0700321
322 getter->getConfiguration(std::vector<std::string>{sensorType});
323}
324
325int main()
326{
Alexander Hansen89be6142025-09-18 15:36:16 +0200327 lg2::debug("ExternalSensor service starting up");
Josh Lehan72432172021-03-17 13:35:43 -0700328
Ed Tanous1f978632023-02-28 18:16:39 -0800329 boost::asio::io_context io;
Josh Lehan2a40e932020-09-02 11:48:14 -0700330 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
Ed Tanous14ed5e92022-07-12 15:50:23 -0700331 sdbusplus::asio::object_server objectServer(systemBus, true);
332
333 objectServer.add_manager("/xyz/openbmc_project/sensors");
Josh Lehan2a40e932020-09-02 11:48:14 -0700334 systemBus->request_name("xyz.openbmc_project.ExternalSensor");
Ed Tanous14ed5e92022-07-12 15:50:23 -0700335
Josh Lehan2a40e932020-09-02 11:48:14 -0700336 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>
337 sensors;
Josh Lehan2a40e932020-09-02 11:48:14 -0700338 auto sensorsChanged =
339 std::make_shared<boost::container::flat_set<std::string>>();
Josh Lehan72432172021-03-17 13:35:43 -0700340 boost::asio::steady_timer reaperTimer(io);
Josh Lehan2a40e932020-09-02 11:48:14 -0700341
Patrick Williams2aaf7172024-08-16 15:20:40 -0400342 boost::asio::post(io, [&objectServer, &sensors, &systemBus,
343 &reaperTimer]() {
Ed Tanous8a17c302021-09-02 15:07:11 -0700344 createSensors(objectServer, sensors, systemBus, nullptr, reaperTimer);
Josh Lehan2a40e932020-09-02 11:48:14 -0700345 });
346
Ed Tanous9b4a20e2022-09-06 08:47:11 -0700347 boost::asio::steady_timer filterTimer(io);
Patrick Williams92f8f512022-07-22 19:26:55 -0500348 std::function<void(sdbusplus::message_t&)> eventHandler =
Ed Tanous8a17c302021-09-02 15:07:11 -0700349 [&objectServer, &sensors, &systemBus, &sensorsChanged, &filterTimer,
Patrick Williams92f8f512022-07-22 19:26:55 -0500350 &reaperTimer](sdbusplus::message_t& message) mutable {
Patrick Williams2aaf7172024-08-16 15:20:40 -0400351 if (message.is_method_error())
Josh Lehan2a40e932020-09-02 11:48:14 -0700352 {
George Liud630b3a2025-02-20 11:02:49 +0800353 lg2::error("callback method error");
Josh Lehan2a40e932020-09-02 11:48:14 -0700354 return;
355 }
Josh Lehan03627382021-03-17 13:35:43 -0700356
Patrick Williams2aaf7172024-08-16 15:20:40 -0400357 const auto* messagePath = message.get_path();
358 sensorsChanged->insert(messagePath);
Alexander Hansen89be6142025-09-18 15:36:16 +0200359 lg2::debug("ExternalSensor change event received: '{PATH}'", "PATH",
360 messagePath);
Patrick Williams2aaf7172024-08-16 15:20:40 -0400361
362 // this implicitly cancels the timer
363 filterTimer.expires_after(std::chrono::seconds(1));
364
365 filterTimer.async_wait(
366 [&objectServer, &sensors, &systemBus, &sensorsChanged,
367 &reaperTimer](const boost::system::error_code& ec) mutable {
368 if (ec != boost::system::errc::success)
369 {
370 if (ec != boost::asio::error::operation_aborted)
371 {
George Liud630b3a2025-02-20 11:02:49 +0800372 lg2::error("callback error: '{ERROR_MESSAGE}'",
373 "ERROR_MESSAGE", ec.message());
Patrick Williams2aaf7172024-08-16 15:20:40 -0400374 }
375 return;
376 }
377
378 createSensors(objectServer, sensors, systemBus,
379 sensorsChanged, reaperTimer);
380 });
381 };
Josh Lehan2a40e932020-09-02 11:48:14 -0700382
Zev Weiss214d9712022-08-12 12:54:31 -0700383 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
384 setupPropertiesChangedMatches(
385 *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
Josh Lehan2a40e932020-09-02 11:48:14 -0700386
Alexander Hansen89be6142025-09-18 15:36:16 +0200387 lg2::debug("ExternalSensor service entering main loop");
Josh Lehan72432172021-03-17 13:35:43 -0700388
Josh Lehan2a40e932020-09-02 11:48:14 -0700389 io.run();
390}