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