xref: /openbmc/pldm/softoff/softoff.cpp (revision a7dbca53)
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(
147             VMMMethod,
148             std::chrono::duration_cast<microsec>(sec(DBUS_TIMEOUT)).count());
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(
191             sysFwMethod,
192             std::chrono::duration_cast<microsec>(sec(DBUS_TIMEOUT)).count());
193 
194         sysFwResponseMsg.read(sysFwResponse);
195 
196         if (sysFwResponse.size() == 0)
197         {
198             error("No effecter ID has been found that matches the criteria");
199             return PLDM_ERROR;
200         }
201 
202         for (auto& rep : sysFwResponse)
203         {
204             auto sysFwPdr =
205                 reinterpret_cast<pldm_state_effecter_pdr*>(rep.data());
206             effecterID = sysFwPdr->effecter_id;
207         }
208     }
209     catch (const sdbusplus::exception_t& e)
210     {
211         error("PLDM soft off: Error get system firmware PDR,ERROR={ERR_EXCEP}",
212               "ERR_EXCEP", e.what());
213         completed = true;
214         return PLDM_ERROR;
215     }
216 
217     return PLDM_SUCCESS;
218 }
219 
220 int SoftPowerOff::getSensorInfo()
221 {
222     pldm::pdr::EntityType entityType;
223 
224     entityType = VMMPdrExist ? PLDM_ENTITY_VIRTUAL_MACHINE_MANAGER
225                              : PLDM_ENTITY_SYS_FIRMWARE;
226 
227     // The Virtual machine manager/System firmware is logical entity, so bit 15
228     // need to be set.
229     entityType = entityType | 0x8000;
230 
231     try
232     {
233         auto& bus = pldm::utils::DBusHandler::getBus();
234         std::vector<std::vector<uint8_t>> Response{};
235         auto method = bus.new_method_call(
236             "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm",
237             "xyz.openbmc_project.PLDM.PDR", "FindStateSensorPDR");
238         method.append(TID, entityType,
239                       (uint16_t)PLDM_STATE_SET_SW_TERMINATION_STATUS);
240 
241         auto ResponseMsg = bus.call(
242             method,
243             std::chrono::duration_cast<microsec>(sec(DBUS_TIMEOUT)).count());
244 
245         ResponseMsg.read(Response);
246 
247         if (Response.size() == 0)
248         {
249             error("No sensor PDR has been found that matches the criteria");
250             return PLDM_ERROR;
251         }
252 
253         pldm_state_sensor_pdr* pdr = nullptr;
254         for (auto& rep : Response)
255         {
256             pdr = reinterpret_cast<pldm_state_sensor_pdr*>(rep.data());
257             if (!pdr)
258             {
259                 error("Failed to get state sensor PDR.");
260                 return PLDM_ERROR;
261             }
262         }
263 
264         sensorID = pdr->sensor_id;
265 
266         auto compositeSensorCount = pdr->composite_sensor_count;
267         auto possibleStatesStart = pdr->possible_states;
268 
269         for (auto offset = 0; offset < compositeSensorCount; offset++)
270         {
271             auto possibleStates =
272                 reinterpret_cast<state_sensor_possible_states*>(
273                     possibleStatesStart);
274             auto setId = possibleStates->state_set_id;
275             auto possibleStateSize = possibleStates->possible_states_size;
276 
277             if (setId == PLDM_STATE_SET_SW_TERMINATION_STATUS)
278             {
279                 sensorOffset = offset;
280                 break;
281             }
282             possibleStatesStart += possibleStateSize + sizeof(setId) +
283                                    sizeof(possibleStateSize);
284         }
285     }
286     catch (const sdbusplus::exception_t& e)
287     {
288         error("PLDM soft off: Error get State Sensor PDR,ERROR={ERR_EXCEP}",
289               "ERR_EXCEP", e.what());
290         return PLDM_ERROR;
291     }
292 
293     return PLDM_SUCCESS;
294 }
295 
296 int SoftPowerOff::hostSoftOff(sdeventplus::Event& event)
297 {
298     constexpr uint8_t effecterCount = 1;
299     uint8_t mctpEID;
300     uint8_t instanceID;
301 
302     mctpEID = pldm::utils::readHostEID();
303 
304     // Get instanceID
305     try
306     {
307         auto& bus = pldm::utils::DBusHandler::getBus();
308         auto method = bus.new_method_call(
309             "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm",
310             "xyz.openbmc_project.PLDM.Requester", "GetInstanceId");
311         method.append(mctpEID);
312 
313         auto ResponseMsg = bus.call(
314             method,
315             std::chrono::duration_cast<microsec>(sec(DBUS_TIMEOUT)).count());
316 
317         ResponseMsg.read(instanceID);
318     }
319     catch (const sdbusplus::exception_t& e)
320     {
321         error("PLDM soft off: Error get instanceID,ERROR={ERR_EXCEP}",
322               "ERR_EXCEP", e.what());
323         return PLDM_ERROR;
324     }
325 
326     std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(effecterID) +
327                             sizeof(effecterCount) +
328                             sizeof(set_effecter_state_field)>
329         requestMsg{};
330     auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
331     set_effecter_state_field stateField{
332         PLDM_REQUEST_SET, PLDM_SW_TERM_GRACEFUL_SHUTDOWN_REQUESTED};
333     auto rc = encode_set_state_effecter_states_req(
334         instanceID, effecterID, effecterCount, &stateField, request);
335     if (rc != PLDM_SUCCESS)
336     {
337         error("Message encode failure. PLDM error code = {RC}", "RC", lg2::hex,
338               static_cast<int>(rc));
339         return PLDM_ERROR;
340     }
341 
342     // Open connection to MCTP socket
343     int fd = pldm_open();
344     if (-1 == fd)
345     {
346         error("Failed to connect to mctp demux daemon");
347         return PLDM_ERROR;
348     }
349 
350     // Add a timer to the event loop, default 30s.
351     auto timerCallback =
352         [=, this](Timer& /*source*/, Timer::TimePoint /*time*/) {
353         if (!responseReceived)
354         {
355             error(
356                 "PLDM soft off: ERROR! Can't get the response for the PLDM request msg. Time out! Exit the pldm-softpoweroff");
357             exit(-1);
358         }
359         return;
360     };
361     Timer time(event, (Clock(event).now() + std::chrono::seconds{30}),
362                std::chrono::seconds{1}, std::move(timerCallback));
363 
364     // Add a callback to handle EPOLLIN on fd
365     auto callback = [=, this](IO& io, int fd, uint32_t revents) {
366         if (!(revents & EPOLLIN))
367         {
368             return;
369         }
370 
371         uint8_t* responseMsg = nullptr;
372         size_t responseMsgSize{};
373 
374         auto rc = pldm_recv(mctpEID, fd, request->hdr.instance_id, &responseMsg,
375                             &responseMsgSize);
376         if (rc)
377         {
378             error("Soft off: failed to recv pldm data. PLDM RC = {RC}", "RC",
379                   static_cast<int>(rc));
380             return;
381         }
382 
383         std::unique_ptr<uint8_t, decltype(std::free)*> responseMsgPtr{
384             responseMsg, std::free};
385 
386         // We've got the response meant for the PLDM request msg that was
387         // sent out
388         io.set_enabled(Enabled::Off);
389         auto response = reinterpret_cast<pldm_msg*>(responseMsgPtr.get());
390         if (response->payload[0] != PLDM_SUCCESS)
391         {
392             error("Getting the wrong response. PLDM RC = {RC}", "RC",
393                   (unsigned)response->payload[0]);
394             exit(-1);
395         }
396 
397         responseReceived = true;
398 
399         // Start Timer
400         using namespace std::chrono;
401         auto timeMicroseconds =
402             duration_cast<microseconds>(seconds(SOFTOFF_TIMEOUT_SECONDS));
403 
404         auto ret = startTimer(timeMicroseconds);
405         if (ret < 0)
406         {
407             error(
408                 "Failure to start Host soft off wait timer, ERRNO = {RET}. Exit the pldm-softpoweroff",
409                 "RET", ret);
410             exit(-1);
411         }
412         else
413         {
414             error(
415                 "Timer started waiting for host soft off, TIMEOUT_IN_SEC = {TIMEOUT_SEC}",
416                 "TIMEOUT_SEC", SOFTOFF_TIMEOUT_SECONDS);
417         }
418         return;
419     };
420     IO io(event, fd, EPOLLIN, std::move(callback));
421 
422     // Send PLDM Request message - pldm_send doesn't wait for response
423     rc = pldm_send(mctpEID, fd, requestMsg.data(), requestMsg.size());
424     if (0 > rc)
425     {
426         error(
427             "Failed to send message/receive response. RC = {RC}, errno = {ERR}",
428             "RC", static_cast<int>(rc), "ERR", errno);
429         return PLDM_ERROR;
430     }
431 
432     // Time out or soft off complete
433     while (!isCompleted() && !isTimerExpired())
434     {
435         try
436         {
437             event.run(std::nullopt);
438         }
439         catch (const sdeventplus::SdEventError& e)
440         {
441             error(
442                 "PLDM host soft off: Failure in processing request.ERROR= {ERR_EXCEP}",
443                 "ERR_EXCEP", e.what());
444             return PLDM_ERROR;
445         }
446     }
447 
448     return PLDM_SUCCESS;
449 }
450 
451 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec)
452 {
453     return timer.start(usec);
454 }
455 } // namespace pldm
456