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