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