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 =
97 pmbusInterface->readString(fileName, 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 =
118 pmbusInterface->readString(fileName, 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 =
228 pmbusInterface->readString(fileName, 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