xref: /openbmc/phosphor-hwmon/sensor.cpp (revision 47fb49ac)
1 #include "config.h"
2 
3 #include "sensor.hpp"
4 
5 #include "env.hpp"
6 #include "gpio_handle.hpp"
7 #include "hwmon.hpp"
8 #include "sensorset.hpp"
9 #include "sysfs.hpp"
10 
11 #include <fmt/format.h>
12 
13 #include <phosphor-logging/elog-errors.hpp>
14 #include <xyz/openbmc_project/Common/error.hpp>
15 #include <xyz/openbmc_project/Sensor/Device/error.hpp>
16 
17 #include <cassert>
18 #include <chrono>
19 #include <cmath>
20 #include <cstring>
21 #include <filesystem>
22 #include <future>
23 #include <thread>
24 
25 namespace sensor
26 {
27 
28 using namespace phosphor::logging;
29 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
30 
31 // todo: this can be simplified once we move to the double interface
32 Sensor::Sensor(const SensorSet::key_type& sensor,
33                const hwmonio::HwmonIOInterface* ioAccess,
34                const std::string& devPath) :
35     _sensor(sensor),
36     _ioAccess(ioAccess), _devPath(devPath), _scale(0), _hasFaultFile(false)
37 {
38     auto chip = env::getEnv("GPIOCHIP", sensor);
39     auto access = env::getEnv("GPIO", sensor);
40     if (!access.empty() && !chip.empty())
41     {
42         _handle = gpio::BuildGpioHandle(chip, access);
43 
44         if (!_handle)
45         {
46             log<level::ERR>("Unable to set up gpio locking");
47             elog<InternalFailure>();
48         }
49     }
50 
51     auto gain = env::getEnv("GAIN", sensor);
52     if (!gain.empty())
53     {
54         _sensorAdjusts.gain = std::stod(gain);
55     }
56 
57     auto offset = env::getEnv("OFFSET", sensor);
58     if (!offset.empty())
59     {
60         _sensorAdjusts.offset = std::stoi(offset);
61     }
62     auto senRmRCs = env::getEnv("REMOVERCS", sensor);
63     // Add sensor removal return codes defined per sensor
64     addRemoveRCs(senRmRCs);
65 }
66 
67 void Sensor::addRemoveRCs(const std::string& rcList)
68 {
69     if (rcList.empty())
70     {
71         return;
72     }
73 
74     // Convert to a char* for strtok
75     std::vector<char> rmRCs(rcList.c_str(), rcList.c_str() + rcList.size() + 1);
76     auto rmRC = std::strtok(&rmRCs[0], ", ");
77     while (rmRC != nullptr)
78     {
79         try
80         {
81             _sensorAdjusts.rmRCs.insert(std::stoi(rmRC));
82         }
83         catch (const std::logic_error& le)
84         {
85             // Unable to convert to int, continue to next token
86             std::string name = _sensor.first + "_" + _sensor.second;
87             log<level::INFO>("Unable to convert sensor removal return code",
88                              entry("SENSOR=%s", name.c_str()),
89                              entry("RC=%s", rmRC),
90                              entry("EXCEPTION=%s", le.what()));
91         }
92         rmRC = std::strtok(nullptr, ", ");
93     }
94 }
95 
96 SensorValueType Sensor::adjustValue(SensorValueType value)
97 {
98 // Because read doesn't have an out pointer to store errors.
99 // let's assume negative values are errors if they have this
100 // set.
101 #if NEGATIVE_ERRNO_ON_FAIL
102     if (value < 0)
103     {
104         return value;
105     }
106 #endif
107 
108     // Adjust based on gain and offset
109     value = static_cast<decltype(value)>(static_cast<double>(value) *
110                                              _sensorAdjusts.gain +
111                                          _sensorAdjusts.offset);
112 
113     if constexpr (std::is_same<SensorValueType, double>::value)
114     {
115         value *= std::pow(10, _scale);
116     }
117 
118     return value;
119 }
120 
121 std::shared_ptr<ValueObject> Sensor::addValue(const RetryIO& retryIO,
122                                               ObjectInfo& info,
123                                               TimedoutMap& timedoutMap)
124 {
125     // Get the initial value for the value interface.
126     auto& bus = *std::get<sdbusplus::bus_t*>(info);
127     auto& obj = std::get<InterfaceMap>(info);
128     auto& objPath = std::get<std::string>(info);
129 
130     SensorValueType val = 0;
131 
132     auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>(
133         obj[InterfaceType::STATUS]);
134     // As long as addStatus is called before addValue, statusIface
135     // should never be nullptr
136     assert(statusIface);
137 
138     // Only read the input value if the status is functional
139     if (statusIface->functional())
140     {
141 #if UPDATE_FUNCTIONAL_ON_FAIL
142         try
143 #endif
144         {
145             // RAII object for GPIO unlock / lock
146             auto locker = gpioUnlock(getGpio());
147 
148             // For sensors with attribute ASYNC_READ_TIMEOUT,
149             // spawn a thread with timeout
150             auto asyncReadTimeout = env::getEnv("ASYNC_READ_TIMEOUT", _sensor);
151             if (!asyncReadTimeout.empty())
152             {
153                 std::chrono::milliseconds asyncTimeout{
154                     std::stoi(asyncReadTimeout)};
155                 val = asyncRead(_sensor, _ioAccess, asyncTimeout, timedoutMap,
156                                 _sensor.first, _sensor.second,
157                                 hwmon::entry::cinput, std::get<size_t>(retryIO),
158                                 std::get<std::chrono::milliseconds>(retryIO));
159             }
160             else
161             {
162                 // Retry for up to a second if device is busy
163                 // or has a transient error.
164                 val = _ioAccess->read(
165                     _sensor.first, _sensor.second, hwmon::entry::cinput,
166                     std::get<size_t>(retryIO),
167                     std::get<std::chrono::milliseconds>(retryIO));
168             }
169         }
170 #if UPDATE_FUNCTIONAL_ON_FAIL
171         catch (const std::system_error& e)
172         {
173             // Catch the exception here and update the functional property.
174             // By catching the exception, it will not propagate it up the stack
175             // and thus the code will skip the "Remove RCs" check in
176             // MainLoop::getObject and will not exit on failure.
177             statusIface->functional(false);
178         }
179 #endif
180     }
181 
182     auto iface = std::make_shared<ValueObject>(bus, objPath.c_str(),
183                                                ValueObject::action::defer_emit);
184 
185     hwmon::Attributes attrs;
186     if (hwmon::getAttributes(_sensor.first, attrs))
187     {
188         iface->unit(hwmon::getUnit(attrs));
189 
190         _scale = hwmon::getScale(attrs);
191     }
192 
193     val = adjustValue(val);
194     iface->value(val);
195 
196     auto maxValue = env::getEnv("MAXVALUE", _sensor);
197     if (!maxValue.empty())
198     {
199         iface->maxValue(std::stoll(maxValue));
200     }
201     auto minValue = env::getEnv("MINVALUE", _sensor);
202     if (!minValue.empty())
203     {
204         iface->minValue(std::stoll(minValue));
205     }
206 
207     obj[InterfaceType::VALUE] = iface;
208     return iface;
209 }
210 
211 std::shared_ptr<StatusObject> Sensor::addStatus(ObjectInfo& info)
212 {
213     namespace fs = std::filesystem;
214 
215     std::shared_ptr<StatusObject> iface = nullptr;
216     auto& objPath = std::get<std::string>(info);
217     auto& obj = std::get<InterfaceMap>(info);
218 
219     // Check if fault sysfs file exists
220     std::string faultName = _sensor.first;
221     std::string faultID = _sensor.second;
222     std::string entry = hwmon::entry::fault;
223 
224     bool functional = true;
225     auto sysfsFullPath = sysfs::make_sysfs_path(_ioAccess->path(), faultName,
226                                                 faultID, entry);
227     if (fs::exists(sysfsFullPath))
228     {
229         _hasFaultFile = true;
230         try
231         {
232             uint32_t fault = _ioAccess->read(faultName, faultID, entry,
233                                              hwmonio::retries, hwmonio::delay);
234             if (fault != 0)
235             {
236                 functional = false;
237             }
238         }
239         catch (const std::system_error& e)
240         {
241             using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::
242                 Error;
243             using metadata = xyz::openbmc_project::Sensor::Device::ReadFailure;
244 
245             report<ReadFailure>(
246                 metadata::CALLOUT_ERRNO(e.code().value()),
247                 metadata::CALLOUT_DEVICE_PATH(_devPath.c_str()));
248 
249             log<level::INFO>(fmt::format("Failing sysfs file: {} errno {}",
250                                          sysfsFullPath, e.code().value())
251                                  .c_str());
252         }
253     }
254 
255     auto& bus = *std::get<sdbusplus::bus_t*>(info);
256 
257     iface = std::make_shared<StatusObject>(
258         bus, objPath.c_str(), StatusObject::action::emit_no_signals);
259     // Set functional property
260     iface->functional(functional);
261 
262     obj[InterfaceType::STATUS] = iface;
263 
264     return iface;
265 }
266 
267 std::shared_ptr<AccuracyObject> Sensor::addAccuracy(ObjectInfo& info,
268                                                     double accuracy)
269 {
270     auto& objPath = std::get<std::string>(info);
271     auto& obj = std::get<InterfaceMap>(info);
272 
273     auto& bus = *std::get<sdbusplus::bus_t*>(info);
274     auto iface = std::make_shared<AccuracyObject>(
275         bus, objPath.c_str(), AccuracyObject::action::emit_no_signals);
276 
277     iface->accuracy(accuracy);
278     obj[InterfaceType::ACCURACY] = iface;
279 
280     return iface;
281 }
282 
283 std::shared_ptr<PriorityObject> Sensor::addPriority(ObjectInfo& info,
284                                                     size_t priority)
285 {
286     auto& objPath = std::get<std::string>(info);
287     auto& obj = std::get<InterfaceMap>(info);
288 
289     auto& bus = *std::get<sdbusplus::bus_t*>(info);
290     auto iface = std::make_shared<PriorityObject>(
291         bus, objPath.c_str(), PriorityObject::action::emit_no_signals);
292 
293     iface->priority(priority);
294     obj[InterfaceType::PRIORITY] = iface;
295 
296     return iface;
297 }
298 
299 void gpioLock(const gpioplus::HandleInterface*&& handle)
300 {
301     handle->setValues({0});
302 }
303 
304 std::optional<GpioLocker> gpioUnlock(const gpioplus::HandleInterface* handle)
305 {
306     if (handle == nullptr)
307     {
308         return std::nullopt;
309     }
310 
311     handle->setValues({1});
312     // Default pause needed to guarantee sensors are ready
313     std::this_thread::sleep_for(std::chrono::milliseconds(500));
314     return GpioLocker(std::move(handle));
315 }
316 
317 SensorValueType asyncRead(const SensorSet::key_type& sensorSetKey,
318                           const hwmonio::HwmonIOInterface* ioAccess,
319                           std::chrono::milliseconds asyncTimeout,
320                           TimedoutMap& timedoutMap, const std::string& type,
321                           const std::string& id, const std::string& sensor,
322                           const size_t retries,
323                           const std::chrono::milliseconds delay)
324 {
325     // Default async read timeout
326     bool valueIsValid = false;
327     std::future<int64_t> asyncThread;
328 
329     auto asyncIter = timedoutMap.find(sensorSetKey);
330     if (asyncIter == timedoutMap.end())
331     {
332         // If sensor not found in timedoutMap, spawn an async thread
333         asyncThread = std::async(std::launch::async,
334                                  &hwmonio::HwmonIOInterface::read, ioAccess,
335                                  type, id, sensor, retries, delay);
336         valueIsValid = true;
337     }
338     else
339     {
340         // If we already have the async thread in the timedoutMap, it means this
341         // sensor has already timed out in the previous reads. No need to wait
342         // on subsequent reads - proceed to check the future_status to see when
343         // the async thread finishes
344         asyncTimeout = std::chrono::seconds(0);
345         asyncThread = std::move(asyncIter->second);
346     }
347 
348     // TODO: This is still not a true asynchronous read as it still blocks the
349     // main thread for asyncTimeout amount of time. To make this completely
350     // asynchronous, schedule a read and register a callback to update the
351     // sensor value
352     std::future_status status = asyncThread.wait_for(asyncTimeout);
353     switch (status)
354     {
355         case std::future_status::ready:
356             // Read has finished
357             if (valueIsValid)
358             {
359                 return asyncThread.get();
360                 // Good sensor reads should skip the code below
361             }
362             // Async read thread has completed but had previously timed out (was
363             // found in the timedoutMap). Erase from timedoutMap and throw to
364             // allow retry in the next read cycle. Not returning the read value
365             // as the sensor reading may be bad / corrupted if it took so long.
366             timedoutMap.erase(sensorSetKey);
367             throw AsyncSensorReadTimeOut();
368         default:
369             // Read timed out so add the thread to the timedoutMap (if the entry
370             // already exists, operator[] updates it).
371             //
372             // Keeping the timed out futures in a map is required to prevent
373             // their destructor from being called when returning from this
374             // stack. The destructor will otherwise block until the read
375             // completes due to the limitation of std::async.
376             timedoutMap[sensorSetKey] = std::move(asyncThread);
377             throw AsyncSensorReadTimeOut();
378     }
379 }
380 
381 } // namespace sensor
382