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