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