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 for (auto& psu : psus) 178 { 179 std::map<std::string, std::string> additionalData; 180 additionalData["_PID"] = std::to_string(getpid()); 181 // TODO: Fault priorities #918 182 if (!psu->isFaultLogged() && !psu->isPresent()) 183 { 184 // Create error for power supply missing. 185 additionalData["CALLOUT_INVENTORY_PATH"] = psu->getInventoryPath(); 186 additionalData["CALLOUT_PRIORITY"] = "H"; 187 createError("xyz.openbmc_project.Power.PowerSupply.Error.Missing", 188 additionalData); 189 psu->setFaultLogged(); 190 } 191 else if (!psu->isFaultLogged() && psu->isFaulted()) 192 { 193 additionalData["STATUS_WORD"] = 194 std::to_string(psu->getStatusWord()); 195 // If there are faults being reported, they possibly could be 196 // related to a bug in the firmware version running on the power 197 // supply. Capture that data into the error as well. 198 additionalData["FW_VERSION"] = psu->getFWVersion(); 199 200 if ((psu->hasInputFault() || psu->hasVINUVFault())) 201 { 202 /* The power supply location might be needed if the input fault 203 * is due to a problem with the power supply itself. Include the 204 * inventory path with a call out priority of low. 205 */ 206 additionalData["CALLOUT_INVENTORY_PATH"] = 207 psu->getInventoryPath(); 208 additionalData["CALLOUT_PRIORITY"] = "L"; 209 createError( 210 "xyz.openbmc_project.Power.PowerSupply.Error.InputFault", 211 additionalData); 212 psu->setFaultLogged(); 213 } 214 else if (psu->hasMFRFault()) 215 { 216 /* This can represent a variety of faults that result in calling 217 * out the power supply for replacement: 218 * Output OverCurrent, Output Under Voltage, and potentially 219 * other faults. 220 * 221 * Also plan on putting specific fault in AdditionalData, 222 * along with register names and register values 223 * (STATUS_WORD, STATUS_MFR, etc.).*/ 224 225 additionalData["CALLOUT_INVENTORY_PATH"] = 226 psu->getInventoryPath(); 227 228 createError("xyz.openbmc_project.Power.PowerSupply.Error.Fault", 229 additionalData); 230 231 psu->setFaultLogged(); 232 } 233 else if (psu->hasCommFault()) 234 { 235 /* Attempts to communicate with the power supply have reached 236 * there limit. Create an error. */ 237 additionalData["CALLOUT_DEVICE_PATH"] = psu->getDevicePath(); 238 239 createError( 240 "xyz.openbmc_project.Power.PowerSupply.Error.CommFault", 241 additionalData); 242 243 psu->setFaultLogged(); 244 } 245 } 246 } 247 } 248 249 } // namespace phosphor::power::manager 250