blob: 0712b00dbf8016eeb190d6f61080e076900db198 [file] [log] [blame]
Josh Lehan2a40e932020-09-02 11:48:14 -07001#include "ExternalSensor.hpp"
2#include "Utils.hpp"
3#include "VariantVisitors.hpp"
4
5#include <boost/algorithm/string/predicate.hpp>
6#include <boost/algorithm/string/replace.hpp>
7#include <boost/container/flat_map.hpp>
8#include <boost/container/flat_set.hpp>
9#include <sdbusplus/asio/connection.hpp>
10#include <sdbusplus/asio/object_server.hpp>
11#include <sdbusplus/bus/match.hpp>
12
13#include <array>
14#include <filesystem>
15#include <fstream>
16#include <functional>
17#include <memory>
18#include <regex>
19#include <stdexcept>
20#include <string>
21#include <utility>
22#include <variant>
23#include <vector>
24
25// Copied from HwmonTempSensor and inspired by
26// https://gerrit.openbmc-project.xyz/c/openbmc/dbus-sensors/+/35476
27
28// The ExternalSensor is a sensor whose value is intended to be writable
29// by something external to the BMC, so that the host (or something else)
Josh Lehan72432172021-03-17 13:35:43 -070030// can write to it, perhaps by using an IPMI or Redfish connection.
Josh Lehan2a40e932020-09-02 11:48:14 -070031
32// Unlike most other sensors, an external sensor does not correspond
Josh Lehan72432172021-03-17 13:35:43 -070033// to a hwmon file or any other kernel/hardware interface,
Josh Lehan2a40e932020-09-02 11:48:14 -070034// so, after initialization, this module does not have much to do,
35// but it handles reinitialization and thresholds, similar to the others.
Josh Lehan72432172021-03-17 13:35:43 -070036// The main work of this module is to provide backing storage for a
37// sensor that exists only virtually, and to provide an optional
38// timeout service for detecting loss of timely updates.
Josh Lehan2a40e932020-09-02 11:48:14 -070039
40// As there is no corresponding driver or hardware to support,
41// all configuration of this sensor comes from the JSON parameters:
Josh Lehan72432172021-03-17 13:35:43 -070042// MinValue, MaxValue, Timeout, PowerState, Units, Name
Josh Lehan2a40e932020-09-02 11:48:14 -070043
Josh Lehan72432172021-03-17 13:35:43 -070044// The purpose of "Units" is to specify the physical characteristic
Josh Lehan2a40e932020-09-02 11:48:14 -070045// the external sensor is measuring, because with an external sensor
46// there is no other way to tell, and it will be used for the object path
Josh Lehan72432172021-03-17 13:35:43 -070047// here: /xyz/openbmc_project/sensors/<Units>/<Name>
48
49// For more information, see external-sensor.md design document:
50// https://gerrit.openbmc-project.xyz/c/openbmc/docs/+/41452
51// https://github.com/openbmc/docs/tree/master/designs/
Josh Lehan2a40e932020-09-02 11:48:14 -070052
Ed Tanous8a57ec02020-10-09 12:46:52 -070053static constexpr bool debug = false;
Josh Lehan2a40e932020-09-02 11:48:14 -070054
55static const char* sensorType =
56 "xyz.openbmc_project.Configuration.ExternalSensor";
57
Josh Lehan72432172021-03-17 13:35:43 -070058void updateReaper(boost::container::flat_map<
59 std::string, std::shared_ptr<ExternalSensor>>& sensors,
60 boost::asio::steady_timer& timer,
61 const std::chrono::steady_clock::time_point& now)
62{
63 // First pass, reap all stale sensors
64 for (auto& sensor : sensors)
65 {
66 if (!sensor.second)
67 {
68 continue;
69 }
70
71 if (!sensor.second->isAliveAndPerishable())
72 {
73 continue;
74 }
75
76 if (!sensor.second->isAliveAndFresh(now))
77 {
78 // Mark sensor as dead, no longer alive
79 sensor.second->writeInvalidate();
80 }
81 }
82
83 std::chrono::steady_clock::duration nextCheck;
84 bool needCheck = false;
85
86 // Second pass, determine timer interval to next check
87 for (auto& sensor : sensors)
88 {
89 if (!sensor.second)
90 {
91 continue;
92 }
93
94 if (!sensor.second->isAliveAndPerishable())
95 {
96 continue;
97 }
98
99 auto expiration = sensor.second->ageRemaining(now);
100
101 if (needCheck)
102 {
103 nextCheck = std::min(nextCheck, expiration);
104 }
105 else
106 {
107 // Initialization
108 nextCheck = expiration;
109 needCheck = true;
110 }
111 }
112
113 if (!needCheck)
114 {
115 if constexpr (debug)
116 {
117 std::cerr << "Next ExternalSensor timer idle\n";
118 }
119
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
129 if (err != boost::system::errc::operation_canceled)
130 {
131 std::cerr << "ExternalSensor timer scheduling problem: "
132 << err.message() << "\n";
133 }
134 return;
135 }
136 updateReaper(sensors, timer, std::chrono::steady_clock::now());
137 });
138
139 if constexpr (debug)
140 {
141 std::cerr << "Next ExternalSensor timer "
142 << std::chrono::duration_cast<std::chrono::microseconds>(
143 nextCheck)
144 .count()
145 << " us\n";
146 }
147}
148
Josh Lehan2a40e932020-09-02 11:48:14 -0700149void createSensors(
150 boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
151 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>&
152 sensors,
153 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
154 const std::shared_ptr<boost::container::flat_set<std::string>>&
Josh Lehan72432172021-03-17 13:35:43 -0700155 sensorsChanged,
156 boost::asio::steady_timer& reaperTimer)
Josh Lehan2a40e932020-09-02 11:48:14 -0700157{
158 auto getter = std::make_shared<GetSensorConfiguration>(
159 dbusConnection,
Josh Lehan72432172021-03-17 13:35:43 -0700160 [&io, &objectServer, &sensors, &dbusConnection, sensorsChanged,
161 &reaperTimer](const ManagedObjectType& sensorConfigurations) {
Josh Lehan2a40e932020-09-02 11:48:14 -0700162 bool firstScan = (sensorsChanged == nullptr);
163
164 for (const std::pair<sdbusplus::message::object_path, SensorData>&
165 sensor : sensorConfigurations)
166 {
167 const std::string& interfacePath = sensor.first.str;
168 const SensorData& sensorData = sensor.second;
169
170 auto sensorBase = sensorData.find(sensorType);
171 if (sensorBase == sensorData.end())
172 {
173 std::cerr << "Base configuration not found for "
174 << interfacePath << "\n";
175 continue;
176 }
177
178 const SensorBaseConfiguration& baseConfiguration = *sensorBase;
179 const SensorBaseConfigMap& baseConfigMap =
180 baseConfiguration.second;
181
182 double minValue;
183 double maxValue;
184
185 // 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 }
193 minValue =
194 std::visit(VariantToDoubleVisitor(), minFound->second);
195 if (!std::isfinite(minValue))
196 {
197 std::cerr << "MinValue parameter not parsed for "
198 << interfacePath << "\n";
199 continue;
200 }
201
202 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 }
209 maxValue =
210 std::visit(VariantToDoubleVisitor(), maxFound->second);
211 if (!std::isfinite(maxValue))
212 {
213 std::cerr << "MaxValue parameter not parsed for "
214 << interfacePath << "\n";
215 continue;
216 }
217
Josh Lehan72432172021-03-17 13:35:43 -0700218 double timeoutSecs = 0.0;
Josh Lehan2a40e932020-09-02 11:48:14 -0700219
Josh Lehan72432172021-03-17 13:35:43 -0700220 // Timeout is an optional numeric parameter
221 auto timeoutFound = baseConfigMap.find("Timeout");
222 if (timeoutFound != baseConfigMap.end())
223 {
224 timeoutSecs = std::visit(VariantToDoubleVisitor(),
225 timeoutFound->second);
226 }
227 if (!(std::isfinite(timeoutSecs) && (timeoutSecs >= 0.0)))
228 {
229 std::cerr << "Timeout parameter not parsed for "
230 << interfacePath << "\n";
231 continue;
232 }
233
234 std::string sensorName;
235 std::string sensorUnits;
236
237 // Name and Units are mandatory string parameters
Josh Lehan2a40e932020-09-02 11:48:14 -0700238 auto nameFound = baseConfigMap.find("Name");
239 if (nameFound == baseConfigMap.end())
240 {
241 std::cerr << "Name parameter not found for "
242 << interfacePath << "\n";
243 continue;
244 }
245 sensorName =
246 std::visit(VariantToStringVisitor(), nameFound->second);
247 if (sensorName.empty())
248 {
249 std::cerr << "Name parameter not parsed for "
250 << interfacePath << "\n";
251 continue;
252 }
253
Josh Lehan72432172021-03-17 13:35:43 -0700254 auto unitsFound = baseConfigMap.find("Units");
255 if (unitsFound == baseConfigMap.end())
Josh Lehan2a40e932020-09-02 11:48:14 -0700256 {
257 std::cerr << "Units parameter not found for "
258 << interfacePath << "\n";
259 continue;
260 }
Josh Lehan72432172021-03-17 13:35:43 -0700261 sensorUnits =
262 std::visit(VariantToStringVisitor(), unitsFound->second);
263 if (sensorUnits.empty())
Josh Lehan2a40e932020-09-02 11:48:14 -0700264 {
Josh Lehan72432172021-03-17 13:35:43 -0700265 std::cerr << "Units parameter not parsed for "
Josh Lehan2a40e932020-09-02 11:48:14 -0700266 << interfacePath << "\n";
267 continue;
268 }
269
270 // 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++)
279 {
280 std::string suffixIt = "/";
281 suffixIt += *it;
282 if (boost::ends_with(suffixIt, suffixName))
283 {
284 sensorsChanged->erase(it);
285 findSensor->second = nullptr;
286 found = true;
287 break;
288 }
289 }
290 if (!found)
291 {
292 continue;
293 }
294 }
295
296 std::vector<thresholds::Threshold> sensorThresholds;
297 if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
298 {
299 std::cerr << "error populating thresholds for "
300 << sensorName << "\n";
301 }
302
303 auto findPowerOn = baseConfiguration.second.find("PowerState");
304 PowerState readState = PowerState::always;
305 if (findPowerOn != baseConfiguration.second.end())
306 {
307 std::string powerState = std::visit(
308 VariantToStringVisitor(), findPowerOn->second);
309 setReadState(powerState, readState);
310 }
311
312 auto& sensorEntry = sensors[sensorName];
313 sensorEntry = nullptr;
314
315 sensorEntry = std::make_shared<ExternalSensor>(
316 sensorType, objectServer, dbusConnection, sensorName,
Josh Lehan72432172021-03-17 13:35:43 -0700317 sensorUnits, std::move(sensorThresholds), interfacePath,
318 maxValue, minValue, timeoutSecs, readState,
319 [&sensors, &reaperTimer](
320 const std::chrono::steady_clock::time_point& now) {
321 updateReaper(sensors, reaperTimer, now);
322 });
323
324 if constexpr (debug)
325 {
326 std::cerr << "ExternalSensor " << sensorName
327 << " created\n";
328 }
Josh Lehan2a40e932020-09-02 11:48:14 -0700329 }
330 });
331
332 getter->getConfiguration(std::vector<std::string>{sensorType});
333}
334
335int main()
336{
Josh Lehan72432172021-03-17 13:35:43 -0700337 if constexpr (debug)
338 {
339 std::cerr << "ExternalSensor service starting up\n";
340 }
341
Josh Lehan2a40e932020-09-02 11:48:14 -0700342 boost::asio::io_service io;
343 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
344 systemBus->request_name("xyz.openbmc_project.ExternalSensor");
345 sdbusplus::asio::object_server objectServer(systemBus);
346 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>
347 sensors;
348 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
349 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
Josh Lehan72432172021-03-17 13:35:43 -0700353 io.post([&io, &objectServer, &sensors, &systemBus, &reaperTimer]() {
354 createSensors(io, objectServer, sensors, systemBus, nullptr,
355 reaperTimer);
Josh Lehan2a40e932020-09-02 11:48:14 -0700356 });
357
358 boost::asio::deadline_timer filterTimer(io);
359 std::function<void(sdbusplus::message::message&)> eventHandler =
360 [&io, &objectServer, &sensors, &systemBus, &sensorsChanged,
Josh Lehan72432172021-03-17 13:35:43 -0700361 &filterTimer, &reaperTimer](sdbusplus::message::message& message) {
Josh Lehan2a40e932020-09-02 11:48:14 -0700362 if (message.is_method_error())
363 {
364 std::cerr << "callback method error\n";
365 return;
366 }
367 sensorsChanged->insert(message.get_path());
368 // this implicitly cancels the timer
369 filterTimer.expires_from_now(boost::posix_time::seconds(1));
370
371 filterTimer.async_wait([&io, &objectServer, &sensors, &systemBus,
Josh Lehan72432172021-03-17 13:35:43 -0700372 &sensorsChanged, &reaperTimer](
Josh Lehan2a40e932020-09-02 11:48:14 -0700373 const boost::system::error_code& ec) {
374 if (ec)
375 {
376 if (ec != boost::asio::error::operation_aborted)
377 {
378 std::cerr << "callback error: " << ec.message() << "\n";
379 }
380 return;
381 }
382 createSensors(io, objectServer, sensors, systemBus,
Josh Lehan72432172021-03-17 13:35:43 -0700383 sensorsChanged, reaperTimer);
Josh Lehan2a40e932020-09-02 11:48:14 -0700384 });
385 };
386
387 auto match = std::make_unique<sdbusplus::bus::match::match>(
388 static_cast<sdbusplus::bus::bus&>(*systemBus),
389 "type='signal',member='PropertiesChanged',path_namespace='" +
390 std::string(inventoryPath) + "',arg0namespace='" + sensorType + "'",
391 eventHandler);
392 matches.emplace_back(std::move(match));
393
Josh Lehan72432172021-03-17 13:35:43 -0700394 if constexpr (debug)
395 {
396 std::cerr << "ExternalSensor service entering main loop\n";
397 }
398
Josh Lehan2a40e932020-09-02 11:48:14 -0700399 io.run();
400}