xref: /openbmc/phosphor-hwmon/sysfs.cpp (revision 1e6324fa4fc233c3f2913b3d5278aa06e45ff42f)
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 <cstdlib>
17 #include <experimental/filesystem>
18 #include <memory>
19 #include <phosphor-logging/elog.hpp>
20 #include <phosphor-logging/elog-errors.hpp>
21 #include <xyz/openbmc_project/Control/Device/error.hpp>
22 #include <xyz/openbmc_project/Sensor/Device/error.hpp>
23 #include "sysfs.hpp"
24 #include "util.hpp"
25 #include <fstream>
26 
27 using namespace phosphor::logging;
28 namespace fs = std::experimental::filesystem;
29 
30 namespace sysfs {
31 
32 static constexpr auto ofRoot = "/sys/firmware/devicetree/base";
33 
34 /**
35  * @brief Return the path to the phandle file matching value in io-channels.
36  *
37  * This function will take two passed in paths.
38  * One path is used to find the io-channels file.
39  * The other path is used to find the phandle file.
40  * The 4 byte phandle value is read from the phandle file(s).
41  * The 4 byte phandle value and 4 byte index value is read from io-channels.
42  * When a match is found, the path to the matching phandle file is returned.
43  *
44  * @param[in] iochanneldir - Path to file for getting phandle from io-channels
45  * @param[in] phandledir - Path to use for reading from phandle file
46  *
47  * @return Path to phandle file with value matching that in io-channels
48  */
49 std::string findPhandleMatch(const std::string& iochanneldir,
50                              const std::string& phandledir)
51 {
52     for (const auto& ofInst : fs::recursive_directory_iterator(phandledir))
53     {
54         auto path = ofInst.path();
55         if ("phandle" == ofInst.path().filename())
56         {
57             auto ioChannelsPath = iochanneldir + "/io-channels";
58             if (fs::exists(ioChannelsPath))
59             {
60                 auto fullOfPathPhandle = ofInst.path();
61                 std::ifstream ioChannelsFile(path);
62                 std::ifstream pHandleFile(fullOfPathPhandle);
63 
64                 uint32_t ioChannelsValue;
65                 uint32_t pHandleValue;
66 
67                 try
68                 {
69                     ioChannelsFile.read(reinterpret_cast<char*>(&ioChannelsValue),
70                                         sizeof(ioChannelsValue));
71                     pHandleFile.read(reinterpret_cast<char*>(&pHandleValue),
72                                      sizeof(pHandleValue));
73 
74                     if (ioChannelsValue == pHandleValue)
75                     {
76                         return ofInst.path();
77                     }
78                 }
79                 catch (const std::exception& e)
80                 {
81                     log<level::INFO>(e.what());
82                     continue;
83                 }
84 
85             }
86         }
87     }
88 
89     return std::string();
90 }
91 
92 /**
93  * @brief Return the path to use for a call out.
94  *
95  * If the path does not contain iio-hwmon, assume passed in path is the call
96  * out path.
97  *
98  * @param[in] ofPath - Open firmware path to search for matching phandle value
99  *
100  * @return Path to use for call out
101  */
102 std::string findCalloutPath(const std::string& ofPath)
103 {
104     static constexpr auto iioHwmonStr = "iio-hwmon";
105 
106     if (ofPath.find(iioHwmonStr) != std::string::npos)
107     {
108         auto matchpath = findPhandleMatch(ofPath, ofRoot);
109         auto n = matchpath.rfind('/');
110         if (n != std::string::npos)
111         {
112             return matchpath.substr(0, n);
113         }
114     }
115 
116     return ofPath;
117 }
118 
119 std::string findHwmon(const std::string& ofNode)
120 {
121     static constexpr auto hwmonRoot = "/sys/class/hwmon";
122 
123     fs::path fullOfPath{ofRoot};
124     fullOfPath /= ofNode;
125 
126     for (const auto& hwmonInst : fs::directory_iterator(hwmonRoot))
127     {
128         auto path = hwmonInst.path();
129         path /= "of_node";
130         if (fs::canonical(path) != fullOfPath)
131         {
132             // Try to find HWMON instance via phandle values.
133             // Used for IIO device drivers.
134             auto ofpath = fullOfPath.string();
135             auto matchpath = findPhandleMatch(path, ofpath);
136             if (!std::string(matchpath).empty())
137             {
138                 return hwmonInst.path();
139             }
140             else
141             {
142                 continue;
143             }
144         }
145 
146         return hwmonInst.path();
147     }
148 
149     return std::string();
150 }
151 
152 int readSysfsWithCallout(const std::string& root,
153                          const std::string& instance,
154                          const std::string& type,
155                          const std::string& id,
156                          const std::string& sensor)
157 {
158     namespace fs = std::experimental::filesystem;
159 
160     int value = 0;
161     std::ifstream ifs;
162     fs::path instancePath{root};
163     instancePath /= instance;
164     std::string fullPath = make_sysfs_path(instancePath,
165                                            type, id, sensor);
166 
167     ifs.exceptions(std::ifstream::failbit
168                    | std::ifstream::badbit
169                    | std::ifstream::eofbit);
170     try
171     {
172         ifs.open(fullPath);
173         ifs >> value;
174     }
175     catch (const std::exception& e)
176     {
177         // Too many GCC bugs (53984, 66145) to do
178         // this the right way...
179 
180         // errno should still reflect the error from the failing open
181         // or read system calls that got us here.
182         auto rc = errno;
183 
184         // If the directory disappeared then this application should gracefully
185         // exit.  There are race conditions between the unloading of a hwmon
186         // driver and the stopping of this service by systemd.  To prevent
187         // this application from falsely failing in these scenarios, it will
188         // simply exit if the directory or file can not be found.  It is up
189         // to the user(s) of this provided hwmon object to log the appropriate
190         // errors if the object disappears when it should not.
191         if (rc == ENOENT)
192         {
193             exit(0);
194         }
195         instancePath /= "device";
196         auto callOutPath = findCalloutPath(fs::canonical(instancePath));
197         using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error;
198 
199         // this throws a ReadFailure.
200         elog<ReadFailure>(
201             xyz::openbmc_project::Sensor::Device::
202                 ReadFailure::CALLOUT_ERRNO(rc),
203             xyz::openbmc_project::Sensor::Device::
204                 ReadFailure::CALLOUT_DEVICE_PATH(
205                     fs::canonical(callOutPath).c_str()));
206     }
207 
208     return value;
209 }
210 
211 uint64_t writeSysfsWithCallout(const uint64_t& value,
212                                const std::string& root,
213                                const std::string& instance,
214                                const std::string& type,
215                                const std::string& id,
216                                const std::string& sensor)
217 {
218     namespace fs = std::experimental::filesystem;
219 
220     std::string valueStr = std::to_string(value);
221     std::ofstream ofs;
222     fs::path instancePath{root};
223     instancePath /= instance;
224     std::string fullPath = make_sysfs_path(instancePath,
225                                            type, id, sensor);
226 
227     ofs.exceptions(std::ofstream::failbit
228                    | std::ofstream::badbit
229                    | std::ofstream::eofbit);
230     try
231     {
232         ofs.open(fullPath);
233         ofs << valueStr;
234     }
235     catch (const std::exception& e)
236     {
237         // errno should still reflect the error from the failing open
238         // or write system calls that got us here.
239         auto rc = errno;
240         instancePath /= "device";
241         auto callOutPath = findCalloutPath(fs::canonical(instancePath));
242         using namespace sdbusplus::xyz::openbmc_project::Control::Device::Error;
243         report<WriteFailure>(
244             xyz::openbmc_project::Control::Device::
245                 WriteFailure::CALLOUT_ERRNO(rc),
246             xyz::openbmc_project::Control::Device::
247                 WriteFailure::CALLOUT_DEVICE_PATH(
248                     fs::canonical(callOutPath).c_str()));
249 
250         exit(EXIT_FAILURE);
251     }
252 
253     return value;
254 }
255 
256 }
257 // vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
258