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