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