/** * Copyright © 2016 IBM Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include "sysfs.hpp" using namespace std::string_literals; namespace fs = std::experimental::filesystem; namespace sysfs { static constexpr auto retryableErrors = { /* * Retry on bus or device errors or timeouts in case * they are transient. */ EIO, ETIMEDOUT, /* * Retry CRC errors. */ EBADMSG, /* * Some hwmon drivers do this when they aren't ready * instead of blocking. Retry. */ EAGAIN, /* * We'll see this when for example i2c devices are * unplugged but the driver is still bound. Retry * rather than exit on the off chance the device is * plugged back in and the driver doesn't do a * remove/probe. If a remove does occur, we'll * eventually get ENOENT. */ ENXIO, /* * Some devices return this when they are busy doing * something else. Even if being busy isn't the cause, * a retry still gives this app a shot at getting data * as opposed to failing out on the first try. */ ENODATA, }; static const auto emptyString = ""s; static constexpr auto ofRoot = "/sys/firmware/devicetree/base"; std::string findPhandleMatch( const std::string& iochanneldir, const std::string& phandledir) { // TODO: At the moment this method only supports device trees // with iio-hwmon nodes with a single sensor. Typically // device trees are defined with all the iio sensors in a // single iio-hwmon node so it would be nice to add support // for lists of phandles (with variable sized entries) via // libfdt or something like that, so that users are not // forced into implementing unusual looking device trees // with multiple iio-hwmon nodes - one for each sensor. fs::path ioChannelsPath{iochanneldir}; ioChannelsPath /= "io-channels"; if (!fs::exists(ioChannelsPath)) { return emptyString; } uint32_t ioChannelsValue; std::ifstream ioChannelsFile(ioChannelsPath); ioChannelsFile.read( reinterpret_cast(&ioChannelsValue), sizeof(ioChannelsValue)); for (const auto& ofInst : fs::recursive_directory_iterator(phandledir)) { auto path = ofInst.path(); if ("phandle" != path.filename()) { continue; } std::ifstream pHandleFile(path); uint32_t pHandleValue; pHandleFile.read( reinterpret_cast(&pHandleValue), sizeof(pHandleValue)); if (ioChannelsValue == pHandleValue) { return path; } } return emptyString; } std::string findCalloutPath(const std::string& instancePath) { // Follow the hwmon instance (/sys/class/hwmon/hwmon) // /sys/devices symlink. fs::path devPath{instancePath}; devPath /= "device"; try { devPath = fs::canonical(devPath); } catch (const std::system_error& e) { return emptyString; } // See if the device is backed by the iio-hwmon driver. fs::path p{devPath}; p /= "driver"; p = fs::canonical(p); if (p.filename() != "iio_hwmon") { // Not backed by iio-hwmon. The device pointed to // is the callout device. return devPath; } // Find the DT path to the iio-hwmon platform device. fs::path ofDevPath{devPath}; ofDevPath /= "of_node"; try { ofDevPath = fs::canonical(ofDevPath); } catch (const std::system_error& e) { return emptyString; } // Search /sys/bus/iio/devices for the phandle in io-channels. // If a match is found, use the corresponding /sys/devices // iio device as the callout device. static constexpr auto iioDevices = "/sys/bus/iio/devices"; for (const auto& iioDev: fs::recursive_directory_iterator(iioDevices)) { p = iioDev.path(); p /= "of_node"; try { p = fs::canonical(p); } catch (const std::system_error& e) { continue; } auto match = findPhandleMatch(ofDevPath, p); auto n = match.rfind('/'); if (n != std::string::npos) { // This is the iio device referred to by io-channels. // Remove iio:device. try { return fs::canonical(iioDev).parent_path(); } catch (const std::system_error& e) { return emptyString; } } } return emptyString; } std::string findHwmon(const std::string& ofNode) { static constexpr auto hwmonRoot = "/sys/class/hwmon"; fs::path fullOfPath{ofRoot}; fullOfPath /= ofNode; for (const auto& hwmonInst : fs::directory_iterator(hwmonRoot)) { auto path = hwmonInst.path(); path /= "of_node"; try { path = fs::canonical(path); } catch (const std::system_error& e) { // realpath may encounter ENOENT (Hwmon // instances have a nasty habit of // going away without warning). continue; } if (path == fullOfPath) { return hwmonInst.path(); } // Try to find HWMON instance via phandle values. // Used for IIO device drivers. auto matchpath = findPhandleMatch(path, fullOfPath); if (!matchpath.empty()) { return hwmonInst.path(); } } return emptyString; } namespace hwmonio { HwmonIO::HwmonIO(const std::string& path) : p(path) { } int64_t HwmonIO::read( const std::string& type, const std::string& id, const std::string& sensor, size_t retries, std::chrono::milliseconds delay) const { int64_t val; std::ifstream ifs; auto fullPath = sysfs::make_sysfs_path( p, type, id, sensor); ifs.exceptions( std::ifstream::failbit | std::ifstream::badbit | std::ifstream::eofbit); while (true) { try { errno = 0; if (!ifs.is_open()) ifs.open(fullPath); ifs.clear(); ifs.seekg(0); ifs >> val; } catch (const std::exception& e) { auto rc = errno; if (!rc) { throw; } if (rc == ENOENT || rc == ENODEV) { // If the directory or device disappeared then this application // should gracefully exit. There are race conditions between the // unloading of a hwmon driver and the stopping of this service // by systemd. To prevent this application from falsely failing // in these scenarios, it will simply exit if the directory or // file can not be found. It is up to the user(s) of this // provided hwmon object to log the appropriate errors if the // object disappears when it should not. exit(0); } if (0 == std::count( retryableErrors.begin(), retryableErrors.end(), rc) || !retries) { // Not a retryable error or out of retries. // Work around GCC bugs 53984 and 66145 for callers by // explicitly raising system_error here. throw std::system_error(rc, std::generic_category()); } --retries; std::this_thread::sleep_for(delay); continue; } break; } return val; } void HwmonIO::write( uint32_t val, const std::string& type, const std::string& id, const std::string& sensor, size_t retries, std::chrono::milliseconds delay) const { std::ofstream ofs; auto fullPath = sysfs::make_sysfs_path( p, type, id, sensor); ofs.exceptions( std::ofstream::failbit | std::ofstream::badbit | std::ofstream::eofbit); // See comments in the read method for an explanation of the odd exception // handling behavior here. while (true) { try { errno = 0; if (!ofs.is_open()) ofs.open(fullPath); ofs.clear(); ofs.seekp(0); ofs << val; ofs.flush(); } catch (const std::exception& e) { auto rc = errno; if (!rc) { throw; } if (rc == ENOENT) { exit(0); } if (0 == std::count( retryableErrors.begin(), retryableErrors.end(), rc) || !retries) { // Not a retryable error or out of retries. // Work around GCC bugs 53984 and 66145 for callers by // explicitly raising system_error here. throw std::system_error(rc, std::generic_category()); } --retries; std::this_thread::sleep_for(delay); continue; } break; } } std::string HwmonIO::path() const { return p; } } // namespace hwmonio } // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4