blob: 3beb5b937b3ee61dbc2f632445ef16d87fc6d067 [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>
12#include <sdbusplus/asio/connection.hpp>
13#include <sdbusplus/asio/object_server.hpp>
14#include <sdbusplus/bus/match.hpp>
Ed Tanouseacbfdd2024-04-04 12:00:24 -070015#include <sdbusplus/message.hpp>
16#include <sdbusplus/message/native_types.hpp>
Josh Lehan2a40e932020-09-02 11:48:14 -070017
Ed Tanouseacbfdd2024-04-04 12:00:24 -070018#include <algorithm>
Josh Lehan2a40e932020-09-02 11:48:14 -070019#include <array>
Ed Tanouseacbfdd2024-04-04 12:00:24 -070020#include <chrono>
21#include <cmath>
Josh Lehan2a40e932020-09-02 11:48:14 -070022#include <functional>
Ed Tanouseacbfdd2024-04-04 12:00:24 -070023#include <iostream>
Josh Lehan2a40e932020-09-02 11:48:14 -070024#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
Josh Lehan72432172021-03-17 13:35:43 -070062void updateReaper(boost::container::flat_map<
63 std::string, std::shared_ptr<ExternalSensor>>& sensors,
64 boost::asio::steady_timer& timer,
65 const std::chrono::steady_clock::time_point& now)
66{
67 // First pass, reap all stale sensors
Zev Weiss08cb50c2022-08-12 18:21:01 -070068 for (const auto& [name, sensor] : sensors)
Josh Lehan72432172021-03-17 13:35:43 -070069 {
Zev Weiss08cb50c2022-08-12 18:21:01 -070070 if (!sensor)
Josh Lehan72432172021-03-17 13:35:43 -070071 {
72 continue;
73 }
74
Zev Weiss08cb50c2022-08-12 18:21:01 -070075 if (!sensor->isAliveAndPerishable())
Josh Lehan72432172021-03-17 13:35:43 -070076 {
77 continue;
78 }
79
Zev Weiss08cb50c2022-08-12 18:21:01 -070080 if (!sensor->isAliveAndFresh(now))
Josh Lehan72432172021-03-17 13:35:43 -070081 {
82 // Mark sensor as dead, no longer alive
Zev Weiss08cb50c2022-08-12 18:21:01 -070083 sensor->writeInvalidate();
Josh Lehan72432172021-03-17 13:35:43 -070084 }
85 }
86
87 std::chrono::steady_clock::duration nextCheck;
88 bool needCheck = false;
89
90 // Second pass, determine timer interval to next check
Zev Weiss08cb50c2022-08-12 18:21:01 -070091 for (const auto& [name, sensor] : sensors)
Josh Lehan72432172021-03-17 13:35:43 -070092 {
Zev Weiss08cb50c2022-08-12 18:21:01 -070093 if (!sensor)
Josh Lehan72432172021-03-17 13:35:43 -070094 {
95 continue;
96 }
97
Zev Weiss08cb50c2022-08-12 18:21:01 -070098 if (!sensor->isAliveAndPerishable())
Josh Lehan72432172021-03-17 13:35:43 -070099 {
100 continue;
101 }
102
Zev Weiss08cb50c2022-08-12 18:21:01 -0700103 auto expiration = sensor->ageRemaining(now);
Josh Lehan72432172021-03-17 13:35:43 -0700104
105 if (needCheck)
106 {
107 nextCheck = std::min(nextCheck, expiration);
108 }
109 else
110 {
111 // Initialization
112 nextCheck = expiration;
113 needCheck = true;
114 }
115 }
116
117 if (!needCheck)
118 {
119 if constexpr (debug)
120 {
121 std::cerr << "Next ExternalSensor timer idle\n";
122 }
123
124 return;
125 }
126
127 timer.expires_at(now + nextCheck);
128
129 timer.async_wait([&sensors, &timer](const boost::system::error_code& err) {
130 if (err != boost::system::errc::success)
131 {
132 // Cancellation is normal, as timer is dynamically rescheduled
Josh Lehan03627382021-03-17 13:35:43 -0700133 if (err != boost::asio::error::operation_aborted)
Josh Lehan72432172021-03-17 13:35:43 -0700134 {
135 std::cerr << "ExternalSensor timer scheduling problem: "
136 << err.message() << "\n";
137 }
138 return;
139 }
Josh Lehan03627382021-03-17 13:35:43 -0700140
Josh Lehan72432172021-03-17 13:35:43 -0700141 updateReaper(sensors, timer, std::chrono::steady_clock::now());
142 });
143
144 if constexpr (debug)
145 {
146 std::cerr << "Next ExternalSensor timer "
147 << std::chrono::duration_cast<std::chrono::microseconds>(
148 nextCheck)
149 .count()
150 << " us\n";
151 }
152}
153
Josh Lehan2a40e932020-09-02 11:48:14 -0700154void createSensors(
Ed Tanous8a17c302021-09-02 15:07:11 -0700155 sdbusplus::asio::object_server& objectServer,
Josh Lehan2a40e932020-09-02 11:48:14 -0700156 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>&
157 sensors,
158 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
159 const std::shared_ptr<boost::container::flat_set<std::string>>&
Josh Lehan72432172021-03-17 13:35:43 -0700160 sensorsChanged,
161 boost::asio::steady_timer& reaperTimer)
Josh Lehan2a40e932020-09-02 11:48:14 -0700162{
Josh Lehan03627382021-03-17 13:35:43 -0700163 if constexpr (debug)
164 {
165 std::cerr << "ExternalSensor considering creating sensors\n";
166 }
167
Josh Lehan2a40e932020-09-02 11:48:14 -0700168 auto getter = std::make_shared<GetSensorConfiguration>(
169 dbusConnection,
Ed Tanous8a17c302021-09-02 15:07:11 -0700170 [&objectServer, &sensors, &dbusConnection, sensorsChanged,
Josh Lehan72432172021-03-17 13:35:43 -0700171 &reaperTimer](const ManagedObjectType& sensorConfigurations) {
Ed Tanousbb679322022-05-16 16:10:00 -0700172 bool firstScan = (sensorsChanged == nullptr);
Josh Lehan2a40e932020-09-02 11:48:14 -0700173
Ed Tanousbb679322022-05-16 16:10:00 -0700174 for (const std::pair<sdbusplus::message::object_path, SensorData>&
175 sensor : sensorConfigurations)
176 {
177 const std::string& interfacePath = sensor.first.str;
178 const SensorData& sensorData = sensor.second;
179
Zev Weiss054aad82022-08-18 01:37:34 -0700180 auto sensorBase = sensorData.find(configInterfaceName(sensorType));
Ed Tanousbb679322022-05-16 16:10:00 -0700181 if (sensorBase == sensorData.end())
Josh Lehan2a40e932020-09-02 11:48:14 -0700182 {
Ed Tanousbb679322022-05-16 16:10:00 -0700183 std::cerr << "Base configuration not found for "
184 << interfacePath << "\n";
185 continue;
186 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700187
Ed Tanousbb679322022-05-16 16:10:00 -0700188 const SensorBaseConfiguration& baseConfiguration = *sensorBase;
189 const SensorBaseConfigMap& baseConfigMap = baseConfiguration.second;
Josh Lehan2a40e932020-09-02 11:48:14 -0700190
Ed Tanousbb679322022-05-16 16:10:00 -0700191 // MinValue and MinValue are mandatory numeric parameters
192 auto minFound = baseConfigMap.find("MinValue");
193 if (minFound == baseConfigMap.end())
194 {
195 std::cerr << "MinValue parameter not found for "
196 << interfacePath << "\n";
197 continue;
198 }
Patrick Williams779c96a2023-05-10 07:50:42 -0500199 double minValue = std::visit(VariantToDoubleVisitor(),
200 minFound->second);
Ed Tanousbb679322022-05-16 16:10:00 -0700201 if (!std::isfinite(minValue))
202 {
203 std::cerr << "MinValue parameter not parsed for "
204 << interfacePath << "\n";
205 continue;
206 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700207
Ed Tanousbb679322022-05-16 16:10:00 -0700208 auto maxFound = baseConfigMap.find("MaxValue");
209 if (maxFound == baseConfigMap.end())
210 {
211 std::cerr << "MaxValue parameter not found for "
212 << interfacePath << "\n";
213 continue;
214 }
Patrick Williams779c96a2023-05-10 07:50:42 -0500215 double maxValue = std::visit(VariantToDoubleVisitor(),
216 maxFound->second);
Ed Tanousbb679322022-05-16 16:10:00 -0700217 if (!std::isfinite(maxValue))
218 {
219 std::cerr << "MaxValue parameter not parsed for "
220 << interfacePath << "\n";
221 continue;
222 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700223
Ed Tanousbb679322022-05-16 16:10:00 -0700224 double timeoutSecs = 0.0;
Josh Lehan2a40e932020-09-02 11:48:14 -0700225
Ed Tanousbb679322022-05-16 16:10:00 -0700226 // Timeout is an optional numeric parameter
227 auto timeoutFound = baseConfigMap.find("Timeout");
228 if (timeoutFound != baseConfigMap.end())
229 {
Patrick Williams779c96a2023-05-10 07:50:42 -0500230 timeoutSecs = std::visit(VariantToDoubleVisitor(),
231 timeoutFound->second);
Ed Tanousbb679322022-05-16 16:10:00 -0700232 }
Patrick Williams9da019c2022-09-28 09:19:48 -0500233 if (!std::isfinite(timeoutSecs) || (timeoutSecs < 0.0))
Ed Tanousbb679322022-05-16 16:10:00 -0700234 {
235 std::cerr << "Timeout parameter not parsed for "
236 << interfacePath << "\n";
237 continue;
238 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700239
Ed Tanousbb679322022-05-16 16:10:00 -0700240 std::string sensorName;
241 std::string sensorUnits;
Josh Lehan72432172021-03-17 13:35:43 -0700242
Ed Tanousbb679322022-05-16 16:10:00 -0700243 // Name and Units are mandatory string parameters
244 auto nameFound = baseConfigMap.find("Name");
245 if (nameFound == baseConfigMap.end())
246 {
247 std::cerr << "Name parameter not found for " << interfacePath
248 << "\n";
249 continue;
250 }
Patrick Williams779c96a2023-05-10 07:50:42 -0500251 sensorName = std::visit(VariantToStringVisitor(),
252 nameFound->second);
Ed Tanousbb679322022-05-16 16:10:00 -0700253 if (sensorName.empty())
254 {
255 std::cerr << "Name parameter not parsed for " << interfacePath
256 << "\n";
257 continue;
258 }
Josh Lehan72432172021-03-17 13:35:43 -0700259
Ed Tanousbb679322022-05-16 16:10:00 -0700260 auto unitsFound = baseConfigMap.find("Units");
261 if (unitsFound == baseConfigMap.end())
262 {
263 std::cerr << "Units parameter not found for " << interfacePath
264 << "\n";
265 continue;
266 }
Patrick Williams779c96a2023-05-10 07:50:42 -0500267 sensorUnits = std::visit(VariantToStringVisitor(),
268 unitsFound->second);
Ed Tanousbb679322022-05-16 16:10:00 -0700269 if (sensorUnits.empty())
270 {
271 std::cerr << "Units parameter not parsed for " << interfacePath
272 << "\n";
273 continue;
274 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700275
Ed Tanousbb679322022-05-16 16:10:00 -0700276 // on rescans, only update sensors we were signaled by
277 auto findSensor = sensors.find(sensorName);
278 if (!firstScan && (findSensor != sensors.end()))
279 {
280 std::string suffixName = "/";
281 suffixName += findSensor->second->name;
282 bool found = false;
283 for (auto it = sensorsChanged->begin();
284 it != sensorsChanged->end(); it++)
Josh Lehan2a40e932020-09-02 11:48:14 -0700285 {
Ed Tanousbb679322022-05-16 16:10:00 -0700286 std::string suffixIt = "/";
287 suffixIt += *it;
Zev Weiss6c106d62022-08-17 20:50:00 -0700288 if (suffixIt.ends_with(suffixName))
Josh Lehan2a40e932020-09-02 11:48:14 -0700289 {
Ed Tanousbb679322022-05-16 16:10:00 -0700290 sensorsChanged->erase(it);
291 findSensor->second = nullptr;
292 found = true;
293 if constexpr (debug)
Josh Lehan2a40e932020-09-02 11:48:14 -0700294 {
Ed Tanousbb679322022-05-16 16:10:00 -0700295 std::cerr << "ExternalSensor " << sensorName
296 << " change found\n";
Josh Lehan2a40e932020-09-02 11:48:14 -0700297 }
Ed Tanousbb679322022-05-16 16:10:00 -0700298 break;
Josh Lehan2a40e932020-09-02 11:48:14 -0700299 }
300 }
Ed Tanousbb679322022-05-16 16:10:00 -0700301 if (!found)
Josh Lehan2a40e932020-09-02 11:48:14 -0700302 {
Ed Tanousbb679322022-05-16 16:10:00 -0700303 continue;
Josh Lehan72432172021-03-17 13:35:43 -0700304 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700305 }
Ed Tanousbb679322022-05-16 16:10:00 -0700306
307 std::vector<thresholds::Threshold> sensorThresholds;
308 if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
309 {
310 std::cerr << "error populating thresholds for " << sensorName
311 << "\n";
312 }
313
Zev Weissa4d27682022-07-19 15:30:36 -0700314 PowerState readState = getPowerState(baseConfigMap);
Ed Tanousbb679322022-05-16 16:10:00 -0700315
316 auto& sensorEntry = sensors[sensorName];
317 sensorEntry = nullptr;
318
319 sensorEntry = std::make_shared<ExternalSensor>(
320 sensorType, objectServer, dbusConnection, sensorName,
321 sensorUnits, std::move(sensorThresholds), interfacePath,
322 maxValue, minValue, timeoutSecs, readState);
323 sensorEntry->initWriteHook(
324 [&sensors, &reaperTimer](
325 const std::chrono::steady_clock::time_point& now) {
326 updateReaper(sensors, reaperTimer, now);
327 });
328
329 if constexpr (debug)
330 {
331 std::cerr << "ExternalSensor " << sensorName << " created\n";
332 }
333 }
Patrick Williams597e8422023-10-20 11:19:01 -0500334 });
Josh Lehan2a40e932020-09-02 11:48:14 -0700335
336 getter->getConfiguration(std::vector<std::string>{sensorType});
337}
338
339int main()
340{
Josh Lehan72432172021-03-17 13:35:43 -0700341 if constexpr (debug)
342 {
343 std::cerr << "ExternalSensor service starting up\n";
344 }
345
Ed Tanous1f978632023-02-28 18:16:39 -0800346 boost::asio::io_context io;
Josh Lehan2a40e932020-09-02 11:48:14 -0700347 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
Ed Tanous14ed5e92022-07-12 15:50:23 -0700348 sdbusplus::asio::object_server objectServer(systemBus, true);
349
350 objectServer.add_manager("/xyz/openbmc_project/sensors");
Josh Lehan2a40e932020-09-02 11:48:14 -0700351 systemBus->request_name("xyz.openbmc_project.ExternalSensor");
Ed Tanous14ed5e92022-07-12 15:50:23 -0700352
Josh Lehan2a40e932020-09-02 11:48:14 -0700353 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>
354 sensors;
Josh Lehan2a40e932020-09-02 11:48:14 -0700355 auto sensorsChanged =
356 std::make_shared<boost::container::flat_set<std::string>>();
Josh Lehan72432172021-03-17 13:35:43 -0700357 boost::asio::steady_timer reaperTimer(io);
Josh Lehan2a40e932020-09-02 11:48:14 -0700358
Ed Tanous83db50c2023-03-01 10:20:24 -0800359 boost::asio::post(io,
360 [&objectServer, &sensors, &systemBus, &reaperTimer]() {
Ed Tanous8a17c302021-09-02 15:07:11 -0700361 createSensors(objectServer, sensors, systemBus, nullptr, reaperTimer);
Josh Lehan2a40e932020-09-02 11:48:14 -0700362 });
363
Ed Tanous9b4a20e2022-09-06 08:47:11 -0700364 boost::asio::steady_timer filterTimer(io);
Patrick Williams92f8f512022-07-22 19:26:55 -0500365 std::function<void(sdbusplus::message_t&)> eventHandler =
Ed Tanous8a17c302021-09-02 15:07:11 -0700366 [&objectServer, &sensors, &systemBus, &sensorsChanged, &filterTimer,
Patrick Williams92f8f512022-07-22 19:26:55 -0500367 &reaperTimer](sdbusplus::message_t& message) mutable {
Ed Tanousbb679322022-05-16 16:10:00 -0700368 if (message.is_method_error())
369 {
370 std::cerr << "callback method error\n";
371 return;
372 }
373
Ed Tanous2049bd22022-07-09 07:20:26 -0700374 const auto* messagePath = message.get_path();
Ed Tanousbb679322022-05-16 16:10:00 -0700375 sensorsChanged->insert(messagePath);
376 if constexpr (debug)
377 {
378 std::cerr << "ExternalSensor change event received: " << messagePath
379 << "\n";
380 }
381
382 // this implicitly cancels the timer
Ed Tanous83db50c2023-03-01 10:20:24 -0800383 filterTimer.expires_after(std::chrono::seconds(1));
Ed Tanousbb679322022-05-16 16:10:00 -0700384
385 filterTimer.async_wait(
386 [&objectServer, &sensors, &systemBus, &sensorsChanged,
387 &reaperTimer](const boost::system::error_code& ec) mutable {
388 if (ec != boost::system::errc::success)
Josh Lehan2a40e932020-09-02 11:48:14 -0700389 {
Ed Tanousbb679322022-05-16 16:10:00 -0700390 if (ec != boost::asio::error::operation_aborted)
391 {
392 std::cerr << "callback error: " << ec.message() << "\n";
393 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700394 return;
395 }
Josh Lehan03627382021-03-17 13:35:43 -0700396
Ed Tanousbb679322022-05-16 16:10:00 -0700397 createSensors(objectServer, sensors, systemBus, sensorsChanged,
398 reaperTimer);
399 });
400 };
Josh Lehan2a40e932020-09-02 11:48:14 -0700401
Zev Weiss214d9712022-08-12 12:54:31 -0700402 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
403 setupPropertiesChangedMatches(
404 *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
Josh Lehan2a40e932020-09-02 11:48:14 -0700405
Josh Lehan72432172021-03-17 13:35:43 -0700406 if constexpr (debug)
407 {
408 std::cerr << "ExternalSensor service entering main loop\n";
409 }
410
Josh Lehan2a40e932020-09-02 11:48:14 -0700411 io.run();
412}