1 #include "psu_manager.hpp" 2 3 #include "utility.hpp" 4 5 #include <fmt/format.h> 6 #include <sys/types.h> 7 #include <unistd.h> 8 9 using namespace phosphor::logging; 10 11 namespace phosphor::power::manager 12 { 13 14 PSUManager::PSUManager(sdbusplus::bus::bus& bus, const sdeventplus::Event& e, 15 const std::string& configfile) : 16 bus(bus) 17 { 18 // Parse out the JSON properties 19 sys_properties properties; 20 getJSONProperties(configfile, bus, properties, psus); 21 22 using namespace sdeventplus; 23 auto interval = std::chrono::milliseconds(1000); 24 timer = std::make_unique<utility::Timer<ClockId::Monotonic>>( 25 e, std::bind(&PSUManager::analyze, this), interval); 26 27 minPSUs = {properties.minPowerSupplies}; 28 maxPSUs = {properties.maxPowerSupplies}; 29 30 // Subscribe to power state changes 31 powerService = util::getService(POWER_OBJ_PATH, POWER_IFACE, bus); 32 powerOnMatch = std::make_unique<sdbusplus::bus::match_t>( 33 bus, 34 sdbusplus::bus::match::rules::propertiesChanged(POWER_OBJ_PATH, 35 POWER_IFACE), 36 [this](auto& msg) { this->powerStateChanged(msg); }); 37 38 initialize(); 39 } 40 41 void PSUManager::getJSONProperties( 42 const std::string& path, sdbusplus::bus::bus& bus, sys_properties& p, 43 std::vector<std::unique_ptr<PowerSupply>>& psus) 44 { 45 nlohmann::json configFileJSON = util::loadJSONFromFile(path.c_str()); 46 47 if (configFileJSON == nullptr) 48 { 49 throw std::runtime_error("Failed to load JSON configuration file"); 50 } 51 52 if (!configFileJSON.contains("SystemProperties")) 53 { 54 throw std::runtime_error("Missing required SystemProperties"); 55 } 56 57 if (!configFileJSON.contains("PowerSupplies")) 58 { 59 throw std::runtime_error("Missing required PowerSupplies"); 60 } 61 62 auto sysProps = configFileJSON["SystemProperties"]; 63 64 if (sysProps.contains("MinPowerSupplies")) 65 { 66 p.minPowerSupplies = sysProps["MinPowerSupplies"]; 67 } 68 else 69 { 70 p.minPowerSupplies = 0; 71 } 72 73 if (sysProps.contains("MaxPowerSupplies")) 74 { 75 p.maxPowerSupplies = sysProps["MaxPowerSupplies"]; 76 } 77 else 78 { 79 p.maxPowerSupplies = 0; 80 } 81 82 for (auto psuJSON : configFileJSON["PowerSupplies"]) 83 { 84 if (psuJSON.contains("Inventory") && psuJSON.contains("Bus") && 85 psuJSON.contains("Address")) 86 { 87 std::string invpath = psuJSON["Inventory"]; 88 std::uint8_t i2cbus = psuJSON["Bus"]; 89 std::string i2caddr = psuJSON["Address"]; 90 auto psu = 91 std::make_unique<PowerSupply>(bus, invpath, i2cbus, i2caddr); 92 psus.emplace_back(std::move(psu)); 93 } 94 else 95 { 96 log<level::ERR>("Insufficient PowerSupply properties"); 97 } 98 } 99 100 if (psus.empty()) 101 { 102 throw std::runtime_error("No power supplies to monitor"); 103 } 104 } 105 106 void PSUManager::powerStateChanged(sdbusplus::message::message& msg) 107 { 108 int32_t state = 0; 109 std::string msgSensor; 110 std::map<std::string, std::variant<int32_t>> msgData; 111 msg.read(msgSensor, msgData); 112 113 // Check if it was the Present property that changed. 114 auto valPropMap = msgData.find("state"); 115 if (valPropMap != msgData.end()) 116 { 117 state = std::get<int32_t>(valPropMap->second); 118 119 // Power is on when state=1. Clear faults. 120 if (state) 121 { 122 powerOn = true; 123 clearFaults(); 124 } 125 else 126 { 127 powerOn = false; 128 } 129 } 130 } 131 132 void PSUManager::createError( 133 const std::string& faultName, 134 const std::map<std::string, std::string>& additionalData) 135 { 136 using namespace sdbusplus::xyz::openbmc_project; 137 constexpr auto loggingObjectPath = "/xyz/openbmc_project/logging"; 138 constexpr auto loggingCreateInterface = 139 "xyz.openbmc_project.Logging.Create"; 140 141 try 142 { 143 auto service = 144 util::getService(loggingObjectPath, loggingCreateInterface, bus); 145 146 if (service.empty()) 147 { 148 log<level::ERR>("Unable to get logging manager service"); 149 return; 150 } 151 152 auto method = bus.new_method_call(service.c_str(), loggingObjectPath, 153 loggingCreateInterface, "Create"); 154 155 auto level = Logging::server::Entry::Level::Error; 156 method.append(faultName, level, additionalData); 157 158 auto reply = bus.call(method); 159 } 160 catch (std::exception& e) 161 { 162 log<level::ERR>( 163 fmt::format( 164 "Failed creating event log for fault {} due to error {}", 165 faultName, e.what()) 166 .c_str()); 167 } 168 } 169 170 void PSUManager::analyze() 171 { 172 for (auto& psu : psus) 173 { 174 psu->analyze(); 175 } 176 177 if (powerOn) 178 { 179 for (auto& psu : psus) 180 { 181 std::map<std::string, std::string> additionalData; 182 additionalData["_PID"] = std::to_string(getpid()); 183 // TODO: Fault priorities #918 184 if (!psu->isFaultLogged() && !psu->isPresent()) 185 { 186 // Create error for power supply missing. 187 additionalData["CALLOUT_INVENTORY_PATH"] = 188 psu->getInventoryPath(); 189 additionalData["CALLOUT_PRIORITY"] = "H"; 190 createError( 191 "xyz.openbmc_project.Power.PowerSupply.Error.Missing", 192 additionalData); 193 psu->setFaultLogged(); 194 } 195 else if (!psu->isFaultLogged() && psu->isFaulted()) 196 { 197 additionalData["STATUS_WORD"] = 198 std::to_string(psu->getStatusWord()); 199 additionalData["STATUS_MFR"] = 200 std::to_string(psu->getMFRFault()); 201 // If there are faults being reported, they possibly could be 202 // related to a bug in the firmware version running on the power 203 // supply. Capture that data into the error as well. 204 additionalData["FW_VERSION"] = psu->getFWVersion(); 205 206 if ((psu->hasInputFault() || psu->hasVINUVFault())) 207 { 208 /* The power supply location might be needed if the input 209 * fault is due to a problem with the power supply itself. 210 * Include the inventory path with a call out priority of 211 * low. 212 */ 213 additionalData["CALLOUT_INVENTORY_PATH"] = 214 psu->getInventoryPath(); 215 additionalData["CALLOUT_PRIORITY"] = "L"; 216 createError("xyz.openbmc_project.Power.PowerSupply.Error." 217 "InputFault", 218 additionalData); 219 psu->setFaultLogged(); 220 } 221 else if (psu->hasMFRFault()) 222 { 223 /* This can represent a variety of faults that result in 224 * calling out the power supply for replacement: Output 225 * OverCurrent, Output Under Voltage, and potentially other 226 * faults. 227 * 228 * Also plan on putting specific fault in AdditionalData, 229 * along with register names and register values 230 * (STATUS_WORD, STATUS_MFR, etc.).*/ 231 232 additionalData["CALLOUT_INVENTORY_PATH"] = 233 psu->getInventoryPath(); 234 235 createError( 236 "xyz.openbmc_project.Power.PowerSupply.Error.Fault", 237 additionalData); 238 239 psu->setFaultLogged(); 240 } 241 else if (psu->hasCommFault()) 242 { 243 /* Attempts to communicate with the power supply have 244 * reached there limit. Create an error. */ 245 additionalData["CALLOUT_DEVICE_PATH"] = 246 psu->getDevicePath(); 247 248 createError( 249 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault", 250 additionalData); 251 252 psu->setFaultLogged(); 253 } 254 } 255 } 256 } 257 } 258 259 } // namespace phosphor::power::manager 260