blob: aeee859ebf49f569c004f34ecaec1c1e7c0ecbcc [file] [log] [blame]
Brad Bishop613a5b32017-01-05 20:58:13 -05001/**
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 */
Brad Bishop754d38c2017-09-08 00:46:58 -040016#include <algorithm>
Brad Bishop8b574a72017-08-25 16:17:19 -040017#include <cerrno>
Brad Bishop613a5b32017-01-05 20:58:13 -050018#include <cstdlib>
Brad Bishop08379a32017-03-06 21:28:46 -050019#include <experimental/filesystem>
Brad Bishop68c43b22017-08-28 16:24:00 -040020#include <fstream>
Brad Bishop613a5b32017-01-05 20:58:13 -050021#include <memory>
Brad Bishop754d38c2017-09-08 00:46:58 -040022#include <thread>
Brad Bishop613a5b32017-01-05 20:58:13 -050023#include "sysfs.hpp"
Brad Bishop613a5b32017-01-05 20:58:13 -050024
Brad Bishopf4bf63a2017-08-28 15:39:19 -040025using namespace std::string_literals;
Brandon Wyman8af8a202017-05-31 18:26:30 -050026namespace fs = std::experimental::filesystem;
Matthew Barth048ac872017-03-09 14:36:08 -060027
Patrick Venture1e6324f2017-06-01 14:07:05 -070028namespace sysfs {
29
Brad Bishop754d38c2017-09-08 00:46:58 -040030static constexpr auto retryableErrors = {
31 /*
32 * Retry on bus or device errors or timeouts in case
33 * they are transient.
34 */
35 EIO,
36 ETIMEDOUT,
37
38 /*
39 * Retry CRC errors.
40 */
41 EBADMSG,
42
43 /*
44 * Some hwmon drivers do this when they aren't ready
45 * instead of blocking. Retry.
46 */
47 EAGAIN,
48 /*
49 * We'll see this when for example i2c devices are
50 * unplugged but the driver is still bound. Retry
51 * rather than exit on the off chance the device is
52 * plugged back in and the driver doesn't do a
53 * remove/probe. If a remove does occur, we'll
54 * eventually get ENOENT.
55 */
56 ENXIO,
Matt Spinler8a37c4b2017-10-04 15:53:56 -050057
58 /*
59 * We can see this from some drivers when we try to do
60 * a read in the middle of them being unbound. The
61 * unbinding should complete before the retries are up
62 * and kill this process.
63 */
64 ENODEV,
Matt Spinler67ce4a42017-11-16 13:03:16 -060065
66 /*
67 * Some devices return this when they are busy doing
68 * something else. Even if being busy isn't the cause,
69 * a retry still gives this app a shot at getting data
70 * as opposed to failing out on the first try.
71 */
72 ENODATA,
Brad Bishop754d38c2017-09-08 00:46:58 -040073};
74
Brad Bishopf4bf63a2017-08-28 15:39:19 -040075static const auto emptyString = ""s;
Brandon Wyman8af8a202017-05-31 18:26:30 -050076static constexpr auto ofRoot = "/sys/firmware/devicetree/base";
77
Brad Bishopf4bf63a2017-08-28 15:39:19 -040078std::string findPhandleMatch(
79 const std::string& iochanneldir,
80 const std::string& phandledir)
Brad Bishop613a5b32017-01-05 20:58:13 -050081{
Brad Bishopf4bf63a2017-08-28 15:39:19 -040082 // TODO: At the moment this method only supports device trees
83 // with iio-hwmon nodes with a single sensor. Typically
84 // device trees are defined with all the iio sensors in a
85 // single iio-hwmon node so it would be nice to add support
86 // for lists of phandles (with variable sized entries) via
87 // libfdt or something like that, so that users are not
88 // forced into implementing unusual looking device trees
89 // with multiple iio-hwmon nodes - one for each sensor.
90
91 fs::path ioChannelsPath{iochanneldir};
92 ioChannelsPath /= "io-channels";
93
94 if (!fs::exists(ioChannelsPath))
95 {
96 return emptyString;
97 }
98
99 uint32_t ioChannelsValue;
100 std::ifstream ioChannelsFile(ioChannelsPath);
101
102 ioChannelsFile.read(
103 reinterpret_cast<char*>(&ioChannelsValue),
104 sizeof(ioChannelsValue));
105
Brandon Wyman8af8a202017-05-31 18:26:30 -0500106 for (const auto& ofInst : fs::recursive_directory_iterator(phandledir))
Brad Bishop613a5b32017-01-05 20:58:13 -0500107 {
Brandon Wyman8af8a202017-05-31 18:26:30 -0500108 auto path = ofInst.path();
Brad Bishopf4bf63a2017-08-28 15:39:19 -0400109 if ("phandle" != path.filename())
Brad Bishop613a5b32017-01-05 20:58:13 -0500110 {
Brad Bishopf4bf63a2017-08-28 15:39:19 -0400111 continue;
112 }
113 std::ifstream pHandleFile(path);
114 uint32_t pHandleValue;
Brandon Wyman4eb98582017-05-24 14:24:00 -0500115
Brad Bishopf4bf63a2017-08-28 15:39:19 -0400116 pHandleFile.read(
117 reinterpret_cast<char*>(&pHandleValue),
118 sizeof(pHandleValue));
Brandon Wyman4eb98582017-05-24 14:24:00 -0500119
Brad Bishopf4bf63a2017-08-28 15:39:19 -0400120 if (ioChannelsValue == pHandleValue)
121 {
122 return path;
Brandon Wyman8af8a202017-05-31 18:26:30 -0500123 }
124 }
125
Brad Bishopf4bf63a2017-08-28 15:39:19 -0400126 return emptyString;
Brandon Wyman8af8a202017-05-31 18:26:30 -0500127}
128
Brad Bishop431d26a2017-08-25 09:47:58 -0400129std::string findCalloutPath(const std::string& instancePath)
Brandon Wyman8af8a202017-05-31 18:26:30 -0500130{
Brad Bishop431d26a2017-08-25 09:47:58 -0400131 // Follow the hwmon instance (/sys/class/hwmon/hwmon<N>)
132 // /sys/devices symlink.
133 fs::path devPath{instancePath};
134 devPath /= "device";
Brandon Wyman8af8a202017-05-31 18:26:30 -0500135
Brad Bishop431d26a2017-08-25 09:47:58 -0400136 try
Brandon Wyman8af8a202017-05-31 18:26:30 -0500137 {
Brad Bishop431d26a2017-08-25 09:47:58 -0400138 devPath = fs::canonical(devPath);
139 }
140 catch (const std::system_error& e)
141 {
142 return emptyString;
143 }
144
145 // See if the device is backed by the iio-hwmon driver.
146 fs::path p{devPath};
147 p /= "driver";
148 p = fs::canonical(p);
149
150 if (p.filename() != "iio_hwmon")
151 {
152 // Not backed by iio-hwmon. The device pointed to
153 // is the callout device.
154 return devPath;
155 }
156
157 // Find the DT path to the iio-hwmon platform device.
158 fs::path ofDevPath{devPath};
159 ofDevPath /= "of_node";
160
161 try
162 {
163 ofDevPath = fs::canonical(ofDevPath);
164 }
165 catch (const std::system_error& e)
166 {
167 return emptyString;
168 }
169
170 // Search /sys/bus/iio/devices for the phandle in io-channels.
171 // If a match is found, use the corresponding /sys/devices
172 // iio device as the callout device.
173 static constexpr auto iioDevices = "/sys/bus/iio/devices";
174 for (const auto& iioDev: fs::recursive_directory_iterator(iioDevices))
175 {
176 p = iioDev.path();
177 p /= "of_node";
178
179 try
180 {
181 p = fs::canonical(p);
182 }
183 catch (const std::system_error& e)
184 {
185 continue;
186 }
187
188 auto match = findPhandleMatch(ofDevPath, p);
189 auto n = match.rfind('/');
Brandon Wyman8af8a202017-05-31 18:26:30 -0500190 if (n != std::string::npos)
191 {
Brad Bishop431d26a2017-08-25 09:47:58 -0400192 // This is the iio device referred to by io-channels.
193 // Remove iio:device<N>.
194 try
195 {
196 return fs::canonical(iioDev).parent_path();
197 }
198 catch (const std::system_error& e)
199 {
200 return emptyString;
201 }
Brandon Wyman8af8a202017-05-31 18:26:30 -0500202 }
203 }
204
Brad Bishop431d26a2017-08-25 09:47:58 -0400205 return emptyString;
Brandon Wyman8af8a202017-05-31 18:26:30 -0500206}
207
208std::string findHwmon(const std::string& ofNode)
209{
210 static constexpr auto hwmonRoot = "/sys/class/hwmon";
211
212 fs::path fullOfPath{ofRoot};
213 fullOfPath /= ofNode;
214
215 for (const auto& hwmonInst : fs::directory_iterator(hwmonRoot))
216 {
217 auto path = hwmonInst.path();
218 path /= "of_node";
Brad Bishop4e24ebd2017-08-28 16:19:16 -0400219
220 try
Brandon Wyman8af8a202017-05-31 18:26:30 -0500221 {
Brad Bishop4e24ebd2017-08-28 16:19:16 -0400222 path = fs::canonical(path);
223 }
224 catch (const std::system_error& e)
225 {
226 // realpath may encounter ENOENT (Hwmon
227 // instances have a nasty habit of
228 // going away without warning).
229 continue;
Brad Bishop613a5b32017-01-05 20:58:13 -0500230 }
231
Brad Bishop4e24ebd2017-08-28 16:19:16 -0400232 if (path == fullOfPath)
233 {
234 return hwmonInst.path();
235 }
236
237 // Try to find HWMON instance via phandle values.
238 // Used for IIO device drivers.
239 auto matchpath = findPhandleMatch(path, fullOfPath);
240 if (!matchpath.empty())
241 {
242 return hwmonInst.path();
243 }
Brad Bishop613a5b32017-01-05 20:58:13 -0500244 }
245
Brad Bishop4e24ebd2017-08-28 16:19:16 -0400246 return emptyString;
Brad Bishop613a5b32017-01-05 20:58:13 -0500247}
248
Brad Bishop8b574a72017-08-25 16:17:19 -0400249namespace hwmonio
250{
251
252HwmonIO::HwmonIO(const std::string& path) : p(path)
253{
254
255}
256
Matt Spinlerfee106b2017-11-29 15:18:05 -0600257int64_t HwmonIO::read(
Brad Bishop8b574a72017-08-25 16:17:19 -0400258 const std::string& type,
259 const std::string& id,
Brad Bishop754d38c2017-09-08 00:46:58 -0400260 const std::string& sensor,
261 size_t retries,
262 std::chrono::milliseconds delay) const
Brad Bishop8b574a72017-08-25 16:17:19 -0400263{
Matt Spinlerfee106b2017-11-29 15:18:05 -0600264 int64_t val;
Brad Bishop8b574a72017-08-25 16:17:19 -0400265 std::ifstream ifs;
266 auto fullPath = sysfs::make_sysfs_path(
267 p, type, id, sensor);
268
269 ifs.exceptions(
270 std::ifstream::failbit |
271 std::ifstream::badbit |
272 std::ifstream::eofbit);
Brad Bishop754d38c2017-09-08 00:46:58 -0400273
274 while (true)
Brad Bishop8b574a72017-08-25 16:17:19 -0400275 {
Brad Bishop754d38c2017-09-08 00:46:58 -0400276 try
Brad Bishop8b574a72017-08-25 16:17:19 -0400277 {
Brad Bishop754d38c2017-09-08 00:46:58 -0400278 if (!ifs.is_open())
279 ifs.open(fullPath);
280 ifs.clear();
281 ifs.seekg(0);
282 ifs >> val;
Brad Bishop8b574a72017-08-25 16:17:19 -0400283 }
Brad Bishop754d38c2017-09-08 00:46:58 -0400284 catch (const std::exception& e)
Brad Bishop8b574a72017-08-25 16:17:19 -0400285 {
Brad Bishop754d38c2017-09-08 00:46:58 -0400286 auto rc = errno;
Brad Bishop8b574a72017-08-25 16:17:19 -0400287
Brad Bishop754d38c2017-09-08 00:46:58 -0400288 if (!rc)
289 {
290 throw;
291 }
292
293 if (rc == ENOENT)
294 {
295 // If the directory disappeared then this application should
296 // gracefully exit. There are race conditions between the
297 // unloading of a hwmon driver and the stopping of this service
298 // by systemd. To prevent this application from falsely failing
299 // in these scenarios, it will simply exit if the directory or
300 // file can not be found. It is up to the user(s) of this
301 // provided hwmon object to log the appropriate errors if the
302 // object disappears when it should not.
303 exit(0);
304 }
305
306 if (0 == std::count(
307 retryableErrors.begin(),
308 retryableErrors.end(),
309 rc) ||
310 !retries)
311 {
312 // Not a retryable error or out of retries.
313
314 // Work around GCC bugs 53984 and 66145 for callers by
315 // explicitly raising system_error here.
316 throw std::system_error(rc, std::generic_category());
317 }
318
319 --retries;
320 std::this_thread::sleep_for(delay);
321 continue;
322 }
323 break;
Brad Bishop8b574a72017-08-25 16:17:19 -0400324 }
Brad Bishop754d38c2017-09-08 00:46:58 -0400325
Brad Bishop8b574a72017-08-25 16:17:19 -0400326 return val;
327}
328
329void HwmonIO::write(
330 uint32_t val,
331 const std::string& type,
332 const std::string& id,
Brad Bishop754d38c2017-09-08 00:46:58 -0400333 const std::string& sensor,
334 size_t retries,
335 std::chrono::milliseconds delay) const
336
Brad Bishop8b574a72017-08-25 16:17:19 -0400337{
338 std::ofstream ofs;
339 auto fullPath = sysfs::make_sysfs_path(
340 p, type, id, sensor);
341
342 ofs.exceptions(
343 std::ofstream::failbit |
344 std::ofstream::badbit |
345 std::ofstream::eofbit);
346
347 // See comments in the read method for an explanation of the odd exception
348 // handling behavior here.
349
Brad Bishop754d38c2017-09-08 00:46:58 -0400350 while (true)
Brad Bishop8b574a72017-08-25 16:17:19 -0400351 {
Brad Bishop754d38c2017-09-08 00:46:58 -0400352 try
Brad Bishop8b574a72017-08-25 16:17:19 -0400353 {
Brad Bishop754d38c2017-09-08 00:46:58 -0400354 if (!ofs.is_open())
355 ofs.open(fullPath);
356 ofs.clear();
357 ofs.seekp(0);
358 ofs << val;
359 ofs.flush();
Brad Bishop8b574a72017-08-25 16:17:19 -0400360 }
Brad Bishop754d38c2017-09-08 00:46:58 -0400361 catch (const std::exception& e)
Brad Bishop8b574a72017-08-25 16:17:19 -0400362 {
Brad Bishop754d38c2017-09-08 00:46:58 -0400363 auto rc = errno;
Brad Bishop8b574a72017-08-25 16:17:19 -0400364
Brad Bishop754d38c2017-09-08 00:46:58 -0400365 if (!rc)
366 {
367 throw;
368 }
369
370 if (rc == ENOENT)
371 {
372 exit(0);
373 }
374
375 if (0 == std::count(
376 retryableErrors.begin(),
377 retryableErrors.end(),
378 rc) ||
379 !retries)
380 {
381 // Not a retryable error or out of retries.
382
383 // Work around GCC bugs 53984 and 66145 for callers by
384 // explicitly raising system_error here.
385 throw std::system_error(rc, std::generic_category());
386 }
387
388 --retries;
389 std::this_thread::sleep_for(delay);
390 continue;
391 }
392 break;
Brad Bishop8b574a72017-08-25 16:17:19 -0400393 }
394}
395
396std::string HwmonIO::path() const
397{
398 return p;
399}
400
401} // namespace hwmonio
Patrick Venture1e6324f2017-06-01 14:07:05 -0700402}
Brad Bishop613a5b32017-01-05 20:58:13 -0500403// vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4