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