xref: /openbmc/phosphor-hwmon/sysfs.cpp (revision c1cece76)
1 /**
2  * Copyright © 2016 IBM Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include <algorithm>
17 #include <cerrno>
18 #include <cstdlib>
19 #include <experimental/filesystem>
20 #include <fstream>
21 #include <memory>
22 #include <phosphor-logging/log.hpp>
23 #include <thread>
24 #include "config.h"
25 #include "sysfs.hpp"
26 
27 using namespace std::string_literals;
28 namespace fs = std::experimental::filesystem;
29 
30 namespace sysfs {
31 
32 static constexpr auto retryableErrors = {
33     /*
34      * Retry on bus or device errors or timeouts in case
35      * they are transient.
36      */
37     EIO,
38     ETIMEDOUT,
39 
40     /*
41      * Retry CRC errors.
42      */
43     EBADMSG,
44 
45     /*
46      * Some hwmon drivers do this when they aren't ready
47      * instead of blocking.  Retry.
48      */
49     EAGAIN,
50     /*
51      * We'll see this when for example i2c devices are
52      * unplugged but the driver is still bound.  Retry
53      * rather than exit on the off chance the device is
54      * plugged back in and the driver doesn't do a
55      * remove/probe.  If a remove does occur, we'll
56      * eventually get ENOENT.
57      */
58     ENXIO,
59 
60     /*
61      * Some devices return this when they are busy doing
62      * something else.  Even if being busy isn't the cause,
63      * a retry still gives this app a shot at getting data
64      * as opposed to failing out on the first try.
65      */
66     ENODATA,
67 };
68 
69 static const auto emptyString = ""s;
70 static constexpr auto ofRoot = "/sys/firmware/devicetree/base";
71 
72 std::string findPhandleMatch(
73         const std::string& iochanneldir,
74         const std::string& phandledir)
75 {
76     // TODO: At the moment this method only supports device trees
77     // with iio-hwmon nodes with a single sensor.  Typically
78     // device trees are defined with all the iio sensors in a
79     // single iio-hwmon node so it would be nice to add support
80     // for lists of phandles (with variable sized entries) via
81     // libfdt or something like that, so that users are not
82     // forced into implementing unusual looking device trees
83     // with multiple iio-hwmon nodes - one for each sensor.
84 
85     fs::path ioChannelsPath{iochanneldir};
86     ioChannelsPath /= "io-channels";
87 
88     if (!fs::exists(ioChannelsPath))
89     {
90         return emptyString;
91     }
92 
93     uint32_t ioChannelsValue;
94     std::ifstream ioChannelsFile(ioChannelsPath);
95 
96     ioChannelsFile.read(
97             reinterpret_cast<char*>(&ioChannelsValue),
98             sizeof(ioChannelsValue));
99 
100     for (const auto& ofInst : fs::recursive_directory_iterator(phandledir))
101     {
102         auto path = ofInst.path();
103         if ("phandle" != path.filename())
104         {
105             continue;
106         }
107         std::ifstream pHandleFile(path);
108         uint32_t pHandleValue;
109 
110         pHandleFile.read(
111                 reinterpret_cast<char*>(&pHandleValue),
112                 sizeof(pHandleValue));
113 
114         if (ioChannelsValue == pHandleValue)
115         {
116             return path;
117         }
118     }
119 
120     return emptyString;
121 }
122 
123 std::string findCalloutPath(const std::string& instancePath)
124 {
125     // Follow the hwmon instance (/sys/class/hwmon/hwmon<N>)
126     // /sys/devices symlink.
127     fs::path devPath{instancePath};
128     devPath /= "device";
129 
130     try
131     {
132         devPath = fs::canonical(devPath);
133     }
134     catch (const std::system_error& e)
135     {
136         return emptyString;
137     }
138 
139     // See if the device is backed by the iio-hwmon driver.
140     fs::path p{devPath};
141     p /= "driver";
142     p = fs::canonical(p);
143 
144     if (p.filename() != "iio_hwmon")
145     {
146         // Not backed by iio-hwmon.  The device pointed to
147         // is the callout device.
148         return devPath;
149     }
150 
151     // Find the DT path to the iio-hwmon platform device.
152     fs::path ofDevPath{devPath};
153     ofDevPath /= "of_node";
154 
155     try
156     {
157         ofDevPath = fs::canonical(ofDevPath);
158     }
159     catch (const std::system_error& e)
160     {
161         return emptyString;
162     }
163 
164     // Search /sys/bus/iio/devices for the phandle in io-channels.
165     // If a match is found, use the corresponding /sys/devices
166     // iio device as the callout device.
167     static constexpr auto iioDevices = "/sys/bus/iio/devices";
168     for (const auto& iioDev: fs::recursive_directory_iterator(iioDevices))
169     {
170         p = iioDev.path();
171         p /= "of_node";
172 
173         try
174         {
175             p = fs::canonical(p);
176         }
177         catch (const std::system_error& e)
178         {
179             continue;
180         }
181 
182         auto match = findPhandleMatch(ofDevPath, p);
183         auto n = match.rfind('/');
184         if (n != std::string::npos)
185         {
186             // This is the iio device referred to by io-channels.
187             // Remove iio:device<N>.
188             try
189             {
190                 return fs::canonical(iioDev).parent_path();
191             }
192             catch (const std::system_error& e)
193             {
194                 return emptyString;
195             }
196         }
197     }
198 
199     return emptyString;
200 }
201 
202 std::string findHwmonFromOFPath(const std::string& ofNode)
203 {
204     static constexpr auto hwmonRoot = "/sys/class/hwmon";
205 
206     fs::path fullOfPath{ofRoot};
207     fullOfPath /= ofNode;
208 
209     for (const auto& hwmonInst : fs::directory_iterator(hwmonRoot))
210     {
211         auto path = hwmonInst.path();
212         path /= "of_node";
213 
214         try
215         {
216             path = fs::canonical(path);
217         }
218         catch (const std::system_error& e)
219         {
220             // realpath may encounter ENOENT (Hwmon
221             // instances have a nasty habit of
222             // going away without warning).
223             continue;
224         }
225 
226         if (path == fullOfPath)
227         {
228             return hwmonInst.path();
229         }
230 
231         // Try to find HWMON instance via phandle values.
232         // Used for IIO device drivers.
233         auto matchpath = findPhandleMatch(path, fullOfPath);
234         if (!matchpath.empty())
235         {
236             return hwmonInst.path();
237         }
238     }
239 
240     return emptyString;
241 }
242 
243 std::string findHwmonFromDevPath(const std::string& devPath)
244 {
245     fs::path p{"/sys"};
246     p /= devPath;
247     p /= "hwmon";
248 
249     try
250     {
251         //This path is also used as a filesystem path to an environment
252         //file, and that has issues with ':'s in the path so they've
253         //been converted to '--'s.  Convert them back now.
254         size_t pos = 0;
255         std::string path = p;
256         while ((pos = path.find("--")) != std::string::npos)
257         {
258             path.replace(pos, 2, ":");
259         }
260 
261         for (const auto& hwmonInst : fs::directory_iterator(path))
262         {
263             if ((hwmonInst.path().filename().string().find("hwmon") !=
264                    std::string::npos))
265             {
266                 return hwmonInst.path();
267             }
268         }
269     }
270     catch (const std::exception& e)
271     {
272         using namespace phosphor::logging;
273         log<level::ERR>(
274                 "Unable to find hwmon directory from the dev path",
275                 entry("PATH=%s", devPath.c_str()));
276     }
277     return emptyString;
278 }
279 
280 namespace hwmonio
281 {
282 
283 HwmonIO::HwmonIO(const std::string& path) : p(path)
284 {
285 
286 }
287 
288 int64_t HwmonIO::read(
289         const std::string& type,
290         const std::string& id,
291         const std::string& sensor,
292         size_t retries,
293         std::chrono::milliseconds delay,
294         bool isOCC) const
295 {
296     int64_t val;
297     std::ifstream ifs;
298     auto fullPath = sysfs::make_sysfs_path(
299             p, type, id, sensor);
300 
301     ifs.exceptions(
302             std::ifstream::failbit |
303                 std::ifstream::badbit |
304                 std::ifstream::eofbit);
305 
306     while (true)
307     {
308         try
309         {
310             errno = 0;
311             if (!ifs.is_open())
312                 ifs.open(fullPath);
313             ifs.clear();
314             ifs.seekg(0);
315             ifs >> val;
316         }
317         catch (const std::exception& e)
318         {
319             auto rc = errno;
320 
321             if (!rc)
322             {
323                 throw;
324             }
325 
326             if (rc == ENOENT || rc == ENODEV)
327             {
328                 // If the directory or device disappeared then this application
329                 // should gracefully exit.  There are race conditions between the
330                 // unloading of a hwmon driver and the stopping of this service
331                 // by systemd.  To prevent this application from falsely failing
332                 // in these scenarios, it will simply exit if the directory or
333                 // file can not be found.  It is up to the user(s) of this
334                 // provided hwmon object to log the appropriate errors if the
335                 // object disappears when it should not.
336                 exit(0);
337             }
338 
339             if (isOCC)
340             {
341                 if (rc == EAGAIN)
342                 {
343                     // For the OCCs, when an EAGAIN is return, just set the
344                     // value to 0 (0x00 = sensor is unavailable)
345                     val = 0;
346                     break;
347                 }
348                 else if (rc == EREMOTEIO)
349                 {
350                     // For the OCCs, when an EREMOTEIO is return, set the
351                     // value to 255*1000
352                     // (0xFF = sensor is failed, 1000 = sensor factor)
353                     val = 255000;
354                     break;
355                 }
356             }
357 
358             if (0 == std::count(
359                         retryableErrors.begin(),
360                         retryableErrors.end(),
361                         rc) ||
362                     !retries)
363             {
364                 // Not a retryable error or out of retries.
365 #ifdef NEGATIVE_ERRNO_ON_FAIL
366                 return -rc;
367 #endif
368 
369                 // Work around GCC bugs 53984 and 66145 for callers by
370                 // explicitly raising system_error here.
371                 throw std::system_error(rc, std::generic_category());
372             }
373 
374             --retries;
375             std::this_thread::sleep_for(delay);
376             continue;
377         }
378         break;
379     }
380 
381     return val;
382 }
383 
384 void HwmonIO::write(
385         uint32_t val,
386         const std::string& type,
387         const std::string& id,
388         const std::string& sensor,
389         size_t retries,
390         std::chrono::milliseconds delay) const
391 
392 {
393     std::ofstream ofs;
394     auto fullPath = sysfs::make_sysfs_path(
395             p, type, id, sensor);
396 
397     ofs.exceptions(
398             std::ofstream::failbit |
399                 std::ofstream::badbit |
400                 std::ofstream::eofbit);
401 
402     // See comments in the read method for an explanation of the odd exception
403     // handling behavior here.
404 
405     while (true)
406     {
407         try
408         {
409             errno = 0;
410             if (!ofs.is_open())
411                 ofs.open(fullPath);
412             ofs.clear();
413             ofs.seekp(0);
414             ofs << val;
415             ofs.flush();
416         }
417         catch (const std::exception& e)
418         {
419             auto rc = errno;
420 
421             if (!rc)
422             {
423                 throw;
424             }
425 
426             if (rc == ENOENT)
427             {
428                 exit(0);
429             }
430 
431             if (0 == std::count(
432                         retryableErrors.begin(),
433                         retryableErrors.end(),
434                         rc) ||
435                     !retries)
436             {
437                 // Not a retryable error or out of retries.
438 
439                 // Work around GCC bugs 53984 and 66145 for callers by
440                 // explicitly raising system_error here.
441                 throw std::system_error(rc, std::generic_category());
442             }
443 
444             --retries;
445             std::this_thread::sleep_for(delay);
446             continue;
447         }
448         break;
449     }
450 }
451 
452 std::string HwmonIO::path() const
453 {
454     return p;
455 }
456 
457 } // namespace hwmonio
458 }
459 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
460