xref: /openbmc/phosphor-hwmon/sysfs.cpp (revision 02e598ab)
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