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 "sysfs.hpp"
17
18 #include <stdplus/print.hpp>
19
20 #include <algorithm>
21 #include <cerrno>
22 #include <cstdlib>
23 #include <filesystem>
24 #include <format>
25 #include <fstream>
26 #include <string>
27
28 using namespace std::string_literals;
29 namespace fs = std::filesystem;
30
31 namespace sysfs
32 {
33
34 static const auto emptyString = ""s;
35 static constexpr auto ofRoot = "/sys/firmware/devicetree/base";
36
findPhandleMatch(const std::string & iochanneldir,const std::string & phandledir)37 std::string findPhandleMatch(const std::string& iochanneldir,
38 const std::string& phandledir)
39 {
40 // TODO: At the moment this method only supports device trees
41 // with iio-hwmon nodes with a single sensor. Typically
42 // device trees are defined with all the iio sensors in a
43 // single iio-hwmon node so it would be nice to add support
44 // for lists of phandles (with variable sized entries) via
45 // libfdt or something like that, so that users are not
46 // forced into implementing unusual looking device trees
47 // with multiple iio-hwmon nodes - one for each sensor.
48
49 fs::path ioChannelsPath{iochanneldir};
50 ioChannelsPath /= "io-channels";
51
52 if (!fs::exists(ioChannelsPath))
53 {
54 return emptyString;
55 }
56
57 uint32_t ioChannelsValue;
58 std::ifstream ioChannelsFile(ioChannelsPath);
59
60 ioChannelsFile.read(reinterpret_cast<char*>(&ioChannelsValue),
61 sizeof(ioChannelsValue));
62
63 for (const auto& ofInst : fs::recursive_directory_iterator(phandledir))
64 {
65 auto path = ofInst.path();
66 if ("phandle" != path.filename())
67 {
68 continue;
69 }
70 std::ifstream pHandleFile(path);
71 uint32_t pHandleValue;
72
73 pHandleFile.read(reinterpret_cast<char*>(&pHandleValue),
74 sizeof(pHandleValue));
75
76 if (ioChannelsValue == pHandleValue)
77 {
78 return path;
79 }
80 }
81
82 return emptyString;
83 }
84
findCalloutPath(const std::string & instancePath)85 std::string findCalloutPath(const std::string& instancePath)
86 {
87 // Follow the hwmon instance (/sys/class/hwmon/hwmon<N>)
88 // /sys/devices symlink.
89 fs::path devPath{instancePath};
90 devPath /= "device";
91
92 try
93 {
94 devPath = fs::canonical(devPath);
95 }
96 catch (const std::system_error& e)
97 {
98 return emptyString;
99 }
100
101 // See if the device is backed by the iio-hwmon driver.
102 fs::path p{devPath};
103 p /= "driver";
104 p = fs::canonical(p);
105
106 if (p.filename() != "iio_hwmon")
107 {
108 // Not backed by iio-hwmon. The device pointed to
109 // is the callout device.
110 return devPath;
111 }
112
113 // Find the DT path to the iio-hwmon platform device.
114 fs::path ofDevPath{devPath};
115 ofDevPath /= "of_node";
116
117 try
118 {
119 ofDevPath = fs::canonical(ofDevPath);
120 }
121 catch (const std::system_error& e)
122 {
123 return emptyString;
124 }
125
126 // Search /sys/bus/iio/devices for the phandle in io-channels.
127 // If a match is found, use the corresponding /sys/devices
128 // iio device as the callout device.
129 static constexpr auto iioDevices = "/sys/bus/iio/devices";
130 for (const auto& iioDev : fs::recursive_directory_iterator(iioDevices))
131 {
132 p = iioDev.path();
133 p /= "of_node";
134
135 try
136 {
137 p = fs::canonical(p);
138 }
139 catch (const std::system_error& e)
140 {
141 continue;
142 }
143
144 auto match = findPhandleMatch(ofDevPath, p);
145 auto n = match.rfind('/');
146 if (n != std::string::npos)
147 {
148 // This is the iio device referred to by io-channels.
149 // Remove iio:device<N>.
150 try
151 {
152 return fs::canonical(iioDev).parent_path();
153 }
154 catch (const std::system_error& e)
155 {
156 return emptyString;
157 }
158 }
159 }
160
161 return emptyString;
162 }
163
findHwmonFromOFPath(const std::string & ofNode)164 std::string findHwmonFromOFPath(const std::string& ofNode)
165 {
166 static constexpr auto hwmonRoot = "/sys/class/hwmon";
167
168 auto fullOfPath = fs::path(ofRoot) / fs::path(ofNode).relative_path();
169
170 for (const auto& hwmonInst : fs::directory_iterator(hwmonRoot))
171 {
172 auto path = hwmonInst.path();
173 path /= "of_node";
174
175 try
176 {
177 path = fs::canonical(path);
178 }
179 catch (const std::system_error& e)
180 {
181 // realpath may encounter ENOENT (Hwmon
182 // instances have a nasty habit of
183 // going away without warning).
184 continue;
185 }
186
187 if (path == fullOfPath)
188 {
189 return hwmonInst.path();
190 }
191
192 // Try to find HWMON instance via phandle values.
193 // Used for IIO device drivers.
194 auto matchpath = findPhandleMatch(path, fullOfPath);
195 if (!matchpath.empty())
196 {
197 return hwmonInst.path();
198 }
199 }
200
201 return emptyString;
202 }
203
findHwmonFromDevPath(const std::string & devPath)204 std::string findHwmonFromDevPath(const std::string& devPath)
205 {
206 fs::path p{"/sys"};
207 p /= fs::path(devPath).relative_path();
208 p /= "hwmon";
209
210 try
211 {
212 // This path is also used as a filesystem path to an environment
213 // file, and that has issues with ':'s in the path so they've
214 // been converted to '--'s. Convert them back now.
215 size_t pos = 0;
216 std::string path = p;
217 while ((pos = path.find("--")) != std::string::npos)
218 {
219 path.replace(pos, 2, ":");
220 }
221
222 auto dir_iter = fs::directory_iterator(path);
223 auto hwmonInst = std::find_if(
224 dir_iter, end(dir_iter), [](const fs::directory_entry& d) {
225 return (d.path().filename().string().find("hwmon") !=
226 std::string::npos);
227 });
228 if (hwmonInst != end(dir_iter))
229 {
230 return hwmonInst->path();
231 }
232 }
233 catch (const std::exception& e)
234 {
235 stdplus::print(stderr,
236 "Unable to find hwmon directory from the dev path: {}\n",
237 devPath.c_str());
238 }
239 return emptyString;
240 }
241
242 } // namespace sysfs
243
244 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
245