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