xref: /openbmc/phosphor-hwmon/sensor.cpp (revision 5e034afc7ac41630f075feda6bbeade1052ed27e)
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 <phosphor-logging/elog-errors.hpp>
19 #include <thread>
20 #include <xyz/openbmc_project/Common/error.hpp>
21 #include <xyz/openbmc_project/Sensor/Device/error.hpp>
22 
23 namespace sensor
24 {
25 
26 using namespace phosphor::logging;
27 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
28 
29 // todo: this can be deleted once we move to double
30 // helper class to set the scale on the value iface only when it's available
31 template <typename T>
32 void setScale(T& iface, int64_t value, double)
33 {
34 }
35 template <typename T>
36 void setScale(T& iface, int64_t value, int64_t)
37 {
38     iface->scale(value);
39 }
40 
41 // todo: this can be simplified once we move to the double interface
42 Sensor::Sensor(const SensorSet::key_type& sensor,
43                const hwmonio::HwmonIOInterface* ioAccess,
44                const std::string& devPath) :
45     _sensor(sensor),
46     _ioAccess(ioAccess), _devPath(devPath), _scale(0), _hasFaultFile(false)
47 {
48     auto chip = env::getEnv("GPIOCHIP", sensor);
49     auto access = env::getEnv("GPIO", sensor);
50     if (!access.empty() && !chip.empty())
51     {
52         _handle = gpio::BuildGpioHandle(chip, access);
53 
54         if (!_handle)
55         {
56             log<level::ERR>("Unable to set up gpio locking");
57             elog<InternalFailure>();
58         }
59     }
60 
61     auto gain = env::getEnv("GAIN", sensor);
62     if (!gain.empty())
63     {
64         _sensorAdjusts.gain = std::stod(gain);
65     }
66 
67     auto offset = env::getEnv("OFFSET", sensor);
68     if (!offset.empty())
69     {
70         _sensorAdjusts.offset = std::stoi(offset);
71     }
72     auto senRmRCs = env::getEnv("REMOVERCS", sensor);
73     // Add sensor removal return codes defined per sensor
74     addRemoveRCs(senRmRCs);
75 }
76 
77 void Sensor::addRemoveRCs(const std::string& rcList)
78 {
79     if (rcList.empty())
80     {
81         return;
82     }
83 
84     // Convert to a char* for strtok
85     std::vector<char> rmRCs(rcList.c_str(), rcList.c_str() + rcList.size() + 1);
86     auto rmRC = std::strtok(&rmRCs[0], ", ");
87     while (rmRC != nullptr)
88     {
89         try
90         {
91             _sensorAdjusts.rmRCs.insert(std::stoi(rmRC));
92         }
93         catch (const std::logic_error& le)
94         {
95             // Unable to convert to int, continue to next token
96             std::string name = _sensor.first + "_" + _sensor.second;
97             log<level::INFO>("Unable to convert sensor removal return code",
98                              entry("SENSOR=%s", name.c_str()),
99                              entry("RC=%s", rmRC),
100                              entry("EXCEPTION=%s", le.what()));
101         }
102         rmRC = std::strtok(nullptr, ", ");
103     }
104 }
105 
106 SensorValueType Sensor::adjustValue(SensorValueType value)
107 {
108 // Because read doesn't have an out pointer to store errors.
109 // let's assume negative values are errors if they have this
110 // set.
111 #ifdef NEGATIVE_ERRNO_ON_FAIL
112     if (value < 0)
113     {
114         return value;
115     }
116 #endif
117 
118     // Adjust based on gain and offset
119     value = static_cast<decltype(value)>(static_cast<double>(value) *
120                                              _sensorAdjusts.gain +
121                                          _sensorAdjusts.offset);
122 
123     if constexpr (std::is_same<SensorValueType, double>::value)
124     {
125         value *= std::pow(10, _scale);
126     }
127 
128     return value;
129 }
130 
131 std::shared_ptr<ValueObject> Sensor::addValue(const RetryIO& retryIO,
132                                               ObjectInfo& info)
133 {
134     static constexpr bool deferSignals = true;
135 
136     // Get the initial value for the value interface.
137     auto& bus = *std::get<sdbusplus::bus::bus*>(info);
138     auto& obj = std::get<InterfaceMap>(info);
139     auto& objPath = std::get<std::string>(info);
140 
141     SensorValueType val = 0;
142 
143     auto& statusIface = std::any_cast<std::shared_ptr<StatusObject>&>(
144         obj[InterfaceType::STATUS]);
145     // As long as addStatus is called before addValue, statusIface
146     // should never be nullptr
147     assert(statusIface);
148 
149     // Only read the input value if the status is functional
150     if (statusIface->functional())
151     {
152 #ifdef UPDATE_FUNCTIONAL_ON_FAIL
153         try
154 #endif
155         {
156             // RAII object for GPIO unlock / lock
157             auto locker = gpioUnlock(getGpio());
158 
159             // Retry for up to a second if device is busy
160             // or has a transient error.
161             val =
162                 _ioAccess->read(_sensor.first, _sensor.second,
163                                 hwmon::entry::cinput, std::get<size_t>(retryIO),
164                                 std::get<std::chrono::milliseconds>(retryIO));
165 
166             val = adjustValue(val);
167         }
168 #ifdef 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 =
181         std::make_shared<ValueObject>(bus, objPath.c_str(), deferSignals);
182     iface->value(val);
183 
184     hwmon::Attributes attrs;
185     if (hwmon::getAttributes(_sensor.first, attrs))
186     {
187         iface->unit(hwmon::getUnit(attrs));
188 
189         setScale(iface, hwmon::getScale(attrs), val);
190 
191         _scale = hwmon::getScale(attrs);
192     }
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 
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>(
248                 fmt::format("Failing sysfs file: {}", sysfsFullPath).c_str());
249         }
250     }
251 
252     static constexpr bool deferSignals = true;
253     auto& bus = *std::get<sdbusplus::bus::bus*>(info);
254 
255     iface = std::make_shared<StatusObject>(bus, objPath.c_str(), deferSignals);
256     // Set functional property
257     iface->functional(functional);
258 
259     obj[InterfaceType::STATUS] = iface;
260 
261     return iface;
262 }
263 
264 void gpioLock(const gpioplus::HandleInterface*&& handle)
265 {
266     handle->setValues({0});
267 }
268 
269 std::optional<GpioLocker> gpioUnlock(const gpioplus::HandleInterface* handle)
270 {
271     if (handle == nullptr)
272     {
273         return std::nullopt;
274     }
275 
276     handle->setValues({1});
277     // Default pause needed to guarantee sensors are ready
278     std::this_thread::sleep_for(std::chrono::milliseconds(500));
279     return GpioLocker(std::move(handle));
280 }
281 
282 } // namespace sensor
283