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