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