xref: /openbmc/phosphor-power/pmbus.cpp (revision af7321ebf1e68c842487782a366abcf683fcd292)
1 /**
2  * Copyright © 2017 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 "pmbus.hpp"
17 
18 #include <phosphor-logging/elog-errors.hpp>
19 #include <phosphor-logging/elog.hpp>
20 #include <phosphor-logging/lg2.hpp>
21 #include <xyz/openbmc_project/Common/Device/error.hpp>
22 #include <xyz/openbmc_project/Common/error.hpp>
23 
24 #include <filesystem>
25 #include <fstream>
26 
27 namespace phosphor
28 {
29 namespace pmbus
30 {
31 
32 using namespace phosphor::logging;
33 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
34 using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error;
35 namespace fs = std::filesystem;
36 
37 /**
38  * @brief Helper to close a file handle
39  */
40 struct FileCloser
41 {
42     void operator()(FILE* fp) const
43     {
44         fclose(fp);
45     }
46 };
47 
48 /**
49  * @brief Returns the canonical path if it exists
50  *        otherwise returns the path passed in.
51  *
52  * @param[in] path - The path to check
53  *
54  * @return fs::path - Either the canonical or original path
55  */
56 static fs::path tryCanonical(const fs::path& path)
57 {
58     std::error_code ec;
59     auto canonical = fs::canonical(path, ec);
60 
61     if (ec)
62     {
63         lg2::error("Could not get canonical path from {PATH}", "PATH", path);
64         return path;
65     }
66 
67     return canonical;
68 }
69 
70 std::string PMBus::insertPageNum(const std::string& templateName, size_t page)
71 {
72     auto name = templateName;
73 
74     // insert the page where the P was
75     auto pos = name.find('P');
76     if (pos != std::string::npos)
77     {
78         name.replace(pos, 1, std::to_string(page));
79     }
80 
81     return name;
82 }
83 
84 fs::path PMBus::getPath(Type type)
85 {
86     switch (type)
87     {
88         default:
89         /* fall through */
90         case Type::Base:
91             return basePath;
92             break;
93         case Type::Hwmon:
94             return basePath / "hwmon" / hwmonDir;
95             break;
96         case Type::Debug:
97             return debugPath / "pmbus" / hwmonDir;
98             break;
99         case Type::DeviceDebug:
100         {
101             auto dir = driverName + "." + std::to_string(instance);
102             return debugPath / dir;
103             break;
104         }
105         case Type::HwmonDeviceDebug:
106             return debugPath / "pmbus" / hwmonDir / getDeviceName();
107             break;
108     }
109 }
110 
111 std::string PMBus::getDeviceName()
112 {
113     std::string name;
114     std::ifstream file;
115     auto path = basePath / "name";
116 
117     file.exceptions(std::ifstream::failbit | std::ifstream::badbit |
118                     std::ifstream::eofbit);
119     try
120     {
121         file.open(path);
122         file >> name;
123     }
124     catch (const std::exception& e)
125     {
126         lg2::error("Unable to read PMBus device name PATH={PATH}", "PATH",
127                    path);
128     }
129 
130     return name;
131 }
132 
133 bool PMBus::readBitInPage(const std::string& name, size_t page, Type type)
134 {
135     auto pagedBit = insertPageNum(name, page);
136     return readBit(pagedBit, type);
137 }
138 
139 bool PMBus::readBit(const std::string& name, Type type)
140 {
141     unsigned long int value = 0;
142     std::ifstream file;
143     fs::path path = getPath(type);
144 
145     path /= name;
146 
147     file.exceptions(std::ifstream::failbit | std::ifstream::badbit |
148                     std::ifstream::eofbit);
149 
150     try
151     {
152         char* err = nullptr;
153         std::string val(1, '\0');
154 
155         file.open(path);
156         file.read(&val[0], 1);
157 
158         value = strtoul(val.c_str(), &err, 10);
159 
160         if (*err)
161         {
162             lg2::error(
163                 "Invalid character in sysfs file FILE={FILE} CONTENTS={CONTENTS}",
164                 "FILE", path, "CONTENTS", val);
165 
166             // Catch below and handle as a read failure
167             elog<InternalFailure>();
168         }
169     }
170     catch (const std::exception& e)
171     {
172         auto rc = errno;
173 
174         lg2::error(
175             "Failed to read sysfs file errno={ERRNO} FILENAME={FILENAME}",
176             "ERRNO", rc, "FILENAME", path);
177 
178         using metadata = xyz::openbmc_project::Common::Device::ReadFailure;
179 
180         elog<ReadFailure>(
181             metadata::CALLOUT_ERRNO(rc),
182             metadata::CALLOUT_DEVICE_PATH(tryCanonical(basePath).c_str()));
183     }
184 
185     return value != 0;
186 }
187 
188 bool PMBus::exists(const std::string& name, Type type)
189 {
190     auto path = getPath(type);
191     path /= name;
192     return fs::exists(path);
193 }
194 
195 uint64_t PMBus::read(const std::string& name, Type type, bool errTrace)
196 {
197     uint64_t data = 0;
198     std::ifstream file;
199     auto path = getPath(type);
200     path /= name;
201 
202     file.exceptions(std::ifstream::failbit | std::ifstream::badbit |
203                     std::ifstream::eofbit);
204 
205     try
206     {
207         file.open(path);
208         file >> std::hex >> data;
209     }
210     catch (const std::exception& e)
211     {
212         auto rc = errno;
213 
214         if (errTrace)
215         {
216             lg2::error(
217                 "Failed to read sysfs file errno={ERRNO} FILENAME={FILENAME}",
218                 "ERRNO", rc, "FILENAME", path);
219 
220             using metadata = xyz::openbmc_project::Common::Device::ReadFailure;
221 
222             elog<ReadFailure>(
223                 metadata::CALLOUT_ERRNO(rc),
224                 metadata::CALLOUT_DEVICE_PATH(tryCanonical(basePath).c_str()));
225         }
226         else
227         {
228             throw ReadFailure();
229         }
230     }
231 
232     return data;
233 }
234 
235 std::string PMBus::readString(const std::string& name, Type type)
236 {
237     std::string data;
238     std::ifstream file;
239     auto path = getPath(type);
240     path /= name;
241 
242     file.exceptions(std::ifstream::failbit | std::ifstream::badbit |
243                     std::ifstream::eofbit);
244 
245     try
246     {
247         file.open(path);
248         file >> data;
249     }
250     catch (const std::exception& e)
251     {
252         auto rc = errno;
253         lg2::error(
254             "Failed to read sysfs file errno={ERRNO} FILENAME={FILENAME}",
255             "ERRNO", rc, "FILENAME", path);
256 
257         using metadata = xyz::openbmc_project::Common::Device::ReadFailure;
258 
259         elog<ReadFailure>(
260             metadata::CALLOUT_ERRNO(rc),
261             metadata::CALLOUT_DEVICE_PATH(tryCanonical(basePath).c_str()));
262     }
263 
264     return data;
265 }
266 
267 std::vector<uint8_t> PMBus::readBinary(const std::string& name, Type type,
268                                        size_t length)
269 {
270     auto path = getPath(type) / name;
271 
272     // Use C style IO because it's easier to handle telling the difference
273     // between hitting EOF or getting an actual error.
274     std::unique_ptr<FILE, FileCloser> file{fopen(path.c_str(), "rb")};
275 
276     if (file)
277     {
278         std::vector<uint8_t> data(length, 0);
279 
280         auto bytes =
281             fread(data.data(), sizeof(decltype(data[0])), length, file.get());
282 
283         if (bytes != length)
284         {
285             // If hit EOF, just return the amount of data that was read.
286             if (feof(file.get()))
287             {
288                 data.erase(data.begin() + bytes, data.end());
289             }
290             else if (ferror(file.get()))
291             {
292                 auto rc = errno;
293                 lg2::error(
294                     "Failed to read sysfs file errno={ERRNO} FILENAME={FILENAME}",
295                     "ERRNO", rc, "FILENAME", path);
296                 using metadata =
297                     xyz::openbmc_project::Common::Device::ReadFailure;
298 
299                 elog<ReadFailure>(metadata::CALLOUT_ERRNO(rc),
300                                   metadata::CALLOUT_DEVICE_PATH(
301                                       tryCanonical(basePath).c_str()));
302             }
303         }
304         return data;
305     }
306 
307     return std::vector<uint8_t>{};
308 }
309 
310 void PMBus::write(const std::string& name, int value, Type type)
311 {
312     std::ofstream file;
313     fs::path path = getPath(type);
314 
315     path /= name;
316 
317     file.exceptions(std::ofstream::failbit | std::ofstream::badbit |
318                     std::ofstream::eofbit);
319 
320     try
321     {
322         file.open(path);
323         file << value;
324     }
325     catch (const std::exception& e)
326     {
327         auto rc = errno;
328         lg2::error(
329             "Failed to write sysfs file errno={ERRNO} FILENAME={FILENAME}",
330             "ERRNO", rc, "FILENAME", path);
331 
332         using metadata = xyz::openbmc_project::Common::Device::WriteFailure;
333 
334         elog<WriteFailure>(
335             metadata::CALLOUT_ERRNO(rc),
336             metadata::CALLOUT_DEVICE_PATH(tryCanonical(basePath).c_str()));
337     }
338 }
339 
340 void PMBus::writeBinary(const std::string& name, std::vector<uint8_t> data,
341                         Type type)
342 {
343     std::ofstream file;
344     fs::path path = getPath(type);
345 
346     path /= name;
347 
348     file.exceptions(std::ofstream::failbit | std::ofstream::badbit |
349                     std::ofstream::eofbit);
350 
351     try
352     {
353         // I need to specify binary mode when I construct the ofstream
354         file.open(path, std::ios::out | std::ios_base::binary);
355         lg2::debug("Write data to sysfs file FILENAME={FILENAME}", "FILENAME",
356                    path);
357         file.write(reinterpret_cast<const char*>(&data[0]), data.size());
358     }
359     catch (const std::exception& e)
360     {
361         auto rc = errno;
362         lg2::error(
363             "Failed to write binary data to sysfs file errno={ERRNO} FILENAME={FILENAME}",
364             "ERRNO", rc, "FILENAME", path);
365 
366         using metadata = xyz::openbmc_project::Common::Device::WriteFailure;
367 
368         elog<WriteFailure>(
369             metadata::CALLOUT_ERRNO(rc),
370             metadata::CALLOUT_DEVICE_PATH(tryCanonical(basePath).c_str()));
371     }
372 }
373 
374 void PMBus::findHwmonDir()
375 {
376     fs::path path{basePath};
377     path /= "hwmon";
378 
379     // Make sure the directory exists, otherwise for things that can be
380     // dynamically present or not present an exception will be thrown if the
381     // hwmon directory is not there, resulting in a program termination.
382     if (fs::is_directory(path))
383     {
384         // look for <basePath>/hwmon/hwmonN/
385         for (auto& f : fs::directory_iterator(path))
386         {
387             if ((f.path().filename().string().find("hwmon") !=
388                  std::string::npos) &&
389                 (fs::is_directory(f.path())))
390             {
391                 hwmonDir = f.path().filename();
392                 break;
393             }
394         }
395     }
396 
397     // Don't really want to crash here, just log it
398     // and let accesses fail later
399     if (hwmonDir.empty())
400     {
401         lg2::info("Unable to find hwmon directory in device base path "
402                   "DEVICE_PATH={DEVICE_PATH}",
403                   "DEVICE_PATH", basePath);
404     }
405 }
406 
407 std::unique_ptr<PMBusBase> PMBus::createPMBus(std::uint8_t bus,
408                                               const std::string& address)
409 {
410     const std::string physpath = {
411         "/sys/bus/i2c/devices/" + std::to_string(bus) + "-" + address};
412     auto interface = std::make_unique<PMBus>(physpath);
413 
414     return interface;
415 }
416 
417 std::unique_ptr<PMBusBase> createPMBus(std::uint8_t bus,
418                                        const std::string& address)
419 {
420     return PMBus::createPMBus(bus, address);
421 }
422 
423 } // namespace pmbus
424 } // namespace phosphor
425