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 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 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 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 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 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 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 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 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> 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