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