blob: af5f86a9607d39bb472932a26749aa0e50bd64e4 [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 /*
Matt Spinler67ce4a42017-11-16 13:03:16 -060059 * Some devices return this when they are busy doing
60 * something else. Even if being busy isn't the cause,
61 * a retry still gives this app a shot at getting data
62 * as opposed to failing out on the first try.
63 */
64 ENODATA,
Brad Bishop754d38c2017-09-08 00:46:58 -040065};
66
Brad Bishopf4bf63a2017-08-28 15:39:19 -040067static const auto emptyString = ""s;
Brandon Wyman8af8a202017-05-31 18:26:30 -050068static constexpr auto ofRoot = "/sys/firmware/devicetree/base";
69
Brad Bishopf4bf63a2017-08-28 15:39:19 -040070std::string findPhandleMatch(
71 const std::string& iochanneldir,
72 const std::string& phandledir)
Brad Bishop613a5b32017-01-05 20:58:13 -050073{
Brad Bishopf4bf63a2017-08-28 15:39:19 -040074 // TODO: At the moment this method only supports device trees
75 // with iio-hwmon nodes with a single sensor. Typically
76 // device trees are defined with all the iio sensors in a
77 // single iio-hwmon node so it would be nice to add support
78 // for lists of phandles (with variable sized entries) via
79 // libfdt or something like that, so that users are not
80 // forced into implementing unusual looking device trees
81 // with multiple iio-hwmon nodes - one for each sensor.
82
83 fs::path ioChannelsPath{iochanneldir};
84 ioChannelsPath /= "io-channels";
85
86 if (!fs::exists(ioChannelsPath))
87 {
88 return emptyString;
89 }
90
91 uint32_t ioChannelsValue;
92 std::ifstream ioChannelsFile(ioChannelsPath);
93
94 ioChannelsFile.read(
95 reinterpret_cast<char*>(&ioChannelsValue),
96 sizeof(ioChannelsValue));
97
Brandon Wyman8af8a202017-05-31 18:26:30 -050098 for (const auto& ofInst : fs::recursive_directory_iterator(phandledir))
Brad Bishop613a5b32017-01-05 20:58:13 -050099 {
Brandon Wyman8af8a202017-05-31 18:26:30 -0500100 auto path = ofInst.path();
Brad Bishopf4bf63a2017-08-28 15:39:19 -0400101 if ("phandle" != path.filename())
Brad Bishop613a5b32017-01-05 20:58:13 -0500102 {
Brad Bishopf4bf63a2017-08-28 15:39:19 -0400103 continue;
104 }
105 std::ifstream pHandleFile(path);
106 uint32_t pHandleValue;
Brandon Wyman4eb98582017-05-24 14:24:00 -0500107
Brad Bishopf4bf63a2017-08-28 15:39:19 -0400108 pHandleFile.read(
109 reinterpret_cast<char*>(&pHandleValue),
110 sizeof(pHandleValue));
Brandon Wyman4eb98582017-05-24 14:24:00 -0500111
Brad Bishopf4bf63a2017-08-28 15:39:19 -0400112 if (ioChannelsValue == pHandleValue)
113 {
114 return path;
Brandon Wyman8af8a202017-05-31 18:26:30 -0500115 }
116 }
117
Brad Bishopf4bf63a2017-08-28 15:39:19 -0400118 return emptyString;
Brandon Wyman8af8a202017-05-31 18:26:30 -0500119}
120
Brad Bishop431d26a2017-08-25 09:47:58 -0400121std::string findCalloutPath(const std::string& instancePath)
Brandon Wyman8af8a202017-05-31 18:26:30 -0500122{
Brad Bishop431d26a2017-08-25 09:47:58 -0400123 // Follow the hwmon instance (/sys/class/hwmon/hwmon<N>)
124 // /sys/devices symlink.
125 fs::path devPath{instancePath};
126 devPath /= "device";
Brandon Wyman8af8a202017-05-31 18:26:30 -0500127
Brad Bishop431d26a2017-08-25 09:47:58 -0400128 try
Brandon Wyman8af8a202017-05-31 18:26:30 -0500129 {
Brad Bishop431d26a2017-08-25 09:47:58 -0400130 devPath = fs::canonical(devPath);
131 }
132 catch (const std::system_error& e)
133 {
134 return emptyString;
135 }
136
137 // See if the device is backed by the iio-hwmon driver.
138 fs::path p{devPath};
139 p /= "driver";
140 p = fs::canonical(p);
141
142 if (p.filename() != "iio_hwmon")
143 {
144 // Not backed by iio-hwmon. The device pointed to
145 // is the callout device.
146 return devPath;
147 }
148
149 // Find the DT path to the iio-hwmon platform device.
150 fs::path ofDevPath{devPath};
151 ofDevPath /= "of_node";
152
153 try
154 {
155 ofDevPath = fs::canonical(ofDevPath);
156 }
157 catch (const std::system_error& e)
158 {
159 return emptyString;
160 }
161
162 // Search /sys/bus/iio/devices for the phandle in io-channels.
163 // If a match is found, use the corresponding /sys/devices
164 // iio device as the callout device.
165 static constexpr auto iioDevices = "/sys/bus/iio/devices";
166 for (const auto& iioDev: fs::recursive_directory_iterator(iioDevices))
167 {
168 p = iioDev.path();
169 p /= "of_node";
170
171 try
172 {
173 p = fs::canonical(p);
174 }
175 catch (const std::system_error& e)
176 {
177 continue;
178 }
179
180 auto match = findPhandleMatch(ofDevPath, p);
181 auto n = match.rfind('/');
Brandon Wyman8af8a202017-05-31 18:26:30 -0500182 if (n != std::string::npos)
183 {
Brad Bishop431d26a2017-08-25 09:47:58 -0400184 // This is the iio device referred to by io-channels.
185 // Remove iio:device<N>.
186 try
187 {
188 return fs::canonical(iioDev).parent_path();
189 }
190 catch (const std::system_error& e)
191 {
192 return emptyString;
193 }
Brandon Wyman8af8a202017-05-31 18:26:30 -0500194 }
195 }
196
Brad Bishop431d26a2017-08-25 09:47:58 -0400197 return emptyString;
Brandon Wyman8af8a202017-05-31 18:26:30 -0500198}
199
200std::string findHwmon(const std::string& ofNode)
201{
202 static constexpr auto hwmonRoot = "/sys/class/hwmon";
203
204 fs::path fullOfPath{ofRoot};
205 fullOfPath /= ofNode;
206
207 for (const auto& hwmonInst : fs::directory_iterator(hwmonRoot))
208 {
209 auto path = hwmonInst.path();
210 path /= "of_node";
Brad Bishop4e24ebd2017-08-28 16:19:16 -0400211
212 try
Brandon Wyman8af8a202017-05-31 18:26:30 -0500213 {
Brad Bishop4e24ebd2017-08-28 16:19:16 -0400214 path = fs::canonical(path);
215 }
216 catch (const std::system_error& e)
217 {
218 // realpath may encounter ENOENT (Hwmon
219 // instances have a nasty habit of
220 // going away without warning).
221 continue;
Brad Bishop613a5b32017-01-05 20:58:13 -0500222 }
223
Brad Bishop4e24ebd2017-08-28 16:19:16 -0400224 if (path == fullOfPath)
225 {
226 return hwmonInst.path();
227 }
228
229 // Try to find HWMON instance via phandle values.
230 // Used for IIO device drivers.
231 auto matchpath = findPhandleMatch(path, fullOfPath);
232 if (!matchpath.empty())
233 {
234 return hwmonInst.path();
235 }
Brad Bishop613a5b32017-01-05 20:58:13 -0500236 }
237
Brad Bishop4e24ebd2017-08-28 16:19:16 -0400238 return emptyString;
Brad Bishop613a5b32017-01-05 20:58:13 -0500239}
240
Brad Bishop8b574a72017-08-25 16:17:19 -0400241namespace hwmonio
242{
243
244HwmonIO::HwmonIO(const std::string& path) : p(path)
245{
246
247}
248
Matt Spinlerfee106b2017-11-29 15:18:05 -0600249int64_t HwmonIO::read(
Brad Bishop8b574a72017-08-25 16:17:19 -0400250 const std::string& type,
251 const std::string& id,
Brad Bishop754d38c2017-09-08 00:46:58 -0400252 const std::string& sensor,
253 size_t retries,
254 std::chrono::milliseconds delay) const
Brad Bishop8b574a72017-08-25 16:17:19 -0400255{
Matt Spinlerfee106b2017-11-29 15:18:05 -0600256 int64_t val;
Brad Bishop8b574a72017-08-25 16:17:19 -0400257 std::ifstream ifs;
258 auto fullPath = sysfs::make_sysfs_path(
259 p, type, id, sensor);
260
261 ifs.exceptions(
262 std::ifstream::failbit |
263 std::ifstream::badbit |
264 std::ifstream::eofbit);
Brad Bishop754d38c2017-09-08 00:46:58 -0400265
266 while (true)
Brad Bishop8b574a72017-08-25 16:17:19 -0400267 {
Brad Bishop754d38c2017-09-08 00:46:58 -0400268 try
Brad Bishop8b574a72017-08-25 16:17:19 -0400269 {
Matt Spinlerc8a8e012017-12-14 10:22:22 -0600270 errno = 0;
Brad Bishop754d38c2017-09-08 00:46:58 -0400271 if (!ifs.is_open())
272 ifs.open(fullPath);
273 ifs.clear();
274 ifs.seekg(0);
275 ifs >> val;
Brad Bishop8b574a72017-08-25 16:17:19 -0400276 }
Brad Bishop754d38c2017-09-08 00:46:58 -0400277 catch (const std::exception& e)
Brad Bishop8b574a72017-08-25 16:17:19 -0400278 {
Brad Bishop754d38c2017-09-08 00:46:58 -0400279 auto rc = errno;
Brad Bishop8b574a72017-08-25 16:17:19 -0400280
Brad Bishop754d38c2017-09-08 00:46:58 -0400281 if (!rc)
282 {
283 throw;
284 }
285
Edward A. James26d21732017-11-30 13:17:26 -0600286 if (rc == ENOENT || rc == ENODEV)
Brad Bishop754d38c2017-09-08 00:46:58 -0400287 {
Edward A. James26d21732017-11-30 13:17:26 -0600288 // If the directory or device disappeared then this application
289 // should gracefully exit. There are race conditions between the
Brad Bishop754d38c2017-09-08 00:46:58 -0400290 // unloading of a hwmon driver and the stopping of this service
291 // by systemd. To prevent this application from falsely failing
292 // in these scenarios, it will simply exit if the directory or
293 // file can not be found. It is up to the user(s) of this
294 // provided hwmon object to log the appropriate errors if the
295 // object disappears when it should not.
296 exit(0);
297 }
298
299 if (0 == std::count(
300 retryableErrors.begin(),
301 retryableErrors.end(),
302 rc) ||
303 !retries)
304 {
305 // Not a retryable error or out of retries.
306
307 // Work around GCC bugs 53984 and 66145 for callers by
308 // explicitly raising system_error here.
309 throw std::system_error(rc, std::generic_category());
310 }
311
312 --retries;
313 std::this_thread::sleep_for(delay);
314 continue;
315 }
316 break;
Brad Bishop8b574a72017-08-25 16:17:19 -0400317 }
Brad Bishop754d38c2017-09-08 00:46:58 -0400318
Brad Bishop8b574a72017-08-25 16:17:19 -0400319 return val;
320}
321
322void HwmonIO::write(
323 uint32_t val,
324 const std::string& type,
325 const std::string& id,
Brad Bishop754d38c2017-09-08 00:46:58 -0400326 const std::string& sensor,
327 size_t retries,
328 std::chrono::milliseconds delay) const
329
Brad Bishop8b574a72017-08-25 16:17:19 -0400330{
331 std::ofstream ofs;
332 auto fullPath = sysfs::make_sysfs_path(
333 p, type, id, sensor);
334
335 ofs.exceptions(
336 std::ofstream::failbit |
337 std::ofstream::badbit |
338 std::ofstream::eofbit);
339
340 // See comments in the read method for an explanation of the odd exception
341 // handling behavior here.
342
Brad Bishop754d38c2017-09-08 00:46:58 -0400343 while (true)
Brad Bishop8b574a72017-08-25 16:17:19 -0400344 {
Brad Bishop754d38c2017-09-08 00:46:58 -0400345 try
Brad Bishop8b574a72017-08-25 16:17:19 -0400346 {
Matt Spinlerc8a8e012017-12-14 10:22:22 -0600347 errno = 0;
Brad Bishop754d38c2017-09-08 00:46:58 -0400348 if (!ofs.is_open())
349 ofs.open(fullPath);
350 ofs.clear();
351 ofs.seekp(0);
352 ofs << val;
353 ofs.flush();
Brad Bishop8b574a72017-08-25 16:17:19 -0400354 }
Brad Bishop754d38c2017-09-08 00:46:58 -0400355 catch (const std::exception& e)
Brad Bishop8b574a72017-08-25 16:17:19 -0400356 {
Brad Bishop754d38c2017-09-08 00:46:58 -0400357 auto rc = errno;
Brad Bishop8b574a72017-08-25 16:17:19 -0400358
Brad Bishop754d38c2017-09-08 00:46:58 -0400359 if (!rc)
360 {
361 throw;
362 }
363
364 if (rc == ENOENT)
365 {
366 exit(0);
367 }
368
369 if (0 == std::count(
370 retryableErrors.begin(),
371 retryableErrors.end(),
372 rc) ||
373 !retries)
374 {
375 // Not a retryable error or out of retries.
376
377 // Work around GCC bugs 53984 and 66145 for callers by
378 // explicitly raising system_error here.
379 throw std::system_error(rc, std::generic_category());
380 }
381
382 --retries;
383 std::this_thread::sleep_for(delay);
384 continue;
385 }
386 break;
Brad Bishop8b574a72017-08-25 16:17:19 -0400387 }
388}
389
390std::string HwmonIO::path() const
391{
392 return p;
393}
394
395} // namespace hwmonio
Patrick Venture1e6324f2017-06-01 14:07:05 -0700396}
Brad Bishop613a5b32017-01-05 20:58:13 -0500397// vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4