1 #include "ExternalSensor.hpp"
2 #include "Thresholds.hpp"
3 #include "Utils.hpp"
4 #include "VariantVisitors.hpp"
5
6 #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>
10 #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>
15 #include <sdbusplus/message.hpp>
16 #include <sdbusplus/message/native_types.hpp>
17
18 #include <algorithm>
19 #include <array>
20 #include <chrono>
21 #include <cmath>
22 #include <functional>
23 #include <iostream>
24 #include <memory>
25 #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)
35 // can write to it, perhaps by using an IPMI or Redfish connection.
36
37 // Unlike most other sensors, an external sensor does not correspond
38 // to a hwmon file or any other kernel/hardware interface,
39 // so, after initialization, this module does not have much to do,
40 // but it handles reinitialization and thresholds, similar to the others.
41 // 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.
44
45 // As there is no corresponding driver or hardware to support,
46 // all configuration of this sensor comes from the JSON parameters:
47 // MinValue, MaxValue, Timeout, PowerState, Units, Name
48
49 // The purpose of "Units" is to specify the physical characteristic
50 // 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
52 // 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/
57
58 static constexpr bool debug = false;
59
60 static const char* sensorType = "ExternalSensor";
61
updateReaper(boost::container::flat_map<std::string,std::shared_ptr<ExternalSensor>> & sensors,boost::asio::steady_timer & timer,const std::chrono::steady_clock::time_point & now)62 void 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)
67 {
68 // First pass, reap all stale sensors
69 for (const auto& [name, sensor] : sensors)
70 {
71 if (!sensor)
72 {
73 continue;
74 }
75
76 if (!sensor->isAliveAndPerishable())
77 {
78 continue;
79 }
80
81 if (!sensor->isAliveAndFresh(now))
82 {
83 // Mark sensor as dead, no longer alive
84 sensor->writeInvalidate();
85 }
86 }
87
88 std::chrono::steady_clock::duration nextCheck;
89 bool needCheck = false;
90
91 // Second pass, determine timer interval to next check
92 for (const auto& [name, sensor] : sensors)
93 {
94 if (!sensor)
95 {
96 continue;
97 }
98
99 if (!sensor->isAliveAndPerishable())
100 {
101 continue;
102 }
103
104 auto expiration = sensor->ageRemaining(now);
105
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 {
122 std::cerr << "Next ExternalSensor timer idle\n";
123 }
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
134 if (err != boost::asio::error::operation_aborted)
135 {
136 std::cerr << "ExternalSensor timer scheduling problem: "
137 << err.message() << "\n";
138 }
139 return;
140 }
141
142 updateReaper(sensors, timer, std::chrono::steady_clock::now());
143 });
144
145 if constexpr (debug)
146 {
147 std::cerr << "Next ExternalSensor timer "
148 << std::chrono::duration_cast<std::chrono::microseconds>(
149 nextCheck)
150 .count()
151 << " us\n";
152 }
153 }
154
createSensors(sdbusplus::asio::object_server & objectServer,boost::container::flat_map<std::string,std::shared_ptr<ExternalSensor>> & sensors,std::shared_ptr<sdbusplus::asio::connection> & dbusConnection,const std::shared_ptr<boost::container::flat_set<std::string>> & sensorsChanged,boost::asio::steady_timer & reaperTimer)155 void createSensors(
156 sdbusplus::asio::object_server& objectServer,
157 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>>&
161 sensorsChanged,
162 boost::asio::steady_timer& reaperTimer)
163 {
164 if constexpr (debug)
165 {
166 std::cerr << "ExternalSensor considering creating sensors\n";
167 }
168
169 auto getter = std::make_shared<GetSensorConfiguration>(
170 dbusConnection,
171 [&objectServer, &sensors, &dbusConnection, sensorsChanged,
172 &reaperTimer](const ManagedObjectType& sensorConfigurations) {
173 bool firstScan = (sensorsChanged == nullptr);
174
175 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;
180
181 auto sensorBase =
182 sensorData.find(configInterfaceName(sensorType));
183 if (sensorBase == sensorData.end())
184 {
185 std::cerr << "Base configuration not found for "
186 << interfacePath << "\n";
187 continue;
188 }
189
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 {
198 std::cerr << "MinValue parameter not found for "
199 << interfacePath << "\n";
200 continue;
201 }
202 double minValue =
203 std::visit(VariantToDoubleVisitor(), minFound->second);
204 if (!std::isfinite(minValue))
205 {
206 std::cerr << "MinValue parameter not parsed for "
207 << interfacePath << "\n";
208 continue;
209 }
210
211 auto maxFound = baseConfigMap.find("MaxValue");
212 if (maxFound == baseConfigMap.end())
213 {
214 std::cerr << "MaxValue parameter not found for "
215 << interfacePath << "\n";
216 continue;
217 }
218 double maxValue =
219 std::visit(VariantToDoubleVisitor(), maxFound->second);
220 if (!std::isfinite(maxValue))
221 {
222 std::cerr << "MaxValue parameter not parsed for "
223 << interfacePath << "\n";
224 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 {
238 std::cerr << "Timeout parameter not parsed for "
239 << interfacePath << "\n";
240 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 {
250 std::cerr << "Name parameter not found for "
251 << interfacePath << "\n";
252 continue;
253 }
254 sensorName =
255 std::visit(VariantToStringVisitor(), nameFound->second);
256 if (sensorName.empty())
257 {
258 std::cerr << "Name parameter not parsed for "
259 << interfacePath << "\n";
260 continue;
261 }
262
263 auto unitsFound = baseConfigMap.find("Units");
264 if (unitsFound == baseConfigMap.end())
265 {
266 std::cerr << "Units parameter not found for "
267 << interfacePath << "\n";
268 continue;
269 }
270 sensorUnits =
271 std::visit(VariantToStringVisitor(), unitsFound->second);
272 if (sensorUnits.empty())
273 {
274 std::cerr << "Units parameter not parsed for "
275 << interfacePath << "\n";
276 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 {
298 std::cerr << "ExternalSensor " << sensorName
299 << " change found\n";
300 }
301 break;
302 }
303 }
304 if (!found)
305 {
306 continue;
307 }
308 }
309
310 std::vector<thresholds::Threshold> sensorThresholds;
311 if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
312 {
313 std::cerr << "error populating thresholds for "
314 << sensorName << "\n";
315 }
316
317 PowerState readState = getPowerState(baseConfigMap);
318
319 auto& sensorEntry = sensors[sensorName];
320 sensorEntry = nullptr;
321
322 sensorEntry = std::make_shared<ExternalSensor>(
323 sensorType, objectServer, dbusConnection, sensorName,
324 sensorUnits, std::move(sensorThresholds), interfacePath,
325 maxValue, minValue, timeoutSecs, readState);
326 sensorEntry->initWriteHook(
327 [&sensors, &reaperTimer](
328 const std::chrono::steady_clock::time_point& now) {
329 updateReaper(sensors, reaperTimer, now);
330 });
331
332 if constexpr (debug)
333 {
334 std::cerr
335 << "ExternalSensor " << sensorName << " created\n";
336 }
337 }
338 });
339
340 getter->getConfiguration(std::vector<std::string>{sensorType});
341 }
342
main()343 int main()
344 {
345 if constexpr (debug)
346 {
347 std::cerr << "ExternalSensor service starting up\n";
348 }
349
350 boost::asio::io_context io;
351 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
352 sdbusplus::asio::object_server objectServer(systemBus, true);
353
354 objectServer.add_manager("/xyz/openbmc_project/sensors");
355 systemBus->request_name("xyz.openbmc_project.ExternalSensor");
356
357 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>
358 sensors;
359 auto sensorsChanged =
360 std::make_shared<boost::container::flat_set<std::string>>();
361 boost::asio::steady_timer reaperTimer(io);
362
363 boost::asio::post(io, [&objectServer, &sensors, &systemBus,
364 &reaperTimer]() {
365 createSensors(objectServer, sensors, systemBus, nullptr, reaperTimer);
366 });
367
368 boost::asio::steady_timer filterTimer(io);
369 std::function<void(sdbusplus::message_t&)> eventHandler =
370 [&objectServer, &sensors, &systemBus, &sensorsChanged, &filterTimer,
371 &reaperTimer](sdbusplus::message_t& message) mutable {
372 if (message.is_method_error())
373 {
374 std::cerr << "callback method error\n";
375 return;
376 }
377
378 const auto* messagePath = message.get_path();
379 sensorsChanged->insert(messagePath);
380 if constexpr (debug)
381 {
382 std::cerr << "ExternalSensor change event received: "
383 << messagePath << "\n";
384 }
385
386 // this implicitly cancels the timer
387 filterTimer.expires_after(std::chrono::seconds(1));
388
389 filterTimer.async_wait(
390 [&objectServer, &sensors, &systemBus, &sensorsChanged,
391 &reaperTimer](const boost::system::error_code& ec) mutable {
392 if (ec != boost::system::errc::success)
393 {
394 if (ec != boost::asio::error::operation_aborted)
395 {
396 std::cerr
397 << "callback error: " << ec.message() << "\n";
398 }
399 return;
400 }
401
402 createSensors(objectServer, sensors, systemBus,
403 sensorsChanged, reaperTimer);
404 });
405 };
406
407 std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
408 setupPropertiesChangedMatches(
409 *systemBus, std::to_array<const char*>({sensorType}), eventHandler);
410
411 if constexpr (debug)
412 {
413 std::cerr << "ExternalSensor service entering main loop\n";
414 }
415
416 io.run();
417 }
418