1 #include "config.h" 2 3 #include "power_supply.hpp" 4 5 #include "types.hpp" 6 #include "util.hpp" 7 8 #include <fmt/format.h> 9 10 #include <xyz/openbmc_project/Common/Device/error.hpp> 11 12 #include <chrono> // sleep_for() 13 #include <cstdint> // uint8_t... 14 #include <thread> // sleep_for() 15 16 namespace phosphor::power::psu 17 { 18 19 using namespace phosphor::logging; 20 using namespace sdbusplus::xyz::openbmc_project::Common::Device::Error; 21 22 void PowerSupply::updatePresence() 23 { 24 try 25 { 26 present = getPresence(bus, inventoryPath); 27 } 28 catch (const sdbusplus::exception::SdBusError& e) 29 { 30 // Relying on property change or interface added to retry. 31 // Log an informational trace to the journal. 32 log<level::INFO>( 33 fmt::format("D-Bus property {} access failure exception", 34 inventoryPath) 35 .c_str()); 36 } 37 } 38 39 void PowerSupply::analyze() 40 { 41 using namespace phosphor::pmbus; 42 43 if ((present) && (readFail < LOG_LIMIT)) 44 { 45 try 46 { 47 statusWord = pmbusIntf->read(STATUS_WORD, Type::Debug); 48 // Read worked, reset the fail count. 49 readFail = 0; 50 51 if (statusWord) 52 { 53 statusMFR = pmbusIntf->read(STATUS_MFR, Type::Debug); 54 if (statusWord & status_word::INPUT_FAULT_WARN) 55 { 56 if (!inputFault) 57 { 58 log<level::INFO>(fmt::format("INPUT fault: " 59 "status word = {:#04x}, " 60 "MFR fault = {:#02x}", 61 statusWord, statusMFR) 62 .c_str()); 63 } 64 65 faultFound = true; 66 inputFault = true; 67 } 68 69 if (statusWord & status_word::MFR_SPECIFIC_FAULT) 70 { 71 if (!mfrFault) 72 { 73 log<level::ERR>(fmt::format("MFR fault: " 74 "status word = {:#04x} " 75 "MFR fault = {:#02x}", 76 statusWord, statusMFR) 77 .c_str()); 78 } 79 faultFound = true; 80 mfrFault = true; 81 } 82 83 if (statusWord & status_word::VIN_UV_FAULT) 84 { 85 if (!vinUVFault) 86 { 87 log<level::INFO>(fmt::format("VIN_UV fault: " 88 "status word = {:#04x}, " 89 "MFR fault = {:#02x}", 90 statusWord, statusMFR) 91 .c_str()); 92 } 93 94 faultFound = true; 95 vinUVFault = true; 96 } 97 } 98 else 99 { 100 faultFound = false; 101 inputFault = false; 102 mfrFault = false; 103 vinUVFault = false; 104 } 105 } 106 catch (ReadFailure& e) 107 { 108 readFail++; 109 phosphor::logging::commit<ReadFailure>(); 110 } 111 } 112 } 113 114 void PowerSupply::onOffConfig(uint8_t data) 115 { 116 using namespace phosphor::pmbus; 117 118 if (present) 119 { 120 log<level::INFO>("ON_OFF_CONFIG write", entry("DATA=0x%02X", data)); 121 try 122 { 123 std::vector<uint8_t> configData{data}; 124 pmbusIntf->writeBinary(ON_OFF_CONFIG, configData, 125 Type::HwmonDeviceDebug); 126 } 127 catch (...) 128 { 129 // The underlying code in writeBinary will log a message to the 130 // journal if the write fails. If the ON_OFF_CONFIG is not setup as 131 // desired, later fault detection and analysis code should catch any 132 // of the fall out. We should not need to terminate the application 133 // if this write fails. 134 } 135 } 136 } 137 138 void PowerSupply::clearFaults() 139 { 140 // The PMBus device driver does not allow for writing CLEAR_FAULTS 141 // directly. However, the pmbus hwmon device driver code will send a 142 // CLEAR_FAULTS after reading from any of the hwmon "files" in sysfs, so 143 // reading in1_input should result in clearing the fault bits in 144 // STATUS_BYTE/STATUS_WORD. 145 // I do not care what the return value is. 146 if (present) 147 { 148 faultFound = false; 149 inputFault = false; 150 mfrFault = false; 151 statusMFR = 0; 152 vinUVFault = false; 153 readFail = 0; 154 faultLogged = false; 155 156 try 157 { 158 static_cast<void>( 159 pmbusIntf->read("in1_input", phosphor::pmbus::Type::Hwmon)); 160 } 161 catch (ReadFailure& e) 162 { 163 // Since I do not care what the return value is, I really do not 164 // care much if it gets a ReadFailure either. However, this should 165 // not prevent the application from continuing to run, so catching 166 // the read failure. 167 } 168 } 169 } 170 171 void PowerSupply::inventoryChanged(sdbusplus::message::message& msg) 172 { 173 std::string msgSensor; 174 std::map<std::string, std::variant<uint32_t, bool>> msgData; 175 msg.read(msgSensor, msgData); 176 177 // Check if it was the Present property that changed. 178 auto valPropMap = msgData.find(PRESENT_PROP); 179 if (valPropMap != msgData.end()) 180 { 181 if (std::get<bool>(valPropMap->second)) 182 { 183 present = true; 184 // TODO: Immediately trying to read or write the "files" causes read 185 // or write failures. 186 using namespace std::chrono_literals; 187 std::this_thread::sleep_for(20ms); 188 pmbusIntf->findHwmonDir(); 189 onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY); 190 clearFaults(); 191 updateInventory(); 192 } 193 else 194 { 195 present = false; 196 197 // Clear out the now outdated inventory properties 198 updateInventory(); 199 } 200 } 201 } 202 203 void PowerSupply::updateInventory() 204 { 205 using namespace phosphor::pmbus; 206 207 #ifdef IBM_VPD 208 std::string ccin; 209 std::string pn; 210 std::string fn; 211 std::string header; 212 std::string sn; 213 using PropertyMap = 214 std::map<std::string, 215 std::variant<std::string, std::vector<uint8_t>, bool>>; 216 PropertyMap assetProps; 217 PropertyMap operProps; 218 PropertyMap versionProps; 219 PropertyMap ipzvpdDINFProps; 220 PropertyMap ipzvpdVINIProps; 221 using InterfaceMap = std::map<std::string, PropertyMap>; 222 InterfaceMap interfaces; 223 using ObjectMap = std::map<sdbusplus::message::object_path, InterfaceMap>; 224 ObjectMap object; 225 #endif 226 227 if (present) 228 { 229 // TODO: non-IBM inventory updates? 230 231 #ifdef IBM_VPD 232 try 233 { 234 ccin = pmbusIntf->readString(CCIN, Type::HwmonDeviceDebug); 235 assetProps.emplace(MODEL_PROP, ccin); 236 } 237 catch (ReadFailure& e) 238 { 239 // Ignore the read failure, let pmbus code indicate failure, path... 240 // TODO - ibm918 241 // https://github.com/openbmc/docs/blob/master/designs/vpd-collection.md 242 // The BMC must log errors if any of the VPD cannot be properly 243 // parsed or fails ECC checks. 244 } 245 246 try 247 { 248 pn = pmbusIntf->readString(PART_NUMBER, Type::HwmonDeviceDebug); 249 assetProps.emplace(PN_PROP, pn); 250 } 251 catch (ReadFailure& e) 252 { 253 // Ignore the read failure, let pmbus code indicate failure, path... 254 } 255 256 try 257 { 258 fn = pmbusIntf->readString(FRU_NUMBER, Type::HwmonDeviceDebug); 259 } 260 catch (ReadFailure& e) 261 { 262 // Ignore the read failure, let pmbus code indicate failure, path... 263 } 264 265 try 266 { 267 header = 268 pmbusIntf->readString(SERIAL_HEADER, Type::HwmonDeviceDebug); 269 sn = pmbusIntf->readString(SERIAL_NUMBER, Type::HwmonDeviceDebug); 270 assetProps.emplace(SN_PROP, sn); 271 } 272 catch (ReadFailure& e) 273 { 274 // Ignore the read failure, let pmbus code indicate failure, path... 275 } 276 277 try 278 { 279 fwVersion = 280 pmbusIntf->readString(FW_VERSION, Type::HwmonDeviceDebug); 281 versionProps.emplace(VERSION_PROP, fwVersion); 282 } 283 catch (ReadFailure& e) 284 { 285 // Ignore the read failure, let pmbus code indicate failure, path... 286 } 287 288 ipzvpdVINIProps.emplace("CC", 289 std::vector<uint8_t>(ccin.begin(), ccin.end())); 290 ipzvpdVINIProps.emplace("PN", 291 std::vector<uint8_t>(pn.begin(), pn.end())); 292 ipzvpdVINIProps.emplace("FN", 293 std::vector<uint8_t>(fn.begin(), fn.end())); 294 std::string header_sn = header + sn + '\0'; 295 ipzvpdVINIProps.emplace( 296 "SN", std::vector<uint8_t>(header_sn.begin(), header_sn.end())); 297 std::string description = "IBM PS"; 298 ipzvpdVINIProps.emplace( 299 "DR", std::vector<uint8_t>(description.begin(), description.end())); 300 301 // Update the Resource Identifier (RI) keyword 302 // 2 byte FRC: 0x0003 303 // 2 byte RID: 0x1000, 0x1001... 304 std::uint8_t num = std::stoul( 305 inventoryPath.substr(inventoryPath.size() - 1, 1), nullptr, 0); 306 std::vector<uint8_t> ri{0x00, 0x03, 0x10, num}; 307 ipzvpdDINFProps.emplace("RI", ri); 308 309 // Fill in the FRU Label (FL) keyword. 310 std::string fl = "E"; 311 fl.push_back(inventoryPath.back()); 312 fl.resize(FL_KW_SIZE, ' '); 313 ipzvpdDINFProps.emplace("FL", 314 std::vector<uint8_t>(fl.begin(), fl.end())); 315 316 interfaces.emplace(ASSET_IFACE, std::move(assetProps)); 317 interfaces.emplace(VERSION_IFACE, std::move(versionProps)); 318 interfaces.emplace(DINF_IFACE, std::move(ipzvpdDINFProps)); 319 interfaces.emplace(VINI_IFACE, std::move(ipzvpdVINIProps)); 320 321 // Update the Functional 322 operProps.emplace(FUNCTIONAL_PROP, present); 323 interfaces.emplace(OPERATIONAL_STATE_IFACE, std::move(operProps)); 324 325 auto path = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH)); 326 object.emplace(path, std::move(interfaces)); 327 328 try 329 { 330 auto service = 331 util::getService(INVENTORY_OBJ_PATH, INVENTORY_MGR_IFACE, bus); 332 333 if (service.empty()) 334 { 335 log<level::ERR>("Unable to get inventory manager service"); 336 return; 337 } 338 339 auto method = 340 bus.new_method_call(service.c_str(), INVENTORY_OBJ_PATH, 341 INVENTORY_MGR_IFACE, "Notify"); 342 343 method.append(std::move(object)); 344 345 auto reply = bus.call(method); 346 } 347 catch (std::exception& e) 348 { 349 log<level::ERR>( 350 std::string(e.what() + std::string(" PATH=") + inventoryPath) 351 .c_str()); 352 } 353 #endif 354 } 355 } 356 357 } // namespace phosphor::power::psu 358