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