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