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