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