1 /**
2  * Copyright © 2024 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 
17 #include "pmbus_driver_device.hpp"
18 
19 #include <ctype.h> // for tolower()
20 
21 #include <algorithm>
22 #include <exception>
23 #include <filesystem>
24 #include <format>
25 #include <regex>
26 #include <stdexcept>
27 
28 namespace phosphor::power::sequencer
29 {
30 
31 using namespace pmbus;
32 namespace fs = std::filesystem;
33 
getGPIOValues(Services & services)34 std::vector<int> PMBusDriverDevice::getGPIOValues(Services& services)
35 {
36     // Get lower case version of device name to use as chip label
37     std::string label{name};
38     std::transform(label.begin(), label.end(), label.begin(), ::tolower);
39 
40     // Read the GPIO values by specifying the chip label
41     std::vector<int> values;
42     try
43     {
44         values = services.getGPIOValues(label);
45     }
46     catch (const std::exception& e)
47     {
48         throw std::runtime_error{std::format(
49             "Unable to read GPIO values from device {} using label {}: {}",
50             name, label, e.what())};
51     }
52     return values;
53 }
54 
getStatusWord(uint8_t page)55 uint16_t PMBusDriverDevice::getStatusWord(uint8_t page)
56 {
57     uint16_t value{0};
58     try
59     {
60         std::string fileName = std::format("status{:d}", page);
61         value = pmbusInterface->read(fileName, Type::Debug);
62     }
63     catch (const std::exception& e)
64     {
65         throw std::runtime_error{std::format(
66             "Unable to read STATUS_WORD for PAGE {:d} of device {}: {}", page,
67             name, e.what())};
68     }
69     return value;
70 }
71 
getStatusVout(uint8_t page)72 uint8_t PMBusDriverDevice::getStatusVout(uint8_t page)
73 {
74     uint8_t value{0};
75     try
76     {
77         std::string fileName = std::format("status{:d}_vout", page);
78         value = pmbusInterface->read(fileName, Type::Debug);
79     }
80     catch (const std::exception& e)
81     {
82         throw std::runtime_error{std::format(
83             "Unable to read STATUS_VOUT for PAGE {:d} of device {}: {}", page,
84             name, e.what())};
85     }
86     return value;
87 }
88 
getReadVout(uint8_t page)89 double PMBusDriverDevice::getReadVout(uint8_t page)
90 {
91     double volts{0.0};
92     try
93     {
94         unsigned int fileNumber = getFileNumber(page);
95         std::string fileName = std::format("in{}_input", fileNumber);
96         std::string millivoltsStr = pmbusInterface->readString(fileName,
97                                                                Type::Hwmon);
98         unsigned long millivolts = std::stoul(millivoltsStr);
99         volts = millivolts / 1000.0;
100     }
101     catch (const std::exception& e)
102     {
103         throw std::runtime_error{std::format(
104             "Unable to read READ_VOUT for PAGE {:d} of device {}: {}", page,
105             name, e.what())};
106     }
107     return volts;
108 }
109 
getVoutUVFaultLimit(uint8_t page)110 double PMBusDriverDevice::getVoutUVFaultLimit(uint8_t page)
111 {
112     double volts{0.0};
113     try
114     {
115         unsigned int fileNumber = getFileNumber(page);
116         std::string fileName = std::format("in{}_lcrit", fileNumber);
117         std::string millivoltsStr = pmbusInterface->readString(fileName,
118                                                                Type::Hwmon);
119         unsigned long millivolts = std::stoul(millivoltsStr);
120         volts = millivolts / 1000.0;
121     }
122     catch (const std::exception& e)
123     {
124         throw std::runtime_error{std::format(
125             "Unable to read VOUT_UV_FAULT_LIMIT for PAGE {:d} of device {}: {}",
126             page, name, e.what())};
127     }
128     return volts;
129 }
130 
getFileNumber(uint8_t page)131 unsigned int PMBusDriverDevice::getFileNumber(uint8_t page)
132 {
133     if (pageToFileNumber.empty())
134     {
135         buildPageToFileNumberMap();
136     }
137 
138     auto it = pageToFileNumber.find(page);
139     if (it == pageToFileNumber.end())
140     {
141         throw std::runtime_error{std::format(
142             "Unable to find hwmon file number for PAGE {:d} of device {}", page,
143             name)};
144     }
145 
146     return it->second;
147 }
148 
buildPageToFileNumberMap()149 void PMBusDriverDevice::buildPageToFileNumberMap()
150 {
151     // Clear any existing mappings
152     pageToFileNumber.clear();
153 
154     // Build mappings using voltage label files in hwmon directory
155     try
156     {
157         fs::path hwmonDir = pmbusInterface->getPath(Type::Hwmon);
158         if (fs::is_directory(hwmonDir))
159         {
160             // Loop through all files in hwmon directory
161             std::string fileName;
162             unsigned int fileNumber;
163             std::optional<uint8_t> page;
164             for (const auto& f : fs::directory_iterator{hwmonDir})
165             {
166                 // If this is a voltage label file
167                 fileName = f.path().filename().string();
168                 if (isLabelFile(fileName, fileNumber))
169                 {
170                     // Read PMBus PAGE number from label file contents
171                     page = readPageFromLabelFile(fileName);
172                     if (page)
173                     {
174                         // Add mapping from PAGE number to file number
175                         pageToFileNumber.emplace(*page, fileNumber);
176                     }
177                 }
178             }
179         }
180     }
181     catch (const std::exception& e)
182     {
183         throw std::runtime_error{
184             std::format("Unable to map PMBus PAGE numbers to hwmon file "
185                         "numbers for device {}: {}",
186                         name, e.what())};
187     }
188 }
189 
isLabelFile(const std::string & fileName,unsigned int & fileNumber)190 bool PMBusDriverDevice::isLabelFile(const std::string& fileName,
191                                     unsigned int& fileNumber)
192 {
193     bool isLabel{false};
194     try
195     {
196         // Check if file name has expected pattern for voltage label file
197         std::regex regex{"in(\\d+)_label"};
198         std::smatch results;
199         if (std::regex_match(fileName, results, regex))
200         {
201             // Verify 2 match results: entire match and one sub-match
202             if (results.size() == 2)
203             {
204                 // Get sub-match that contains the file number
205                 std::string fileNumberStr = results.str(1);
206                 fileNumber = std::stoul(fileNumberStr);
207                 isLabel = true;
208             }
209         }
210     }
211     catch (...)
212     {
213         // Ignore error.  If this file is needed for pgood fault detection, an
214         // error will occur later when the necessary mapping is missing.  Avoid
215         // logging unnecessary errors for files that may not be required.
216     }
217     return isLabel;
218 }
219 
220 std::optional<uint8_t>
readPageFromLabelFile(const std::string & fileName)221     PMBusDriverDevice::readPageFromLabelFile(const std::string& fileName)
222 {
223     std::optional<uint8_t> page;
224     try
225     {
226         // Read voltage label file contents
227         std::string contents = pmbusInterface->readString(fileName,
228                                                           Type::Hwmon);
229 
230         // Check if file contents match the expected pattern
231         std::regex regex{"vout(\\d+)"};
232         std::smatch results;
233         if (std::regex_match(contents, results, regex))
234         {
235             // Verify 2 match results: entire match and one sub-match
236             if (results.size() == 2)
237             {
238                 // Get sub-match that contains the page number + 1
239                 std::string pageStr = results.str(1);
240                 page = std::stoul(pageStr) - 1;
241             }
242         }
243     }
244     catch (...)
245     {
246         // Ignore error.  If this file is needed for pgood fault detection, an
247         // error will occur later when the necessary mapping is missing.  Avoid
248         // logging unnecessary errors for files that may not be required.
249     }
250     return page;
251 }
252 
253 } // namespace phosphor::power::sequencer
254