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 <phosphor-logging/log.hpp> 17 #include <phosphor-logging/elog.hpp> 18 #include <xyz/openbmc_project/Sensor/Device/error.hpp> 19 #include <xyz/openbmc_project/Control/Device/error.hpp> 20 #include <xyz/openbmc_project/Power/Fault/error.hpp> 21 #include "elog-errors.hpp" 22 #include "names_values.hpp" 23 #include "power_supply.hpp" 24 #include "pmbus.hpp" 25 #include "utility.hpp" 26 27 using namespace phosphor::logging; 28 using namespace sdbusplus::xyz::openbmc_project::Control::Device::Error; 29 using namespace sdbusplus::xyz::openbmc_project::Sensor::Device::Error; 30 using namespace sdbusplus::xyz::openbmc_project::Power::Fault::Error; 31 32 namespace witherspoon 33 { 34 namespace power 35 { 36 namespace psu 37 { 38 39 constexpr auto INVENTORY_OBJ_PATH = "/xyz/openbmc_project/inventory"; 40 constexpr auto INVENTORY_INTERFACE = "xyz.openbmc_project.Inventory.Item"; 41 constexpr auto PRESENT_PROP = "Present"; 42 constexpr auto POWER_OBJ_PATH = "/org/openbmc/control/power0"; 43 constexpr auto POWER_INTERFACE = "org.openbmc.control.Power"; 44 45 PowerSupply::PowerSupply(const std::string& name, size_t inst, 46 const std::string& objpath, 47 const std::string& invpath, 48 sdbusplus::bus::bus& bus, 49 event::Event& e, 50 std::chrono::seconds& t) 51 : Device(name, inst), monitorPath(objpath), pmbusIntf(objpath), 52 inventoryPath(invpath), bus(bus), event(e), powerOnInterval(t), 53 powerOnTimer(e, [this]() 54 { 55 this->powerOn = true; 56 }) 57 { 58 using namespace sdbusplus::bus; 59 auto present_obj_path = INVENTORY_OBJ_PATH + inventoryPath; 60 presentMatch = std::make_unique<match_t>(bus, 61 match::rules::propertiesChanged( 62 present_obj_path, 63 INVENTORY_INTERFACE), 64 [this](auto& msg) 65 { 66 this->inventoryChanged(msg); 67 }); 68 // Get initial presence state. 69 updatePresence(); 70 71 // Subscribe to power state changes 72 powerOnMatch = std::make_unique<match_t>(bus, 73 match::rules::propertiesChanged( 74 POWER_OBJ_PATH, 75 POWER_INTERFACE), 76 [this](auto& msg) 77 { 78 this->powerStateChanged(msg); 79 }); 80 // Get initial power state. 81 updatePowerState(); 82 } 83 84 void PowerSupply::captureCmd(util::NamesValues& nv, const std::string& cmd, 85 witherspoon::pmbus::Type type) 86 { 87 if (pmbusIntf.exists(cmd, type)) 88 { 89 try 90 { 91 auto val = pmbusIntf.read(cmd, type); 92 nv.add(cmd, val); 93 } 94 catch (std::exception& e) 95 { 96 log<level::INFO>("Unable to capture metadata", entry("CMD=%s", 97 cmd)); 98 } 99 } 100 } 101 102 void PowerSupply::analyze() 103 { 104 using namespace witherspoon::pmbus; 105 106 try 107 { 108 if (present) 109 { 110 std::uint16_t statusWord = 0; 111 112 // Read the 2 byte STATUS_WORD value to check for faults. 113 statusWord = pmbusIntf.read(STATUS_WORD, Type::Debug); 114 115 //TODO: 3 consecutive reads should be performed. 116 // If 3 consecutive reads are seen, log the fault. 117 // Driver gives cached value, read once a second. 118 // increment for fault on, decrement for fault off, to deglitch. 119 // If count reaches 3, we have fault. If count reaches 0, fault is 120 // cleared. 121 122 checkInputFault(statusWord); 123 124 if (powerOn) 125 { 126 checkFanFault(statusWord); 127 checkTemperatureFault(statusWord); 128 checkOutputOvervoltageFault(statusWord); 129 checkCurrentOutOverCurrentFault(statusWord); 130 checkPGOrUnitOffFault(statusWord); 131 } 132 } 133 } 134 catch (ReadFailure& e) 135 { 136 if (!readFailLogged) 137 { 138 commit<ReadFailure>(); 139 readFailLogged = true; 140 } 141 } 142 143 return; 144 } 145 146 void PowerSupply::inventoryChanged(sdbusplus::message::message& msg) 147 { 148 std::string msgSensor; 149 std::map<std::string, sdbusplus::message::variant<uint32_t, bool>> msgData; 150 msg.read(msgSensor, msgData); 151 152 // Check if it was the Present property that changed. 153 auto valPropMap = msgData.find(PRESENT_PROP); 154 if (valPropMap != msgData.end()) 155 { 156 present = sdbusplus::message::variant_ns::get<bool>(valPropMap->second); 157 158 if (present) 159 { 160 readFailLogged = false; 161 vinUVFault = false; 162 inputFault = false; 163 outputOCFault = false; 164 outputOVFault = false; 165 fanFault = false; 166 temperatureFault = false; 167 } 168 } 169 170 return; 171 } 172 173 void PowerSupply::updatePresence() 174 { 175 // Use getProperty utility function to get presence status. 176 std::string path = INVENTORY_OBJ_PATH + inventoryPath; 177 std::string service = "xyz.openbmc_project.Inventory.Manager"; 178 179 try 180 { 181 util::getProperty(INVENTORY_INTERFACE, PRESENT_PROP, path, 182 service, bus, this->present); 183 } 184 catch (std::exception& e) 185 { 186 // If we happen to be trying to update presence just as it is being 187 // updated, we may encounter a runtime_error. Just catch that for 188 // now, let the inventoryChanged signal handler update presence later. 189 present = false; 190 } 191 192 } 193 194 void PowerSupply::powerStateChanged(sdbusplus::message::message& msg) 195 { 196 int32_t state = 0; 197 std::string msgSensor; 198 std::map<std::string, sdbusplus::message::variant<int32_t, int32_t>> 199 msgData; 200 msg.read(msgSensor, msgData); 201 202 // Check if it was the Present property that changed. 203 auto valPropMap = msgData.find("state"); 204 if (valPropMap != msgData.end()) 205 { 206 state = sdbusplus::message::variant_ns::get<int32_t>(valPropMap->second); 207 208 // Power is on when state=1. Set the fault logged variables to false 209 // and start the power on timer when the state changes to 1. 210 if (state) 211 { 212 readFailLogged = false; 213 vinUVFault = false; 214 inputFault = false; 215 powerOnFault = false; 216 outputOCFault = false; 217 outputOVFault = false; 218 fanFault = false; 219 temperatureFault = false; 220 powerOnTimer.start(powerOnInterval, Timer::TimerType::oneshot); 221 } 222 else 223 { 224 powerOnTimer.stop(); 225 powerOn = false; 226 } 227 } 228 229 } 230 231 void PowerSupply::updatePowerState() 232 { 233 // When state = 1, system is powered on 234 int32_t state = 0; 235 236 try 237 { 238 auto service = util::getService(POWER_OBJ_PATH, 239 POWER_INTERFACE, 240 bus); 241 242 // Use getProperty utility function to get power state. 243 util::getProperty<int32_t>(POWER_INTERFACE, 244 "state", 245 POWER_OBJ_PATH, 246 service, 247 bus, 248 state); 249 250 if (state) 251 { 252 powerOn = true; 253 } 254 else 255 { 256 powerOn = false; 257 } 258 } 259 catch (std::exception& e) 260 { 261 log<level::INFO>("Failed to get power state. Assuming it is off."); 262 powerOn = false; 263 } 264 265 } 266 267 void PowerSupply::checkInputFault(const uint16_t statusWord) 268 { 269 using namespace witherspoon::pmbus; 270 271 std::uint8_t statusInput = 0; 272 273 if ((statusWord & status_word::VIN_UV_FAULT) && !vinUVFault) 274 { 275 vinUVFault = true; 276 277 util::NamesValues nv; 278 nv.add("STATUS_WORD", statusWord); 279 280 using metadata = xyz::openbmc_project::Power::Fault:: 281 PowerSupplyUnderVoltageFault; 282 283 report<PowerSupplyUnderVoltageFault>(metadata::RAW_STATUS( 284 nv.get().c_str())); 285 } 286 else 287 { 288 if (vinUVFault) 289 { 290 vinUVFault = false; 291 log<level::INFO>("VIN_UV_FAULT cleared", 292 entry("POWERSUPPLY=%s", 293 inventoryPath.c_str())); 294 } 295 } 296 297 if ((statusWord & status_word::INPUT_FAULT_WARN) && !inputFault) 298 { 299 inputFault = true; 300 301 util::NamesValues nv; 302 nv.add("STATUS_WORD", statusWord); 303 captureCmd(nv, STATUS_INPUT, Type::Debug); 304 305 using metadata = xyz::openbmc_project::Power::Fault:: 306 PowerSupplyInputFault; 307 308 report<PowerSupplyInputFault>( 309 metadata::RAW_STATUS(nv.get().c_str())); 310 } 311 else 312 { 313 if ((inputFault) && 314 !(statusWord & status_word::INPUT_FAULT_WARN)) 315 { 316 inputFault = false; 317 statusInput = pmbusIntf.read(STATUS_INPUT, Type::Debug); 318 319 log<level::INFO>("INPUT_FAULT_WARN cleared", 320 entry("POWERSUPPLY=%s", inventoryPath.c_str()), 321 entry("STATUS_WORD=0x%04X", statusWord), 322 entry("STATUS_INPUT=0x%02X", statusInput)); 323 } 324 } 325 } 326 327 void PowerSupply::checkPGOrUnitOffFault(const uint16_t statusWord) 328 { 329 using namespace witherspoon::pmbus; 330 331 // Check PG# and UNIT_IS_OFF 332 if (((statusWord & status_word::POWER_GOOD_NEGATED) || 333 (statusWord & status_word::UNIT_IS_OFF)) && 334 !powerOnFault) 335 { 336 util::NamesValues nv; 337 nv.add("STATUS_WORD", statusWord); 338 captureCmd(nv, STATUS_INPUT, Type::Debug); 339 auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0); 340 captureCmd(nv, status0Vout, Type::Debug); 341 captureCmd(nv, STATUS_IOUT, Type::Debug); 342 captureCmd(nv, STATUS_MFR, Type::Debug); 343 344 using metadata = xyz::openbmc_project::Power::Fault:: 345 PowerSupplyShouldBeOn; 346 347 // A power supply is OFF (or pgood low) but should be on. 348 report<PowerSupplyShouldBeOn>(metadata::RAW_STATUS(nv.get().c_str()), 349 metadata::CALLOUT_INVENTORY_PATH( 350 inventoryPath.c_str())); 351 352 powerOnFault = true; 353 } 354 355 } 356 357 void PowerSupply::checkCurrentOutOverCurrentFault(const uint16_t statusWord) 358 { 359 using namespace witherspoon::pmbus; 360 361 // Check for an output overcurrent fault. 362 if ((statusWord & status_word::IOUT_OC_FAULT) && 363 !outputOCFault) 364 { 365 util::NamesValues nv; 366 nv.add("STATUS_WORD", statusWord); 367 captureCmd(nv, STATUS_INPUT, Type::Debug); 368 auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0); 369 captureCmd(nv, status0Vout, Type::Debug); 370 captureCmd(nv, STATUS_IOUT, Type::Debug); 371 captureCmd(nv, STATUS_MFR, Type::Debug); 372 373 using metadata = xyz::openbmc_project::Power::Fault:: 374 PowerSupplyOutputOvercurrent; 375 376 report<PowerSupplyOutputOvercurrent>(metadata::RAW_STATUS( 377 nv.get().c_str()), 378 metadata::CALLOUT_INVENTORY_PATH( 379 inventoryPath.c_str())); 380 381 outputOCFault = true; 382 } 383 } 384 385 void PowerSupply::checkOutputOvervoltageFault(const uint16_t statusWord) 386 { 387 using namespace witherspoon::pmbus; 388 389 // Check for an output overvoltage fault. 390 if ((statusWord & status_word::VOUT_OV_FAULT) && 391 !outputOVFault) 392 { 393 util::NamesValues nv; 394 nv.add("STATUS_WORD", statusWord); 395 captureCmd(nv, STATUS_INPUT, Type::Debug); 396 auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0); 397 captureCmd(nv, status0Vout, Type::Debug); 398 captureCmd(nv, STATUS_IOUT, Type::Debug); 399 captureCmd(nv, STATUS_MFR, Type::Debug); 400 401 using metadata = xyz::openbmc_project::Power::Fault:: 402 PowerSupplyOutputOvervoltage; 403 404 report<PowerSupplyOutputOvervoltage>(metadata::RAW_STATUS( 405 nv.get().c_str()), 406 metadata::CALLOUT_INVENTORY_PATH( 407 inventoryPath.c_str())); 408 409 outputOVFault = true; 410 } 411 } 412 413 void PowerSupply::checkFanFault(const uint16_t statusWord) 414 { 415 using namespace witherspoon::pmbus; 416 417 // Check for a fan fault or warning condition 418 if ((statusWord & status_word::FAN_FAULT) && 419 !fanFault) 420 { 421 util::NamesValues nv; 422 nv.add("STATUS_WORD", statusWord); 423 captureCmd(nv, STATUS_MFR, Type::Debug); 424 captureCmd(nv, STATUS_TEMPERATURE, Type::Debug); 425 captureCmd(nv, STATUS_FANS_1_2, Type::Debug); 426 427 using metadata = xyz::openbmc_project::Power::Fault:: 428 PowerSupplyFanFault; 429 430 report<PowerSupplyFanFault>( 431 metadata::RAW_STATUS(nv.get().c_str()), 432 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str())); 433 434 fanFault = true; 435 } 436 } 437 438 void PowerSupply::checkTemperatureFault(const uint16_t statusWord) 439 { 440 using namespace witherspoon::pmbus; 441 442 // Due to how the PMBus core device driver sends a clear faults command 443 // the bit in STATUS_WORD will likely be cleared when we attempt to examine 444 // it for a Thermal Fault or Warning. So, check the STATUS_WORD and the 445 // STATUS_TEMPERATURE bits. If either indicates a fault, proceed with 446 // logging the over-temperature condition. 447 std::uint8_t statusTemperature = 0; 448 statusTemperature = pmbusIntf.read(STATUS_TEMPERATURE, Type::Debug); 449 if (((statusWord & status_word::TEMPERATURE_FAULT_WARN) || 450 (statusTemperature & status_temperature::OT_FAULT)) && 451 !temperatureFault) 452 { 453 // The power supply has had an over-temperature condition. 454 // This may not result in a shutdown if experienced for a short 455 // duration. 456 // This should not occur under normal conditions. 457 // The power supply may be faulty, or the paired supply may be putting 458 // out less current. 459 // Capture command responses with potentially relevant information, 460 // and call out the power supply reporting the condition. 461 util::NamesValues nv; 462 nv.add("STATUS_WORD", statusWord); 463 captureCmd(nv, STATUS_MFR, Type::Debug); 464 captureCmd(nv, STATUS_IOUT, Type::Debug); 465 nv.add("STATUS_TEMPERATURE", statusTemperature); 466 captureCmd(nv, STATUS_FANS_1_2, Type::Debug); 467 468 using metadata = xyz::openbmc_project::Power::Fault:: 469 PowerSupplyTemperatureFault; 470 471 report<PowerSupplyTemperatureFault>( 472 metadata::RAW_STATUS(nv.get().c_str()), 473 metadata::CALLOUT_INVENTORY_PATH(inventoryPath.c_str())); 474 475 temperatureFault = true; 476 } 477 } 478 479 void PowerSupply::clearFaults() 480 { 481 //TODO - Clear faults at pre-poweron. openbmc/openbmc#1736 482 return; 483 } 484 485 } 486 } 487 } 488