xref: /openbmc/phosphor-power/pmbus.cpp (revision 415094c1)
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 (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 (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)
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 (std::exception& e)
193     {
194         auto rc = errno;
195         log<level::ERR>((std::string("Failed to read sysfs file "
196                                      "errno=") +
197                          std::to_string(rc) + " FILENAME=" + path.string())
198                             .c_str());
199 
200         using metadata = xyz::openbmc_project::Common::Device::ReadFailure;
201 
202         elog<ReadFailure>(
203             metadata::CALLOUT_ERRNO(rc),
204             metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str()));
205     }
206 
207     return data;
208 }
209 
210 std::string PMBus::readString(const std::string& name, Type type)
211 {
212     std::string data;
213     std::ifstream file;
214     auto path = getPath(type);
215     path /= name;
216 
217     file.exceptions(std::ifstream::failbit | std::ifstream::badbit |
218                     std::ifstream::eofbit);
219 
220     try
221     {
222         file.open(path);
223         file >> data;
224     }
225     catch (std::exception& e)
226     {
227         auto rc = errno;
228         log<level::ERR>((std::string("Failed to read sysfs file "
229                                      "errno=") +
230                          std::to_string(rc) + " FILENAME=" + path.string())
231                             .c_str());
232 
233         using metadata = xyz::openbmc_project::Common::Device::ReadFailure;
234 
235         elog<ReadFailure>(
236             metadata::CALLOUT_ERRNO(rc),
237             metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str()));
238     }
239 
240     return data;
241 }
242 
243 std::vector<uint8_t> PMBus::readBinary(const std::string& name, Type type,
244                                        size_t length)
245 {
246     auto path = getPath(type) / name;
247 
248     // Use C style IO because it's easier to handle telling the difference
249     // between hitting EOF or getting an actual error.
250     std::unique_ptr<FILE, FileCloser> file{fopen(path.c_str(), "rb")};
251 
252     if (file)
253     {
254         std::vector<uint8_t> data(length, 0);
255 
256         auto bytes =
257             fread(data.data(), sizeof(decltype(data[0])), length, file.get());
258 
259         if (bytes != length)
260         {
261             // If hit EOF, just return the amount of data that was read.
262             if (feof(file.get()))
263             {
264                 data.erase(data.begin() + bytes, data.end());
265             }
266             else if (ferror(file.get()))
267             {
268                 auto rc = errno;
269                 log<level::ERR>((std::string("Failed to read sysfs file "
270                                              "errno=") +
271                                  std::to_string(rc) +
272                                  " FILENAME=" + path.string())
273                                     .c_str());
274                 using metadata =
275                     xyz::openbmc_project::Common::Device::ReadFailure;
276 
277                 elog<ReadFailure>(metadata::CALLOUT_ERRNO(rc),
278                                   metadata::CALLOUT_DEVICE_PATH(
279                                       fs::canonical(basePath).c_str()));
280             }
281         }
282         return data;
283     }
284 
285     return std::vector<uint8_t>{};
286 }
287 
288 void PMBus::write(const std::string& name, int value, Type type)
289 {
290     std::ofstream file;
291     fs::path path = getPath(type);
292 
293     path /= name;
294 
295     file.exceptions(std::ofstream::failbit | std::ofstream::badbit |
296                     std::ofstream::eofbit);
297 
298     try
299     {
300         file.open(path);
301         file << value;
302     }
303     catch (const std::exception& e)
304     {
305         auto rc = errno;
306         log<level::ERR>((std::string("Failed to write sysfs file "
307                                      "errno=") +
308                          std::to_string(rc) + " FILENAME=" + path.string())
309                             .c_str());
310 
311         using metadata = xyz::openbmc_project::Common::Device::WriteFailure;
312 
313         elog<WriteFailure>(
314             metadata::CALLOUT_ERRNO(rc),
315             metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str()));
316     }
317 }
318 
319 void PMBus::writeBinary(const std::string& name, std::vector<uint8_t> data,
320                         Type type)
321 {
322     std::ofstream file;
323     fs::path path = getPath(type);
324 
325     path /= name;
326 
327     file.exceptions(std::ofstream::failbit | std::ofstream::badbit |
328                     std::ofstream::eofbit);
329 
330     try
331     {
332         // I need to specify binary mode when I construct the ofstream
333         file.open(path, std::ios::out | std::ios_base::binary);
334         log<level::DEBUG>(std::string("Write data to sysfs file "
335                                       "FILENAME=" +
336                                       path.string())
337                               .c_str());
338         file.write(reinterpret_cast<const char*>(&data[0]), data.size());
339     }
340     catch (const std::exception& e)
341     {
342         auto rc = errno;
343         log<level::ERR>(
344             (std::string("Failed to write binary data to sysfs file "
345                          "errno=") +
346              std::to_string(rc) + " FILENAME=" + path.string())
347                 .c_str());
348 
349         using metadata = xyz::openbmc_project::Common::Device::WriteFailure;
350 
351         elog<WriteFailure>(
352             metadata::CALLOUT_ERRNO(rc),
353             metadata::CALLOUT_DEVICE_PATH(fs::canonical(basePath).c_str()));
354     }
355 }
356 
357 void PMBus::findHwmonDir()
358 {
359     fs::path path{basePath};
360     path /= "hwmon";
361 
362     // Make sure the directory exists, otherwise for things that can be
363     // dynamically present or not present an exception will be thrown if the
364     // hwmon directory is not there, resulting in a program termination.
365     if (fs::is_directory(path))
366     {
367         // look for <basePath>/hwmon/hwmonN/
368         for (auto& f : fs::directory_iterator(path))
369         {
370             if ((f.path().filename().string().find("hwmon") !=
371                  std::string::npos) &&
372                 (fs::is_directory(f.path())))
373             {
374                 hwmonDir = f.path().filename();
375                 break;
376             }
377         }
378     }
379 
380     // Don't really want to crash here, just log it
381     // and let accesses fail later
382     if (hwmonDir.empty())
383     {
384         log<level::INFO>(std::string("Unable to find hwmon directory "
385                                      "in device base path"
386                                      " DEVICE_PATH=" +
387                                      basePath.string())
388                              .c_str());
389     }
390 }
391 
392 std::unique_ptr<PMBusBase> PMBus::createPMBus(std::uint8_t bus,
393                                               const std::string& address)
394 {
395     const std::string physpath = {"/sys/bus/i2c/devices/" +
396                                   std::to_string(bus) + "-" + address};
397     auto interface = std::make_unique<PMBus>(physpath);
398 
399     return interface;
400 }
401 
402 std::unique_ptr<PMBusBase> createPMBus(std::uint8_t bus,
403                                        const std::string& address)
404 {
405     return PMBus::createPMBus(bus, address);
406 }
407 
408 } // namespace pmbus
409 } // namespace phosphor
410