xref: /openbmc/phosphor-hwmon/sensor.cpp (revision 02e598ab)
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), _ioAccess(ioAccess), _devPath(devPath), _scale(0),
35     _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)>(
109         static_cast<double>(value) * _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(
121     const RetryIO& retryIO, ObjectInfo& info, TimedoutMap& timedoutMap)
122 {
123     // Get the initial value for the value interface.
124     auto& bus = *std::get<sdbusplus::bus_t*>(info);
125     auto& obj = std::get<InterfaceMap>(info);
126     auto& objPath = std::get<std::string>(info);
127 
128     SensorValueType val = 0;
129 
130     auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>(
131         obj[InterfaceType::STATUS]);
132     // As long as addStatus is called before addValue, statusIface
133     // should never be nullptr
134     assert(statusIface);
135 
136     // Only read the input value if the status is functional
137     if (statusIface->functional())
138     {
139 #if UPDATE_FUNCTIONAL_ON_FAIL
140         try
141 #endif
142         {
143             // RAII object for GPIO unlock / lock
144             auto locker = gpioUnlock(getGpio());
145 
146             // For sensors with attribute ASYNC_READ_TIMEOUT,
147             // spawn a thread with timeout
148             auto asyncReadTimeout = env::getEnv("ASYNC_READ_TIMEOUT", _sensor);
149             if (!asyncReadTimeout.empty())
150             {
151                 std::chrono::milliseconds asyncTimeout{
152                     std::stoi(asyncReadTimeout)};
153                 val = asyncRead(_sensor, _ioAccess, asyncTimeout, timedoutMap,
154                                 _sensor.first, _sensor.second,
155                                 hwmon::entry::cinput, std::get<size_t>(retryIO),
156                                 std::get<std::chrono::milliseconds>(retryIO));
157             }
158             else
159             {
160                 // Retry for up to a second if device is busy
161                 // or has a transient error.
162                 val = _ioAccess->read(
163                     _sensor.first, _sensor.second, hwmon::entry::cinput,
164                     std::get<size_t>(retryIO),
165                     std::get<std::chrono::milliseconds>(retryIO));
166             }
167         }
168 #if UPDATE_FUNCTIONAL_ON_FAIL
169         catch (const std::system_error& e)
170         {
171             // Catch the exception here and update the functional property.
172             // By catching the exception, it will not propagate it up the stack
173             // and thus the code will skip the "Remove RCs" check in
174             // MainLoop::getObject and will not exit on failure.
175             statusIface->functional(false);
176         }
177 #endif
178     }
179 
180     auto iface = std::make_shared<ValueObject>(bus, objPath.c_str(),
181                                                ValueObject::action::defer_emit);
182 
183     hwmon::Attributes attrs;
184     if (hwmon::getAttributes(_sensor.first, attrs))
185     {
186         iface->unit(hwmon::getUnit(attrs));
187 
188         _scale = hwmon::getScale(attrs);
189     }
190 
191     val = adjustValue(val);
192     iface->value(val);
193 
194     auto maxValue = env::getEnv("MAXVALUE", _sensor);
195     if (!maxValue.empty())
196     {
197         iface->maxValue(std::stoll(maxValue));
198     }
199     auto minValue = env::getEnv("MINVALUE", _sensor);
200     if (!minValue.empty())
201     {
202         iface->minValue(std::stoll(minValue));
203     }
204 
205     obj[InterfaceType::VALUE] = iface;
206     return iface;
207 }
208 
addStatus(ObjectInfo & info)209 std::shared_ptr<StatusObject> Sensor::addStatus(ObjectInfo& info)
210 {
211     namespace fs = std::filesystem;
212 
213     std::shared_ptr<StatusObject> iface = nullptr;
214     auto& objPath = std::get<std::string>(info);
215     auto& obj = std::get<InterfaceMap>(info);
216 
217     // Check if fault sysfs file exists
218     std::string faultName = _sensor.first;
219     std::string faultID = _sensor.second;
220     std::string entry = hwmon::entry::fault;
221 
222     bool functional = true;
223     auto sysfsFullPath =
224         sysfs::make_sysfs_path(_ioAccess->path(), faultName, faultID, entry);
225     if (fs::exists(sysfsFullPath))
226     {
227         _hasFaultFile = true;
228         try
229         {
230             uint32_t fault = _ioAccess->read(faultName, faultID, entry,
231                                              hwmonio::retries, hwmonio::delay);
232             if (fault != 0)
233             {
234                 functional = false;
235             }
236         }
237         catch (const std::system_error& e)
238         {
239             using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::
240                 Error;
241             using metadata = xyz::openbmc_project::Sensor::Device::ReadFailure;
242 
243             report<ReadFailure>(
244                 metadata::CALLOUT_ERRNO(e.code().value()),
245                 metadata::CALLOUT_DEVICE_PATH(_devPath.c_str()));
246 
247             log<level::INFO>(std::format("Failing sysfs file: {} errno {}",
248                                          sysfsFullPath, e.code().value())
249                                  .c_str());
250         }
251     }
252 
253     auto& bus = *std::get<sdbusplus::bus_t*>(info);
254 
255     iface = std::make_shared<StatusObject>(
256         bus, objPath.c_str(), StatusObject::action::emit_no_signals);
257     // Set functional property
258     iface->functional(functional);
259 
260     obj[InterfaceType::STATUS] = iface;
261 
262     return iface;
263 }
264 
265 std::shared_ptr<AccuracyObject>
addAccuracy(ObjectInfo & info,double accuracy)266     Sensor::addAccuracy(ObjectInfo& info, double accuracy)
267 {
268     auto& objPath = std::get<std::string>(info);
269     auto& obj = std::get<InterfaceMap>(info);
270 
271     auto& bus = *std::get<sdbusplus::bus_t*>(info);
272     auto iface = std::make_shared<AccuracyObject>(
273         bus, objPath.c_str(), AccuracyObject::action::emit_no_signals);
274 
275     iface->accuracy(accuracy);
276     obj[InterfaceType::ACCURACY] = iface;
277 
278     return iface;
279 }
280 
281 std::shared_ptr<PriorityObject>
addPriority(ObjectInfo & info,size_t priority)282     Sensor::addPriority(ObjectInfo& info, size_t priority)
283 {
284     auto& objPath = std::get<std::string>(info);
285     auto& obj = std::get<InterfaceMap>(info);
286 
287     auto& bus = *std::get<sdbusplus::bus_t*>(info);
288     auto iface = std::make_shared<PriorityObject>(
289         bus, objPath.c_str(), PriorityObject::action::emit_no_signals);
290 
291     iface->priority(priority);
292     obj[InterfaceType::PRIORITY] = iface;
293 
294     return iface;
295 }
296 
gpioLock(const gpioplus::HandleInterface * && handle)297 void gpioLock(const gpioplus::HandleInterface*&& handle)
298 {
299     handle->setValues({0});
300 }
301 
gpioUnlock(const gpioplus::HandleInterface * handle)302 std::optional<GpioLocker> gpioUnlock(const gpioplus::HandleInterface* handle)
303 {
304     if (handle == nullptr)
305     {
306         return std::nullopt;
307     }
308 
309     handle->setValues({1});
310     // Default pause needed to guarantee sensors are ready
311     std::this_thread::sleep_for(std::chrono::milliseconds(500));
312     return GpioLocker(std::move(handle));
313 }
314 
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)315 SensorValueType asyncRead(
316     const SensorSet::key_type& sensorSetKey,
317     const hwmonio::HwmonIOInterface* ioAccess,
318     std::chrono::milliseconds asyncTimeout, TimedoutMap& timedoutMap,
319     const std::string& type, const std::string& id, const std::string& sensor,
320     const size_t retries, const std::chrono::milliseconds delay)
321 {
322     // Default async read timeout
323     bool valueIsValid = false;
324     std::future<int64_t> asyncThread;
325 
326     auto asyncIter = timedoutMap.find(sensorSetKey);
327     if (asyncIter == timedoutMap.end())
328     {
329         // If sensor not found in timedoutMap, spawn an async thread
330         asyncThread =
331             std::async(std::launch::async, &hwmonio::HwmonIOInterface::read,
332                        ioAccess, type, id, sensor, retries, delay);
333         valueIsValid = true;
334     }
335     else
336     {
337         // If we already have the async thread in the timedoutMap, it means this
338         // sensor has already timed out in the previous reads. No need to wait
339         // on subsequent reads - proceed to check the future_status to see when
340         // the async thread finishes
341         asyncTimeout = std::chrono::seconds(0);
342         asyncThread = std::move(asyncIter->second);
343     }
344 
345     // TODO: This is still not a true asynchronous read as it still blocks the
346     // main thread for asyncTimeout amount of time. To make this completely
347     // asynchronous, schedule a read and register a callback to update the
348     // sensor value
349     std::future_status status = asyncThread.wait_for(asyncTimeout);
350     switch (status)
351     {
352         case std::future_status::ready:
353             // Read has finished
354             if (valueIsValid)
355             {
356                 return asyncThread.get();
357                 // Good sensor reads should skip the code below
358             }
359             // Async read thread has completed but had previously timed out (was
360             // found in the timedoutMap). Erase from timedoutMap and throw to
361             // allow retry in the next read cycle. Not returning the read value
362             // as the sensor reading may be bad / corrupted if it took so long.
363             timedoutMap.erase(sensorSetKey);
364             throw AsyncSensorReadTimeOut();
365         default:
366             // Read timed out so add the thread to the timedoutMap (if the entry
367             // already exists, operator[] updates it).
368             //
369             // Keeping the timed out futures in a map is required to prevent
370             // their destructor from being called when returning from this
371             // stack. The destructor will otherwise block until the read
372             // completes due to the limitation of std::async.
373             timedoutMap[sensorSetKey] = std::move(asyncThread);
374             throw AsyncSensorReadTimeOut();
375     }
376 }
377 
378 } // namespace sensor
379