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 "rail.hpp" 18 19 #include "pmbus.hpp" 20 21 #include <exception> 22 #include <format> 23 24 namespace phosphor::power::sequencer 25 { 26 namespace status_vout = phosphor::pmbus::status_vout; 27 28 bool Rail::isPresent(Services& services) 29 { 30 // Initially assume rail is present 31 bool present{true}; 32 33 // If presence data member contains an inventory path to check 34 if (presence) 35 { 36 const std::string& inventoryPath = *presence; 37 try 38 { 39 present = services.isPresent(inventoryPath); 40 } 41 catch (const std::exception& e) 42 { 43 throw std::runtime_error{std::format( 44 "Unable to determine presence of rail {} using inventory path {}: {}", 45 name, inventoryPath, e.what())}; 46 } 47 } 48 49 return present; 50 } 51 52 uint16_t Rail::getStatusWord(PowerSequencerDevice& device) 53 { 54 uint16_t value{0}; 55 try 56 { 57 verifyHasPage(); 58 value = device.getStatusWord(*page); 59 } 60 catch (const std::exception& e) 61 { 62 throw std::runtime_error{ 63 std::format("Unable to read STATUS_WORD value for rail {}: {}", 64 name, e.what())}; 65 } 66 return value; 67 } 68 69 uint8_t Rail::getStatusVout(PowerSequencerDevice& device) 70 { 71 uint8_t value{0}; 72 try 73 { 74 verifyHasPage(); 75 value = device.getStatusVout(*page); 76 } 77 catch (const std::exception& e) 78 { 79 throw std::runtime_error{ 80 std::format("Unable to read STATUS_VOUT value for rail {}: {}", 81 name, e.what())}; 82 } 83 return value; 84 } 85 86 double Rail::getReadVout(PowerSequencerDevice& device) 87 { 88 double value{0.0}; 89 try 90 { 91 verifyHasPage(); 92 value = device.getReadVout(*page); 93 } 94 catch (const std::exception& e) 95 { 96 throw std::runtime_error{std::format( 97 "Unable to read READ_VOUT value for rail {}: {}", name, e.what())}; 98 } 99 return value; 100 } 101 102 double Rail::getVoutUVFaultLimit(PowerSequencerDevice& device) 103 { 104 double value{0.0}; 105 try 106 { 107 verifyHasPage(); 108 value = device.getVoutUVFaultLimit(*page); 109 } 110 catch (const std::exception& e) 111 { 112 throw std::runtime_error{std::format( 113 "Unable to read VOUT_UV_FAULT_LIMIT value for rail {}: {}", name, 114 e.what())}; 115 } 116 return value; 117 } 118 119 bool Rail::hasPgoodFault(PowerSequencerDevice& device, Services& services, 120 const std::vector<int>& gpioValues, 121 std::map<std::string, std::string>& additionalData) 122 { 123 // If rail is not present, return false and don't check anything else 124 if (!isPresent(services)) 125 { 126 services.logInfoMsg(std::format("Rail {} is not present", name)); 127 return false; 128 } 129 130 // Check if STATUS_VOUT indicates a pgood fault occurred 131 bool hasFault = hasPgoodFaultStatusVout(device, services, additionalData); 132 133 // Check if a GPIO value indicates a pgood fault occurred 134 if (!hasFault) 135 { 136 hasFault = hasPgoodFaultGPIO(services, gpioValues, additionalData); 137 } 138 139 // Check if output voltage is below UV limit indicating pgood fault occurred 140 if (!hasFault) 141 { 142 hasFault = hasPgoodFaultOutputVoltage(device, services, additionalData); 143 } 144 145 // If fault detected, store debug data in additional data map 146 if (hasFault) 147 { 148 services.logErrorMsg( 149 std::format("Pgood fault detected in rail {}", name)); 150 storePgoodFaultDebugData(device, services, additionalData); 151 } 152 153 return hasFault; 154 } 155 156 void Rail::verifyHasPage() 157 { 158 if (!page) 159 { 160 throw std::runtime_error{ 161 std::format("No PAGE number defined for rail {}", name)}; 162 } 163 } 164 165 bool Rail::hasPgoodFaultStatusVout( 166 PowerSequencerDevice& device, Services& services, 167 std::map<std::string, std::string>& additionalData) 168 { 169 bool hasFault{false}; 170 171 // If we are checking the value of STATUS_VOUT for the rail 172 if (checkStatusVout) 173 { 174 // Read STATUS_VOUT value from device 175 uint8_t statusVout = getStatusVout(device); 176 177 // Check if fault (non-warning) bits are set in value 178 if (statusVout & ~status_vout::WARNING_MASK) 179 { 180 hasFault = true; 181 services.logErrorMsg(std::format( 182 "Rail {} has fault bits set in STATUS_VOUT: {:#04x}", name, 183 statusVout)); 184 additionalData.emplace("STATUS_VOUT", 185 std::format("{:#04x}", statusVout)); 186 } 187 else if (statusVout != 0) 188 { 189 services.logInfoMsg(std::format( 190 "Rail {} has warning bits set in STATUS_VOUT: {:#04x}", name, 191 statusVout)); 192 } 193 } 194 195 return hasFault; 196 } 197 198 bool Rail::hasPgoodFaultGPIO(Services& services, 199 const std::vector<int>& gpioValues, 200 std::map<std::string, std::string>& additionalData) 201 { 202 bool hasFault{false}; 203 204 // If a GPIO is defined for checking pgood status 205 if (gpio) 206 { 207 // Get GPIO value 208 unsigned int line = gpio->line; 209 bool activeLow = gpio->activeLow; 210 if (line >= gpioValues.size()) 211 { 212 throw std::runtime_error{std::format( 213 "Invalid GPIO line offset {} for rail {}: Device only has {} GPIO values", 214 line, name, gpioValues.size())}; 215 } 216 int value = gpioValues[line]; 217 218 // Check if value indicates pgood signal is not active 219 if ((activeLow && (value == 1)) || (!activeLow && (value == 0))) 220 { 221 hasFault = true; 222 services.logErrorMsg(std::format( 223 "Rail {} pgood GPIO line offset {} has inactive value {}", name, 224 line, value)); 225 additionalData.emplace("GPIO_LINE", std::format("{}", line)); 226 additionalData.emplace("GPIO_VALUE", std::format("{}", value)); 227 } 228 } 229 230 return hasFault; 231 } 232 233 bool Rail::hasPgoodFaultOutputVoltage( 234 PowerSequencerDevice& device, Services& services, 235 std::map<std::string, std::string>& additionalData) 236 { 237 bool hasFault{false}; 238 239 // If we are comparing output voltage to UV limit to check pgood status 240 if (compareVoltageToLimit) 241 { 242 // Read output voltage and UV fault limit values from device 243 double vout = getReadVout(device); 244 double uvLimit = getVoutUVFaultLimit(device); 245 246 // If output voltage is at or below UV fault limit 247 if (vout <= uvLimit) 248 { 249 hasFault = true; 250 services.logErrorMsg(std::format( 251 "Rail {} output voltage {}V is <= UV fault limit {}V", name, 252 vout, uvLimit)); 253 additionalData.emplace("READ_VOUT", std::format("{}", vout)); 254 additionalData.emplace("VOUT_UV_FAULT_LIMIT", 255 std::format("{}", uvLimit)); 256 } 257 } 258 259 return hasFault; 260 } 261 262 void Rail::storePgoodFaultDebugData( 263 PowerSequencerDevice& device, Services& services, 264 std::map<std::string, std::string>& additionalData) 265 { 266 additionalData.emplace("RAIL_NAME", name); 267 if (page) 268 { 269 try 270 { 271 uint16_t statusWord = getStatusWord(device); 272 services.logInfoMsg( 273 std::format("Rail {} STATUS_WORD: {:#06x}", name, statusWord)); 274 additionalData.emplace("STATUS_WORD", 275 std::format("{:#06x}", statusWord)); 276 } 277 catch (...) 278 { 279 // Ignore error; don't interrupt pgood fault handling 280 } 281 } 282 } 283 284 } // namespace phosphor::power::sequencer 285