xref: /openbmc/pldm/softoff/softoff.cpp (revision 4d99c311)
1 #include "softoff.hpp"
2 
3 #include "common/instance_id.hpp"
4 #include "common/transport.hpp"
5 #include "common/utils.hpp"
6 
7 #include <libpldm/entity.h>
8 #include <libpldm/platform.h>
9 #include <libpldm/state_set.h>
10 
11 #include <phosphor-logging/lg2.hpp>
12 #include <sdbusplus/bus.hpp>
13 #include <sdeventplus/clock.hpp>
14 #include <sdeventplus/exception.hpp>
15 #include <sdeventplus/source/io.hpp>
16 #include <sdeventplus/source/time.hpp>
17 
18 #include <array>
19 #include <filesystem>
20 #include <fstream>
21 
22 PHOSPHOR_LOG2_USING;
23 
24 namespace pldm
25 {
26 using namespace sdeventplus;
27 using namespace sdeventplus::source;
28 namespace fs = std::filesystem;
29 constexpr auto clockId = sdeventplus::ClockId::RealTime;
30 using Clock = Clock<clockId>;
31 using Timer = Time<clockId>;
32 
33 pldm::pdr::TerminusID TID = 0; // TID will be implemented later.
34 namespace sdbusRule = sdbusplus::bus::match::rules;
35 
36 SoftPowerOff::SoftPowerOff(sdbusplus::bus_t& bus, sd_event* event,
37                            pldm::InstanceIdDb& instanceIdDb) :
38     bus(bus),
39     timer(event), instanceIdDb(instanceIdDb)
40 {
41     auto jsonData = parseConfig();
42 
43     if (jsonData.is_discarded())
44     {
45         error("Parsing softoff config JSON file failed");
46         return;
47     }
48 
49     getHostState();
50     if (hasError || completed)
51     {
52         return;
53     }
54     const std::vector<Json> emptyJsonList{};
55     auto entries = jsonData.value("entries", emptyJsonList);
56     for (const auto& entry : entries)
57     {
58         TID = entry.value("tid", 0);
59         pldm::pdr::EntityType entityType = entry.value("entityType", 0);
60         pldm::pdr::StateSetId stateSetId = entry.value("stateSetId", 0);
61 
62         bool effecterFound = getEffecterID(entityType, stateSetId);
63         if (effecterFound)
64         {
65             auto rc = getSensorInfo(entityType, stateSetId);
66             if (rc != PLDM_SUCCESS)
67             {
68                 error("Message get Sensor PDRs error. PLDM error code = {RC}",
69                       "RC", lg2::hex, static_cast<int>(rc));
70                 hasError = true;
71                 return;
72             }
73             break;
74         }
75         else
76         {
77             continue;
78         }
79     }
80 
81     // Matches on the pldm StateSensorEvent signal
82     pldmEventSignal = std::make_unique<sdbusplus::bus::match_t>(
83         bus,
84         sdbusRule::type::signal() + sdbusRule::member("StateSensorEvent") +
85             sdbusRule::path("/xyz/openbmc_project/pldm") +
86             sdbusRule::interface("xyz.openbmc_project.PLDM.Event"),
87         std::bind(std::mem_fn(&SoftPowerOff::hostSoftOffComplete), this,
88                   std::placeholders::_1));
89 }
90 
91 int SoftPowerOff::getHostState()
92 {
93     try
94     {
95         pldm::utils::PropertyValue propertyValue =
96             pldm::utils::DBusHandler().getDbusPropertyVariant(
97                 "/xyz/openbmc_project/state/host0", "CurrentHostState",
98                 "xyz.openbmc_project.State.Host");
99 
100         if ((std::get<std::string>(propertyValue) !=
101              "xyz.openbmc_project.State.Host.HostState.Running") &&
102             (std::get<std::string>(propertyValue) !=
103              "xyz.openbmc_project.State.Host.HostState.TransitioningToOff"))
104         {
105             // Host state is not "Running", this app should return success
106             completed = true;
107             return PLDM_SUCCESS;
108         }
109     }
110     catch (const std::exception& e)
111     {
112         error("PLDM host soft off: Can't get current host state: {ERROR}",
113               "ERROR", e);
114         hasError = true;
115         return PLDM_ERROR;
116     }
117 
118     return PLDM_SUCCESS;
119 }
120 
121 void SoftPowerOff::hostSoftOffComplete(sdbusplus::message_t& msg)
122 {
123     pldm::pdr::TerminusID msgTID;
124     pldm::pdr::SensorID msgSensorID;
125     pldm::pdr::SensorOffset msgSensorOffset;
126     pldm::pdr::EventState msgEventState;
127     pldm::pdr::EventState msgPreviousEventState;
128 
129     // Read the msg and populate each variable
130     msg.read(msgTID, msgSensorID, msgSensorOffset, msgEventState,
131              msgPreviousEventState);
132 
133     if (msgSensorID == sensorID && msgSensorOffset == sensorOffset &&
134         msgEventState == PLDM_SW_TERM_GRACEFUL_SHUTDOWN)
135     {
136         // Receive Graceful shutdown completion event message. Disable the timer
137         auto rc = timer.stop();
138         if (rc < 0)
139         {
140             error("PLDM soft off: Failure to STOP the timer. ERRNO={RC}", "RC",
141                   rc);
142         }
143 
144         // This marks the completion of pldm soft power off.
145         completed = true;
146     }
147 }
148 
149 Json SoftPowerOff::parseConfig()
150 {
151     fs::path softoffConfigJson(fs::path(SOFTOFF_CONFIG_JSON) /
152                                "softoff_config.json");
153 
154     if (!fs::exists(softoffConfigJson) || fs::is_empty(softoffConfigJson))
155     {
156         error("Parsing softoff config JSON file failed, File does not exist");
157         return PLDM_ERROR;
158     }
159 
160     std::ifstream jsonFile(softoffConfigJson);
161     return Json::parse(jsonFile);
162 }
163 
164 bool SoftPowerOff::getEffecterID(pldm::pdr::EntityType& entityType,
165                                  pldm::pdr::StateSetId& stateSetId)
166 {
167     auto& bus = pldm::utils::DBusHandler::getBus();
168     try
169     {
170         std::vector<std::vector<uint8_t>> response{};
171         auto method = bus.new_method_call(
172             "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm",
173             "xyz.openbmc_project.PLDM.PDR", "FindStateEffecterPDR");
174         method.append(TID, entityType, stateSetId);
175         auto responseMsg = bus.call(method, dbusTimeout);
176 
177         responseMsg.read(response);
178         if (response.size())
179         {
180             for (auto& rep : response)
181             {
182                 auto softoffPdr =
183                     reinterpret_cast<pldm_state_effecter_pdr*>(rep.data());
184                 effecterID = softoffPdr->effecter_id;
185             }
186         }
187         else
188         {
189             return false;
190         }
191     }
192     catch (const sdbusplus::exception_t& e)
193     {
194         error("PLDM soft off: Error get softPowerOff PDR,ERROR={ERR_EXCEP}",
195               "ERR_EXCEP", e.what());
196         return false;
197     }
198     return true;
199 }
200 
201 int SoftPowerOff::getSensorInfo(pldm::pdr::EntityType& entityType,
202                                 pldm::pdr::StateSetId& stateSetId)
203 {
204     try
205     {
206         auto& bus = pldm::utils::DBusHandler::getBus();
207         std::vector<std::vector<uint8_t>> Response{};
208         auto method = bus.new_method_call(
209             "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm",
210             "xyz.openbmc_project.PLDM.PDR", "FindStateSensorPDR");
211         method.append(TID, entityType, stateSetId);
212 
213         auto ResponseMsg = bus.call(method, dbusTimeout);
214 
215         ResponseMsg.read(Response);
216 
217         if (Response.size() == 0)
218         {
219             error("No sensor PDR has been found that matches the criteria");
220             return PLDM_ERROR;
221         }
222 
223         pldm_state_sensor_pdr* pdr = nullptr;
224         for (auto& rep : Response)
225         {
226             pdr = reinterpret_cast<pldm_state_sensor_pdr*>(rep.data());
227             if (!pdr)
228             {
229                 error("Failed to get state sensor PDR.");
230                 return PLDM_ERROR;
231             }
232         }
233 
234         sensorID = pdr->sensor_id;
235 
236         auto compositeSensorCount = pdr->composite_sensor_count;
237         auto possibleStatesStart = pdr->possible_states;
238 
239         for (auto offset = 0; offset < compositeSensorCount; offset++)
240         {
241             auto possibleStates =
242                 reinterpret_cast<state_sensor_possible_states*>(
243                     possibleStatesStart);
244             auto setId = possibleStates->state_set_id;
245             auto possibleStateSize = possibleStates->possible_states_size;
246 
247             if (setId == PLDM_STATE_SET_SW_TERMINATION_STATUS)
248             {
249                 sensorOffset = offset;
250                 break;
251             }
252             possibleStatesStart += possibleStateSize + sizeof(setId) +
253                                    sizeof(possibleStateSize);
254         }
255     }
256     catch (const sdbusplus::exception_t& e)
257     {
258         error("PLDM soft off: Error get State Sensor PDR,ERROR={ERR_EXCEP}",
259               "ERR_EXCEP", e.what());
260         return PLDM_ERROR;
261     }
262 
263     return PLDM_SUCCESS;
264 }
265 
266 int SoftPowerOff::hostSoftOff(sdeventplus::Event& event)
267 {
268     constexpr uint8_t effecterCount = 1;
269     PldmTransport pldmTransport{};
270     uint8_t instanceID;
271     uint8_t mctpEID;
272 
273     mctpEID = pldm::utils::readHostEID();
274     // TODO: fix mapping to work around OpenBMC ecosystem deficiencies
275     pldm_tid_t pldmTID = static_cast<pldm_tid_t>(mctpEID);
276 
277     std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(effecterID) +
278                             sizeof(effecterCount) +
279                             sizeof(set_effecter_state_field)>
280         requestMsg{};
281     auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
282     set_effecter_state_field stateField{
283         PLDM_REQUEST_SET, PLDM_SW_TERM_GRACEFUL_SHUTDOWN_REQUESTED};
284     instanceID = instanceIdDb.next(pldmTID);
285     auto rc = encode_set_state_effecter_states_req(
286         instanceID, effecterID, effecterCount, &stateField, request);
287     if (rc != PLDM_SUCCESS)
288     {
289         instanceIdDb.free(pldmTID, instanceID);
290         error("Message encode failure. PLDM error code = {RC}", "RC", lg2::hex,
291               static_cast<int>(rc));
292         return PLDM_ERROR;
293     }
294 
295     // Add a timer to the event loop, default 30s.
296     auto timerCallback = [=, this](Timer& /*source*/,
297                                    Timer::TimePoint /*time*/) mutable {
298         if (!responseReceived)
299         {
300             instanceIdDb.free(pldmTID, instanceID);
301             error(
302                 "PLDM soft off: ERROR! Can't get the response for the PLDM request msg. Time out! Exit the pldm-softpoweroff");
303             exit(-1);
304         }
305         return;
306     };
307     Timer time(event, (Clock(event).now() + std::chrono::seconds{30}),
308                std::chrono::seconds{1}, std::move(timerCallback));
309 
310     // Add a callback to handle EPOLLIN on fd
311     auto callback = [=, &pldmTransport, this](IO& io, int fd,
312                                               uint32_t revents) mutable {
313         if (fd != pldmTransport.getEventSource())
314         {
315             return;
316         }
317 
318         if (!(revents & EPOLLIN))
319         {
320             return;
321         }
322 
323         void* responseMsg = nullptr;
324         size_t responseMsgSize{};
325         pldm_tid_t srcTID = pldmTID;
326 
327         auto rc = pldmTransport.recvMsg(pldmTID, responseMsg, responseMsgSize);
328         if (rc)
329         {
330             error("Soft off: failed to recv pldm data. PLDM RC = {RC}", "RC",
331                   static_cast<int>(rc));
332             return;
333         }
334 
335         std::unique_ptr<void, decltype(std::free)*> responseMsgPtr{responseMsg,
336                                                                    std::free};
337 
338         // We've got the response meant for the PLDM request msg that was
339         // sent out
340         io.set_enabled(Enabled::Off);
341         auto response = reinterpret_cast<pldm_msg*>(responseMsgPtr.get());
342 
343         if (srcTID != pldmTID ||
344             !pldm_msg_hdr_correlate_response(&request->hdr, &response->hdr))
345         {
346             /* This isn't the response we were looking for */
347             return;
348         }
349 
350         /* We have the right response, release the instance ID and process */
351         io.set_enabled(Enabled::Off);
352         instanceIdDb.free(pldmTID, instanceID);
353 
354         if (response->payload[0] != PLDM_SUCCESS)
355         {
356             error("Getting the wrong response. PLDM RC = {RC}", "RC",
357                   (unsigned)response->payload[0]);
358             exit(-1);
359         }
360 
361         responseReceived = true;
362 
363         // Start Timer
364         using namespace std::chrono;
365         auto timeMicroseconds =
366             duration_cast<microseconds>(seconds(SOFTOFF_TIMEOUT_SECONDS));
367 
368         auto ret = startTimer(timeMicroseconds);
369         if (ret < 0)
370         {
371             error(
372                 "Failure to start Host soft off wait timer, ERRNO = {RET}. Exit the pldm-softpoweroff",
373                 "RET", ret);
374             exit(-1);
375         }
376         else
377         {
378             error(
379                 "Timer started waiting for host soft off, TIMEOUT_IN_SEC = {TIMEOUT_SEC}",
380                 "TIMEOUT_SEC", SOFTOFF_TIMEOUT_SECONDS);
381         }
382         return;
383     };
384     IO io(event, pldmTransport.getEventSource(), EPOLLIN, std::move(callback));
385 
386     // Asynchronously send the PLDM request
387     rc = pldmTransport.sendMsg(pldmTID, requestMsg.data(), requestMsg.size());
388     if (0 > rc)
389     {
390         instanceIdDb.free(pldmTID, instanceID);
391         error(
392             "Failed to send message/receive response. RC = {RC}, errno = {ERR}",
393             "RC", static_cast<int>(rc), "ERR", errno);
394         return PLDM_ERROR;
395     }
396 
397     // Time out or soft off complete
398     while (!isCompleted() && !isTimerExpired())
399     {
400         try
401         {
402             event.run(std::nullopt);
403         }
404         catch (const sdeventplus::SdEventError& e)
405         {
406             instanceIdDb.free(pldmTID, instanceID);
407             error(
408                 "PLDM host soft off: Failure in processing request.ERROR= {ERR_EXCEP}",
409                 "ERR_EXCEP", e.what());
410             return PLDM_ERROR;
411         }
412     }
413 
414     return PLDM_SUCCESS;
415 }
416 
417 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec)
418 {
419     return timer.start(usec);
420 }
421 } // namespace pldm
422