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 85 void PowerSupply::analyze() 86 { 87 using namespace witherspoon::pmbus; 88 89 try 90 { 91 if (present) 92 { 93 std::uint16_t statusWord = 0; 94 95 // Read the 2 byte STATUS_WORD value to check for faults. 96 statusWord = pmbusIntf.read(STATUS_WORD, Type::Debug); 97 98 //TODO: 3 consecutive reads should be performed. 99 // If 3 consecutive reads are seen, log the fault. 100 // Driver gives cached value, read once a second. 101 // increment for fault on, decrement for fault off, to deglitch. 102 // If count reaches 3, we have fault. If count reaches 0, fault is 103 // cleared. 104 105 checkInputFault(statusWord); 106 107 if (powerOn) 108 { 109 checkPGOrUnitOffFault(statusWord); 110 checkCurrentOutOverCurrentFault(statusWord); 111 checkOutputOvervoltageFault(statusWord); 112 } 113 } 114 } 115 catch (ReadFailure& e) 116 { 117 if (!readFailLogged) 118 { 119 commit<ReadFailure>(); 120 readFailLogged = true; 121 } 122 } 123 124 return; 125 } 126 127 void PowerSupply::inventoryChanged(sdbusplus::message::message& msg) 128 { 129 std::string msgSensor; 130 std::map<std::string, sdbusplus::message::variant<uint32_t, bool>> msgData; 131 msg.read(msgSensor, msgData); 132 133 // Check if it was the Present property that changed. 134 auto valPropMap = msgData.find(PRESENT_PROP); 135 if (valPropMap != msgData.end()) 136 { 137 present = sdbusplus::message::variant_ns::get<bool>(valPropMap->second); 138 139 if (present) 140 { 141 readFailLogged = false; 142 vinUVFault = false; 143 inputFault = false; 144 outputOCFault = false; 145 outputOVFault = false; 146 } 147 } 148 149 return; 150 } 151 152 void PowerSupply::updatePresence() 153 { 154 // Use getProperty utility function to get presence status. 155 std::string path = INVENTORY_OBJ_PATH + inventoryPath; 156 std::string service = "xyz.openbmc_project.Inventory.Manager"; 157 158 try 159 { 160 util::getProperty(INVENTORY_INTERFACE, PRESENT_PROP, path, 161 service, bus, this->present); 162 } 163 catch (std::exception& e) 164 { 165 // If we happen to be trying to update presence just as it is being 166 // updated, we may encounter a runtime_error. Just catch that for 167 // now, let the inventoryChanged signal handler update presence later. 168 present = false; 169 } 170 171 } 172 173 void PowerSupply::powerStateChanged(sdbusplus::message::message& msg) 174 { 175 int32_t state = 0; 176 std::string msgSensor; 177 std::map<std::string, sdbusplus::message::variant<int32_t, int32_t>> 178 msgData; 179 msg.read(msgSensor, msgData); 180 181 // Check if it was the Present property that changed. 182 auto valPropMap = msgData.find("state"); 183 if (valPropMap != msgData.end()) 184 { 185 state = sdbusplus::message::variant_ns::get<int32_t>(valPropMap->second); 186 187 // Power is on when state=1. Set the fault logged variables to false 188 // and start the power on timer when the state changes to 1. 189 if (state) 190 { 191 readFailLogged = false; 192 vinUVFault = false; 193 inputFault = false; 194 powerOnFault = false; 195 outputOCFault = false; 196 outputOVFault = false; 197 powerOnTimer.start(powerOnInterval, Timer::TimerType::oneshot); 198 } 199 else 200 { 201 powerOnTimer.stop(); 202 powerOn = false; 203 } 204 } 205 206 } 207 208 void PowerSupply::updatePowerState() 209 { 210 // When state = 1, system is powered on 211 int32_t state = 0; 212 213 try 214 { 215 auto service = util::getService(POWER_OBJ_PATH, 216 POWER_INTERFACE, 217 bus); 218 219 // Use getProperty utility function to get power state. 220 util::getProperty<int32_t>(POWER_INTERFACE, 221 "state", 222 POWER_OBJ_PATH, 223 service, 224 bus, 225 state); 226 227 if (state) 228 { 229 powerOn = true; 230 } 231 else 232 { 233 powerOn = false; 234 } 235 } 236 catch (std::exception& e) 237 { 238 log<level::INFO>("Failed to get power state. Assuming it is off."); 239 powerOn = false; 240 } 241 242 } 243 244 void PowerSupply::checkInputFault(const uint16_t statusWord) 245 { 246 using namespace witherspoon::pmbus; 247 248 std::uint8_t statusInput = 0; 249 250 if ((statusWord & status_word::VIN_UV_FAULT) && !vinUVFault) 251 { 252 vinUVFault = true; 253 254 util::NamesValues nv; 255 nv.add("STATUS_WORD", statusWord); 256 257 using metadata = xyz::openbmc_project::Power::Fault:: 258 PowerSupplyUnderVoltageFault; 259 260 report<PowerSupplyUnderVoltageFault>(metadata::RAW_STATUS( 261 nv.get().c_str())); 262 } 263 else 264 { 265 if (vinUVFault) 266 { 267 vinUVFault = false; 268 log<level::INFO>("VIN_UV_FAULT cleared", 269 entry("POWERSUPPLY=%s", 270 inventoryPath.c_str())); 271 } 272 } 273 274 if ((statusWord & status_word::INPUT_FAULT_WARN) && !inputFault) 275 { 276 inputFault = true; 277 278 statusInput = pmbusIntf.read(STATUS_INPUT, Type::Debug); 279 280 util::NamesValues nv; 281 nv.add("STATUS_WORD", statusWord); 282 nv.add("STATUS_INPUT", statusInput); 283 284 using metadata = xyz::openbmc_project::Power::Fault:: 285 PowerSupplyInputFault; 286 287 report<PowerSupplyInputFault>( 288 metadata::RAW_STATUS(nv.get().c_str())); 289 } 290 else 291 { 292 if ((inputFault) && 293 !(statusWord & status_word::INPUT_FAULT_WARN)) 294 { 295 inputFault = false; 296 statusInput = pmbusIntf.read(STATUS_INPUT, Type::Debug); 297 298 log<level::INFO>("INPUT_FAULT_WARN cleared", 299 entry("POWERSUPPLY=%s", inventoryPath.c_str()), 300 entry("STATUS_WORD=0x%04X", statusWord), 301 entry("STATUS_INPUT=0x%02X", statusInput)); 302 } 303 } 304 } 305 306 void PowerSupply::checkPGOrUnitOffFault(const uint16_t statusWord) 307 { 308 using namespace witherspoon::pmbus; 309 310 std::uint8_t statusInput = 0; 311 std::uint8_t statusVout = 0; 312 std::uint8_t statusIout = 0; 313 std::uint8_t statusMFR = 0; 314 315 // Check PG# and UNIT_IS_OFF 316 if (((statusWord & status_word::POWER_GOOD_NEGATED) || 317 (statusWord & status_word::UNIT_IS_OFF)) && 318 !powerOnFault) 319 { 320 statusInput = pmbusIntf.read(STATUS_INPUT, Type::Debug); 321 auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0); 322 statusVout = pmbusIntf.read(status0Vout, Type::Debug); 323 statusIout = pmbusIntf.read(STATUS_IOUT, Type::Debug); 324 statusMFR = pmbusIntf.read(STATUS_MFR, Type::Debug); 325 326 util::NamesValues nv; 327 nv.add("STATUS_WORD", statusWord); 328 nv.add("STATUS_INPUT", statusInput); 329 nv.add("STATUS_VOUT", statusVout); 330 nv.add("STATUS_IOUT", statusIout); 331 nv.add("MFR_SPECIFIC", statusMFR); 332 333 using metadata = xyz::openbmc_project::Power::Fault:: 334 PowerSupplyShouldBeOn; 335 336 // A power supply is OFF (or pgood low) but should be on. 337 report<PowerSupplyShouldBeOn>(metadata::RAW_STATUS(nv.get().c_str()), 338 metadata::CALLOUT_INVENTORY_PATH( 339 inventoryPath.c_str())); 340 341 powerOnFault = true; 342 } 343 344 } 345 346 void PowerSupply::checkCurrentOutOverCurrentFault(const uint16_t statusWord) 347 { 348 using namespace witherspoon::pmbus; 349 350 std::uint8_t statusInput = 0; 351 std::uint8_t statusVout = 0; 352 std::uint8_t statusIout = 0; 353 std::uint8_t statusMFR = 0; 354 355 // Check for an output overcurrent fault. 356 if ((statusWord & status_word::IOUT_OC_FAULT) && 357 !outputOCFault) 358 { 359 statusInput = pmbusIntf.read(STATUS_INPUT, Type::Debug); 360 auto status0Vout = pmbusIntf.insertPageNum(STATUS_VOUT, 0); 361 statusVout = pmbusIntf.read(status0Vout, Type::Debug); 362 statusIout = pmbusIntf.read(STATUS_IOUT, Type::Debug); 363 statusMFR = pmbusIntf.read(STATUS_MFR, Type::Debug); 364 365 util::NamesValues nv; 366 nv.add("STATUS_WORD", statusWord); 367 nv.add("STATUS_INPUT", statusInput); 368 nv.add("STATUS_VOUT", statusVout); 369 nv.add("STATUS_IOUT", statusIout); 370 nv.add("MFR_SPECIFIC", statusMFR); 371 372 using metadata = xyz::openbmc_project::Power::Fault:: 373 PowerSupplyOutputOvercurrent; 374 375 report<PowerSupplyOutputOvercurrent>(metadata::RAW_STATUS( 376 nv.get().c_str()), 377 metadata::CALLOUT_INVENTORY_PATH( 378 inventoryPath.c_str())); 379 380 outputOCFault = true; 381 } 382 } 383 384 void PowerSupply::checkOutputOvervoltageFault(const uint16_t statusWord) 385 { 386 using namespace witherspoon::pmbus; 387 388 std::uint8_t statusInput = 0; 389 std::uint8_t statusVout = 0; 390 std::uint8_t statusIout = 0; 391 std::uint8_t statusMFR = 0; 392 393 // Check for an output overvoltage fault. 394 if ((statusWord & status_word::VOUT_OV_FAULT) && 395 !outputOVFault) 396 { 397 statusInput = pmbusIntf.read(STATUS_INPUT, Type::Debug); 398 statusVout = pmbusIntf.read(STATUS_VOUT, Type::Debug); 399 statusIout = pmbusIntf.read(STATUS_IOUT, Type::Debug); 400 statusMFR = pmbusIntf.read(STATUS_MFR, Type::Debug); 401 402 util::NamesValues nv; 403 nv.add("STATUS_WORD", statusWord); 404 nv.add("STATUS_INPUT", statusInput); 405 nv.add("STATUS_VOUT", statusVout); 406 nv.add("STATUS_IOUT", statusIout); 407 nv.add("MFR_SPECIFIC", statusMFR); 408 409 using metadata = xyz::openbmc_project::Power::Fault:: 410 PowerSupplyOutputOvervoltage; 411 412 report<PowerSupplyOutputOvervoltage>(metadata::RAW_STATUS( 413 nv.get().c_str()), 414 metadata::CALLOUT_INVENTORY_PATH( 415 inventoryPath.c_str())); 416 417 outputOVFault = true; 418 } 419 } 420 421 void PowerSupply::clearFaults() 422 { 423 //TODO - Clear faults at pre-poweron. openbmc/openbmc#1736 424 return; 425 } 426 427 } 428 } 429 } 430