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 <experimental/filesystem>
17 #include <fstream>
18 #include <phosphor-logging/elog.hpp>
19 #include <phosphor-logging/elog-errors.hpp>
20 #include <xyz/openbmc_project/Common/error.hpp>
21 #include <xyz/openbmc_project/Common/Device/error.hpp>
22 #include "pmbus.hpp"
23 
24 namespace witherspoon
25 {
26 namespace pmbus
27 {
28 
29 using namespace phosphor::logging;
30 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
31 using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error;
32 namespace fs = std::experimental::filesystem;
33 
34 /**
35  * @brief Helper to close a file handle
36  */
37 struct FileCloser
38 {
39     void operator()(FILE* fp) const
40     {
41         fclose(fp);
42     }
43 };
44 
45 std::string PMBus::insertPageNum(const std::string& templateName,
46                                  size_t page)
47 {
48     auto name = templateName;
49 
50     //insert the page where the P was
51     auto pos = name.find('P');
52     if (pos != std::string::npos)
53     {
54         name.replace(pos, 1, std::to_string(page));
55     }
56 
57     return name;
58 }
59 
60 fs::path PMBus::getPath(Type type)
61 {
62     switch (type)
63     {
64         default:
65         /* fall through */
66         case Type::Base:
67             return basePath;
68             break;
69         case Type::Hwmon:
70             return basePath / "hwmon" / hwmonDir;
71             break;
72         case Type::Debug:
73             return debugPath / "pmbus" / hwmonDir;
74             break;
75         case Type::DeviceDebug:
76         {
77             auto dir = driverName + "." + std::to_string(instance);
78             return debugPath / dir;
79             break;
80         }
81         case Type::HwmonDeviceDebug:
82             return debugPath / "pmbus" / hwmonDir / getDeviceName();
83             break;
84     }
85 }
86 
87 std::string PMBus::getDeviceName()
88 {
89     std::string name;
90     std::ifstream file;
91     auto path = basePath / "name";
92 
93     file.exceptions(std::ifstream::failbit |
94                     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>("Unable to read PMBus device name",
104                         entry("PATH=%s", path.c_str()));
105     }
106 
107     return name;
108 }
109 
110 bool PMBus::readBitInPage(const std::string& name,
111                           size_t page,
112                           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 |
127                     std::ifstream::badbit |
128                     std::ifstream::eofbit);
129 
130     try
131     {
132         char* err = NULL;
133         std::string val{1, '\0'};
134 
135         file.open(path);
136         file.read(&val[0], 1);
137 
138         value = strtoul(val.c_str(), &err, 10);
139 
140         if (*err)
141         {
142             log<level::ERR>("Invalid character in sysfs file",
143                             entry("FILE=%s", path.c_str()),
144                             entry("CONTENTS=%s", val.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>("Failed to read sysfs file",
155                         entry("FILENAME=%s", path.c_str()));
156 
157         using metadata = xyz::openbmc_project::Common::Device::ReadFailure;
158 
159         elog<ReadFailure>(metadata::CALLOUT_ERRNO(rc),
160                           metadata::CALLOUT_DEVICE_PATH(
161                                   fs::canonical(basePath).c_str()));
162     }
163 
164     return value != 0;
165 }
166 
167 bool PMBus::exists(const std::string& name, Type type)
168 {
169     auto path = getPath(type);
170     path /= name;
171     return fs::exists(path);
172 }
173 
174 uint64_t PMBus::read(const std::string& name, Type type)
175 {
176     uint64_t data = 0;
177     std::ifstream file;
178     auto path = getPath(type);
179     path /= name;
180 
181     file.exceptions(std::ifstream::failbit |
182                     std::ifstream::badbit |
183                     std::ifstream::eofbit);
184 
185     try
186     {
187         file.open(path);
188         file >> std::hex >> data;
189     }
190     catch (std::exception& e)
191     {
192         auto rc = errno;
193         log<level::ERR>("Failed to read sysfs file",
194                         entry("FILENAME=%s", path.c_str()));
195 
196         using metadata = xyz::openbmc_project::Common::Device::ReadFailure;
197 
198         elog<ReadFailure>(metadata::CALLOUT_ERRNO(rc),
199                           metadata::CALLOUT_DEVICE_PATH(
200                                   fs::canonical(basePath).c_str()));
201     }
202 
203     return data;
204 }
205 
206 std::string PMBus::readString(const std::string& name, Type type)
207 {
208     std::string data;
209     std::ifstream file;
210     auto path = getPath(type);
211     path /= name;
212 
213     file.exceptions(std::ifstream::failbit |
214                     std::ifstream::badbit |
215                     std::ifstream::eofbit);
216 
217     try
218     {
219         file.open(path);
220         file >> data;
221     }
222     catch (std::exception& e)
223     {
224         auto rc = errno;
225         log<level::ERR>("Failed to read sysfs file",
226                         entry("FILENAME=%s", path.c_str()));
227 
228         using metadata = xyz::openbmc_project::Common::Device::ReadFailure;
229 
230         elog<ReadFailure>(metadata::CALLOUT_ERRNO(rc),
231                           metadata::CALLOUT_DEVICE_PATH(
232                                   fs::canonical(basePath).c_str()));
233     }
234 
235     return data;
236 }
237 
238 std::vector<uint8_t> PMBus::readBinary(const std::string& name,
239                                        Type type,
240                                        size_t length)
241 {
242     auto path = getPath(type) / name;
243 
244     //Use C style IO because it's easier to handle telling the difference
245     //between hitting EOF or getting an actual error.
246     std::unique_ptr<FILE, FileCloser> file{fopen(path.c_str(), "rb")};
247 
248     if (file)
249     {
250         std::vector<uint8_t> data(length, 0);
251 
252         auto bytes = fread(data.data(),
253                            sizeof(decltype(data[0])),
254                            length,
255                            file.get());
256 
257         if (bytes != length)
258         {
259             //If hit EOF, just return the amount of data that was read.
260             if (feof(file.get()))
261             {
262                data.erase(data.begin() + bytes, data.end());
263             }
264             else if (ferror(file.get()))
265             {
266                 auto rc = errno;
267                 log<level::ERR>("Failed to read sysfs file",
268                                 entry("FILENAME=%s", path.c_str()));
269 
270                 using metadata = xyz::openbmc_project::Common::
271                         Device::ReadFailure;
272 
273                 elog<ReadFailure>(metadata::CALLOUT_ERRNO(rc),
274                                   metadata::CALLOUT_DEVICE_PATH(
275                                           fs::canonical(basePath).c_str()));
276             }
277         }
278         return data;
279     }
280 
281     return std::vector<uint8_t>{};
282 }
283 
284 void PMBus::write(const std::string& name, int value, Type type)
285 {
286     std::ofstream file;
287     fs::path path = getPath(type);
288 
289     path /= name;
290 
291     file.exceptions(std::ofstream::failbit |
292                     std::ofstream::badbit |
293                     std::ofstream::eofbit);
294 
295     try
296     {
297         file.open(path);
298         file << value;
299     }
300     catch (const std::exception& e)
301     {
302         auto rc = errno;
303 
304         log<level::ERR>("Failed to write sysfs file",
305                         entry("FILENAME=%s", path.c_str()));
306 
307         using metadata = xyz::openbmc_project::Common::Device::WriteFailure;
308 
309         elog<WriteFailure>(metadata::CALLOUT_ERRNO(rc),
310                            metadata::CALLOUT_DEVICE_PATH(
311                                    fs::canonical(basePath).c_str()));
312     }
313 }
314 
315 void PMBus::findHwmonDir()
316 {
317     fs::path path{basePath};
318     path /= "hwmon";
319 
320     // Make sure the directory exists, otherwise for things that can be
321     // dynamically present or not present an exception will be thrown if the
322     // hwmon directory is not there, resulting in a program termination.
323     if (fs::is_directory(path))
324     {
325         //look for <basePath>/hwmon/hwmonN/
326         for (auto& f : fs::directory_iterator(path))
327         {
328             if ((f.path().filename().string().find("hwmon") !=
329                  std::string::npos) &&
330                 (fs::is_directory(f.path())))
331             {
332                 hwmonDir = f.path().filename();
333                 break;
334             }
335         }
336     }
337 
338     //Don't really want to crash here, just log it
339     //and let accesses fail later
340     if (hwmonDir.empty())
341     {
342         log<level::INFO>("Unable to find hwmon directory "
343                          "in device base path",
344                          entry("DEVICE_PATH=%s", basePath.c_str()));
345     }
346 
347 }
348 
349 }
350 }
351