xref: /openbmc/pldm/softoff/softoff.cpp (revision 96a3a048)
1 #include "config.h"
2 
3 #include "softoff.hpp"
4 
5 #include "libpldm/entity.h"
6 #include "libpldm/platform.h"
7 #include "libpldm/requester/pldm.h"
8 #include "libpldm/state_set.h"
9 
10 #include "common/utils.hpp"
11 
12 #include <sdbusplus/bus.hpp>
13 #include <sdeventplus/clock.hpp>
14 #include <sdeventplus/source/io.hpp>
15 #include <sdeventplus/source/time.hpp>
16 
17 #include <array>
18 #include <iostream>
19 
20 namespace pldm
21 {
22 
23 using namespace sdeventplus;
24 using namespace sdeventplus::source;
25 constexpr auto clockId = sdeventplus::ClockId::RealTime;
26 using Clock = Clock<clockId>;
27 using Timer = Time<clockId>;
28 
29 using sdbusplus::exception::SdBusError;
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::bus& bus, sd_event* event) :
35     bus(bus), timer(event)
36 {
37     auto rc = getHostState();
38     if (hasError || completed)
39     {
40         return;
41     }
42 
43     rc = getEffecterID();
44     if (completed)
45     {
46         std::cerr
47             << "pldm-softpoweroff: effecter to initiate softoff not found \n";
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         std::cerr << "Message get Sensor PDRs error. PLDM "
60                      "error code = "
61                   << std::hex << std::showbase << rc << "\n";
62         hasError = true;
63         return;
64     }
65 
66     // Matches on the pldm StateSensorEvent signal
67     pldmEventSignal = std::make_unique<sdbusplus::bus::match_t>(
68         bus,
69         sdbusRule::type::signal() + sdbusRule::member("StateSensorEvent") +
70             sdbusRule::path("/xyz/openbmc_project/pldm") +
71             sdbusRule::interface("xyz.openbmc_project.PLDM.Event"),
72         std::bind(std::mem_fn(&SoftPowerOff::hostSoftOffComplete), this,
73                   std::placeholders::_1));
74 }
75 
76 int SoftPowerOff::getHostState()
77 {
78     try
79     {
80         pldm::utils::PropertyValue propertyValue =
81             pldm::utils::DBusHandler().getDbusPropertyVariant(
82                 "/xyz/openbmc_project/state/host0", "CurrentHostState",
83                 "xyz.openbmc_project.State.Host");
84 
85         if ((std::get<std::string>(propertyValue) !=
86              "xyz.openbmc_project.State.Host.HostState.Running") &&
87             (std::get<std::string>(propertyValue) !=
88              "xyz.openbmc_project.State.Host.HostState.TransitioningToOff"))
89         {
90             // Host state is not "Running", this app should return success
91             completed = true;
92             return PLDM_SUCCESS;
93         }
94     }
95     catch (const std::exception& e)
96     {
97         std::cerr << "PLDM host soft off: Can't get current host state.\n";
98         hasError = true;
99         return PLDM_ERROR;
100     }
101 
102     return PLDM_SUCCESS;
103 }
104 
105 void SoftPowerOff::hostSoftOffComplete(sdbusplus::message::message& 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             std::cerr << "PLDM soft off: Failure to STOP the timer. ERRNO="
125                       << rc << "\n";
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);
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 SdBusError& e)
167     {
168         std::cerr << "PLDM soft off: Error get VMM PDR,ERROR=" << e.what()
169                   << "\n";
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);
192 
193         sysFwResponseMsg.read(sysFwResponse);
194 
195         if (sysFwResponse.size() == 0)
196         {
197             std::cerr
198                 << "No effecter ID has been found that matches the criteria"
199                 << "\n";
200             return PLDM_ERROR;
201         }
202 
203         for (auto& rep : sysFwResponse)
204         {
205             auto sysFwPdr =
206                 reinterpret_cast<pldm_state_effecter_pdr*>(rep.data());
207             effecterID = sysFwPdr->effecter_id;
208         }
209     }
210     catch (const SdBusError& e)
211     {
212         std::cerr << "PLDM soft off: Error get system firmware PDR,ERROR="
213                   << e.what() << "\n";
214         completed = true;
215         return PLDM_ERROR;
216     }
217 
218     return PLDM_SUCCESS;
219 }
220 
221 int SoftPowerOff::getSensorInfo()
222 {
223     pldm::pdr::EntityType entityType;
224 
225     entityType = VMMPdrExist ? PLDM_ENTITY_VIRTUAL_MACHINE_MANAGER
226                              : PLDM_ENTITY_SYS_FIRMWARE;
227 
228     // The Virtual machine manager/System firmware is logical entity, so bit 15
229     // need to be set.
230     entityType = entityType | 0x8000;
231 
232     try
233     {
234         auto& bus = pldm::utils::DBusHandler::getBus();
235         std::vector<std::vector<uint8_t>> Response{};
236         auto method = bus.new_method_call(
237             "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm",
238             "xyz.openbmc_project.PLDM.PDR", "FindStateSensorPDR");
239         method.append(TID, entityType,
240                       (uint16_t)PLDM_STATE_SET_SW_TERMINATION_STATUS);
241 
242         auto ResponseMsg = bus.call(method);
243 
244         ResponseMsg.read(Response);
245 
246         if (Response.size() == 0)
247         {
248             std::cerr
249                 << "No sensor PDR has been found that matches the criteria"
250                 << "\n";
251             return PLDM_ERROR;
252         }
253 
254         pldm_state_sensor_pdr* pdr;
255         for (auto& rep : Response)
256         {
257             pdr = reinterpret_cast<pldm_state_sensor_pdr*>(rep.data());
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 +=
279                 possibleStateSize + sizeof(setId) + sizeof(possibleStateSize);
280         }
281     }
282     catch (const SdBusError& e)
283     {
284         std::cerr << "PLDM soft off: Error get State Sensor PDR,ERROR="
285                   << e.what() << "\n";
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 SdBusError& e)
314     {
315         std::cerr << "PLDM soft off: Error get instanceID,ERROR=" << e.what()
316                   << "\n";
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         std::cerr << "Message encode failure. PLDM error code = " << std::hex
332                   << std::showbase << rc << "\n";
333         return PLDM_ERROR;
334     }
335 
336     // Open connection to MCTP socket
337     int fd = pldm_open();
338     if (-1 == fd)
339     {
340         std::cerr << "Failed to connect to mctp demux daemon"
341                   << "\n";
342         return PLDM_ERROR;
343     }
344 
345     // Add a timer to the event loop, default 30s.
346     auto timerCallback = [=](Timer& /*source*/, Timer::TimePoint /*time*/) {
347         if (!responseReceived)
348         {
349             std::cerr << "PLDM soft off: ERROR! Can't get the response for the "
350                          "PLDM request msg. Time out!\n"
351                       << "Exit the pldm-softpoweroff\n";
352             exit(-1);
353         }
354         return;
355     };
356     Timer time(event, (Clock(event).now() + std::chrono::seconds{30}),
357                std::chrono::seconds{1}, std::move(timerCallback));
358 
359     // Add a callback to handle EPOLLIN on fd
360     auto callback = [=](IO& io, int fd, uint32_t revents) {
361         if (!(revents & EPOLLIN))
362         {
363             return;
364         }
365 
366         uint8_t* responseMsg = nullptr;
367         size_t responseMsgSize{};
368 
369         auto rc = pldm_recv(mctpEID, fd, request->hdr.instance_id, &responseMsg,
370                             &responseMsgSize);
371         if (rc)
372         {
373             return;
374         }
375 
376         std::unique_ptr<uint8_t, decltype(std::free)*> responseMsgPtr{
377             responseMsg, std::free};
378 
379         // We've got the response meant for the PLDM request msg that was
380         // sent out
381         io.set_enabled(Enabled::Off);
382         auto response = reinterpret_cast<pldm_msg*>(responseMsgPtr.get());
383         std::cerr << "Getting the response. PLDM RC = " << std::hex
384                   << std::showbase
385                   << static_cast<uint16_t>(response->payload[0]) << "\n";
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             std::cerr << "Failure to start Host soft off wait timer, ERRNO = "
398                       << ret << "Exit the pldm-softpoweroff\n";
399             exit(-1);
400         }
401         else
402         {
403             std::cerr << "Timer started waiting for host soft off, "
404                          "TIMEOUT_IN_SEC = "
405                       << SOFTOFF_TIMEOUT_SECONDS << "\n";
406         }
407         return;
408     };
409     IO io(event, fd, EPOLLIN, std::move(callback));
410 
411     // Send PLDM Request message - pldm_send doesn't wait for response
412     rc = pldm_send(mctpEID, fd, requestMsg.data(), requestMsg.size());
413     if (0 > rc)
414     {
415         std::cerr << "Failed to send message/receive response. RC = " << rc
416                   << ", errno = " << errno << "\n";
417         return PLDM_ERROR;
418     }
419 
420     // Time out or soft off complete
421     while (!isCompleted() && !isTimerExpired())
422     {
423         try
424         {
425             event.run(std::nullopt);
426         }
427         catch (const sdeventplus::SdEventError& e)
428         {
429             std::cerr
430                 << "PLDM host soft off: Failure in processing request.ERROR= "
431                 << e.what() << "\n";
432             return PLDM_ERROR;
433         }
434     }
435 
436     return PLDM_SUCCESS;
437 }
438 
439 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec)
440 {
441     return timer.start(usec);
442 }
443 } // namespace pldm
444