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