xref: /openbmc/phosphor-hwmon/sensor.cpp (revision 5906173a)
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 <cassert>
12 #include <chrono>
13 #include <cmath>
14 #include <cstring>
15 #include <filesystem>
16 #include <phosphor-logging/elog-errors.hpp>
17 #include <thread>
18 #include <xyz/openbmc_project/Common/error.hpp>
19 #include <xyz/openbmc_project/Sensor/Device/error.hpp>
20 
21 namespace sensor
22 {
23 
24 using namespace phosphor::logging;
25 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
26 
27 // todo: this can be deleted once we move to double
28 // helper class to set the scale on the value iface only when it's available
29 template <typename T>
30 void setScale(T& iface, int64_t value, double)
31 {
32 }
33 template <typename T>
34 void setScale(T& iface, int64_t value, int64_t)
35 {
36     iface->scale(value);
37 }
38 
39 // todo: this can be simplified once we move to the double interface
40 Sensor::Sensor(const SensorSet::key_type& sensor,
41                const hwmonio::HwmonIOInterface* ioAccess,
42                const std::string& devPath) :
43     _sensor(sensor),
44     _ioAccess(ioAccess), _devPath(devPath), _scale(0), _hasFaultFile(false)
45 {
46     auto chip = env::getEnv("GPIOCHIP", sensor);
47     auto access = env::getEnv("GPIO", sensor);
48     if (!access.empty() && !chip.empty())
49     {
50         _handle = gpio::BuildGpioHandle(chip, access);
51 
52         if (!_handle)
53         {
54             log<level::ERR>("Unable to set up gpio locking");
55             elog<InternalFailure>();
56         }
57     }
58 
59     auto gain = env::getEnv("GAIN", sensor);
60     if (!gain.empty())
61     {
62         _sensorAdjusts.gain = std::stod(gain);
63     }
64 
65     auto offset = env::getEnv("OFFSET", sensor);
66     if (!offset.empty())
67     {
68         _sensorAdjusts.offset = std::stoi(offset);
69     }
70     auto senRmRCs = env::getEnv("REMOVERCS", sensor);
71     // Add sensor removal return codes defined per sensor
72     addRemoveRCs(senRmRCs);
73 }
74 
75 void Sensor::addRemoveRCs(const std::string& rcList)
76 {
77     if (rcList.empty())
78     {
79         return;
80     }
81 
82     // Convert to a char* for strtok
83     std::vector<char> rmRCs(rcList.c_str(), rcList.c_str() + rcList.size() + 1);
84     auto rmRC = std::strtok(&rmRCs[0], ", ");
85     while (rmRC != nullptr)
86     {
87         try
88         {
89             _sensorAdjusts.rmRCs.insert(std::stoi(rmRC));
90         }
91         catch (const std::logic_error& le)
92         {
93             // Unable to convert to int, continue to next token
94             std::string name = _sensor.first + "_" + _sensor.second;
95             log<level::INFO>("Unable to convert sensor removal return code",
96                              entry("SENSOR=%s", name.c_str()),
97                              entry("RC=%s", rmRC),
98                              entry("EXCEPTION=%s", le.what()));
99         }
100         rmRC = std::strtok(nullptr, ", ");
101     }
102 }
103 
104 SensorValueType Sensor::adjustValue(SensorValueType value)
105 {
106 // Because read doesn't have an out pointer to store errors.
107 // let's assume negative values are errors if they have this
108 // set.
109 #ifdef NEGATIVE_ERRNO_ON_FAIL
110     if (value < 0)
111     {
112         return value;
113     }
114 #endif
115 
116     // Adjust based on gain and offset
117     value = static_cast<decltype(value)>(static_cast<double>(value) *
118                                              _sensorAdjusts.gain +
119                                          _sensorAdjusts.offset);
120 
121     if constexpr (std::is_same<SensorValueType, double>::value)
122     {
123         value *= std::pow(10, _scale);
124     }
125 
126     return value;
127 }
128 
129 std::shared_ptr<ValueObject> Sensor::addValue(const RetryIO& retryIO,
130                                               ObjectInfo& info)
131 {
132     static constexpr bool deferSignals = true;
133 
134     // Get the initial value for the value interface.
135     auto& bus = *std::get<sdbusplus::bus::bus*>(info);
136     auto& obj = std::get<InterfaceMap>(info);
137     auto& objPath = std::get<std::string>(info);
138 
139     SensorValueType val = 0;
140 
141     auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>(
142         obj[InterfaceType::STATUS]);
143     // As long as addStatus is called before addValue, statusIface
144     // should never be nullptr
145     assert(statusIface);
146 
147     // Only read the input value if the status is functional
148     if (statusIface->functional())
149     {
150 #ifdef UPDATE_FUNCTIONAL_ON_FAIL
151         try
152 #endif
153         {
154             // RAII object for GPIO unlock / lock
155             auto locker = gpioUnlock(getGpio());
156 
157             // Retry for up to a second if device is busy
158             // or has a transient error.
159             val =
160                 _ioAccess->read(_sensor.first, _sensor.second,
161                                 hwmon::entry::cinput, std::get<size_t>(retryIO),
162                                 std::get<std::chrono::milliseconds>(retryIO));
163 
164             val = adjustValue(val);
165         }
166 #ifdef UPDATE_FUNCTIONAL_ON_FAIL
167         catch (const std::system_error& e)
168         {
169             // Catch the exception here and update the functional property.
170             // By catching the exception, it will not propagate it up the stack
171             // and thus the code will skip the "Remove RCs" check in
172             // MainLoop::getObject and will not exit on failure.
173             statusIface->functional(false);
174         }
175 #endif
176     }
177 
178     auto iface =
179         std::make_shared<ValueObject>(bus, objPath.c_str(), deferSignals);
180     iface->value(val);
181 
182     hwmon::Attributes attrs;
183     if (hwmon::getAttributes(_sensor.first, attrs))
184     {
185         iface->unit(hwmon::getUnit(attrs));
186 
187         setScale(iface, hwmon::getScale(attrs), val);
188 
189         _scale = hwmon::getScale(attrs);
190     }
191 
192     auto maxValue = env::getEnv("MAXVALUE", _sensor);
193     if (!maxValue.empty())
194     {
195         iface->maxValue(std::stoll(maxValue));
196     }
197     auto minValue = env::getEnv("MINVALUE", _sensor);
198     if (!minValue.empty())
199     {
200         iface->minValue(std::stoll(minValue));
201     }
202 
203     obj[InterfaceType::VALUE] = iface;
204     return iface;
205 }
206 
207 std::shared_ptr<StatusObject> Sensor::addStatus(ObjectInfo& info)
208 {
209     namespace fs = std::filesystem;
210 
211     std::shared_ptr<StatusObject> iface = nullptr;
212     auto& objPath = std::get<std::string>(info);
213     auto& obj = std::get<InterfaceMap>(info);
214 
215     // Check if fault sysfs file exists
216     std::string faultName = _sensor.first;
217     std::string faultID = _sensor.second;
218     std::string entry = hwmon::entry::fault;
219 
220     bool functional = true;
221     auto sysfsFullPath =
222         sysfs::make_sysfs_path(_ioAccess->path(), faultName, faultID, entry);
223     if (fs::exists(sysfsFullPath))
224     {
225         _hasFaultFile = true;
226         try
227         {
228             uint32_t fault = _ioAccess->read(faultName, faultID, entry,
229                                              hwmonio::retries, hwmonio::delay);
230             if (fault != 0)
231             {
232                 functional = false;
233             }
234         }
235         catch (const std::system_error& e)
236         {
237             using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::
238                 Error;
239             using metadata = xyz::openbmc_project::Sensor::Device::ReadFailure;
240 
241             report<ReadFailure>(
242                 metadata::CALLOUT_ERRNO(e.code().value()),
243                 metadata::CALLOUT_DEVICE_PATH(_devPath.c_str()));
244 
245             log<level::INFO>(
246                 "Logging failing sysfs file",
247                 phosphor::logging::entry("FILE=%s", sysfsFullPath.c_str()));
248         }
249     }
250 
251     static constexpr bool deferSignals = true;
252     auto& bus = *std::get<sdbusplus::bus::bus*>(info);
253 
254     iface = std::make_shared<StatusObject>(bus, objPath.c_str(), deferSignals);
255     // Set functional property
256     iface->functional(functional);
257 
258     obj[InterfaceType::STATUS] = iface;
259 
260     return iface;
261 }
262 
263 void gpioLock(const gpioplus::HandleInterface*&& handle)
264 {
265     handle->setValues({0});
266 }
267 
268 std::optional<GpioLocker> gpioUnlock(const gpioplus::HandleInterface* handle)
269 {
270     if (handle == nullptr)
271     {
272         return std::nullopt;
273     }
274 
275     handle->setValues({1});
276     // Default pause needed to guarantee sensors are ready
277     std::this_thread::sleep_for(std::chrono::milliseconds(500));
278     return GpioLocker(std::move(handle));
279 }
280 
281 } // namespace sensor
282