xref: /openbmc/phosphor-hwmon/sysfs.cpp (revision 5906173aec2a48f37d1356f1ade788c8d25530b2)
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(const std::string& ofNode)
166 {
167     static constexpr auto hwmonRoot = "/sys/class/hwmon";
168 
169     auto fullOfPath = fs::path(ofRoot) / fs::path(ofNode).relative_path();
170 
171     for (const auto& hwmonInst : fs::directory_iterator(hwmonRoot))
172     {
173         auto path = hwmonInst.path();
174         path /= "of_node";
175 
176         try
177         {
178             path = fs::canonical(path);
179         }
180         catch (const std::system_error& e)
181         {
182             // realpath may encounter ENOENT (Hwmon
183             // instances have a nasty habit of
184             // going away without warning).
185             continue;
186         }
187 
188         if (path == fullOfPath)
189         {
190             return hwmonInst.path();
191         }
192 
193         // Try to find HWMON instance via phandle values.
194         // Used for IIO device drivers.
195         auto matchpath = findPhandleMatch(path, fullOfPath);
196         if (!matchpath.empty())
197         {
198             return hwmonInst.path();
199         }
200     }
201 
202     return emptyString;
203 }
204 
205 std::string findHwmonFromDevPath(const std::string& devPath)
206 {
207     fs::path p{"/sys"};
208     p /= fs::path(devPath).relative_path();
209     p /= "hwmon";
210 
211     try
212     {
213         // This path is also used as a filesystem path to an environment
214         // file, and that has issues with ':'s in the path so they've
215         // been converted to '--'s.  Convert them back now.
216         size_t pos = 0;
217         std::string path = p;
218         while ((pos = path.find("--")) != std::string::npos)
219         {
220             path.replace(pos, 2, ":");
221         }
222 
223         auto dir_iter = fs::directory_iterator(path);
224         auto hwmonInst = std::find_if(
225             dir_iter, end(dir_iter), [](const fs::directory_entry& d) {
226                 return (d.path().filename().string().find("hwmon") !=
227                         std::string::npos);
228             });
229         if (hwmonInst != end(dir_iter))
230         {
231             return hwmonInst->path();
232         }
233     }
234     catch (const std::exception& e)
235     {
236         using namespace phosphor::logging;
237         log<level::ERR>("Unable to find hwmon directory from the dev path",
238                         entry("PATH=%s", devPath.c_str()));
239     }
240     return emptyString;
241 }
242 
243 } // namespace sysfs
244 
245 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
246