xref: /openbmc/pldm/softoff/softoff.cpp (revision a114c1075310e5fa24211a4796fe1ed25b828d69)
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 constexpr pldm::pdr::TerminusID TID = 0; // TID will be implemented later.
31 namespace sdbusRule = sdbusplus::bus::match::rules;
32 
33 SoftPowerOff::SoftPowerOff(sdbusplus::bus::bus& bus, sd_event* event) :
34     bus(bus), timer(event)
35 {
36     auto rc = getHostState();
37     if (hasError || completed)
38     {
39         return;
40     }
41 
42     rc = getEffecterID();
43     if (completed)
44     {
45         std::cerr
46             << "pldm-softpoweroff: effecter to initiate softoff not found \n";
47         return;
48     }
49     else if (rc != PLDM_SUCCESS)
50     {
51         hasError = true;
52         return;
53     }
54 
55     rc = getSensorInfo();
56     if (rc != PLDM_SUCCESS)
57     {
58         std::cerr << "Message get Sensor PDRs error. PLDM "
59                      "error code = "
60                   << std::hex << std::showbase << rc << "\n";
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         std::cerr << "PLDM host soft off: Can't get current host state.\n";
97         hasError = true;
98         return PLDM_ERROR;
99     }
100 
101     return PLDM_SUCCESS;
102 }
103 
104 void SoftPowerOff::hostSoftOffComplete(sdbusplus::message::message& msg)
105 {
106     pldm::pdr::TerminusID msgTID;
107     pldm::pdr::SensorID msgSensorID;
108     pldm::pdr::SensorOffset msgSensorOffset;
109     pldm::pdr::EventState msgEventState;
110     pldm::pdr::EventState msgPreviousEventState;
111 
112     // Read the msg and populate each variable
113     msg.read(msgTID, msgSensorID, msgSensorOffset, msgEventState,
114              msgPreviousEventState);
115 
116     if (msgSensorID == sensorID && msgSensorOffset == sensorOffset &&
117         msgEventState == PLDM_SW_TERM_GRACEFUL_SHUTDOWN)
118     {
119         // Receive Graceful shutdown completion event message. Disable the timer
120         auto rc = timer.stop();
121         if (rc < 0)
122         {
123             std::cerr << "PLDM soft off: Failure to STOP the timer. ERRNO="
124                       << rc << "\n";
125         }
126 
127         // This marks the completion of pldm soft power off.
128         completed = true;
129     }
130 }
131 
132 int SoftPowerOff::getEffecterID()
133 {
134     auto& bus = pldm::utils::DBusHandler::getBus();
135 
136     // VMM is a logical entity, so the bit 15 in entity type is set.
137     pdr::EntityType entityType = PLDM_ENTITY_VIRTUAL_MACHINE_MANAGER | 0x8000;
138 
139     try
140     {
141         std::vector<std::vector<uint8_t>> VMMResponse{};
142         auto VMMMethod = bus.new_method_call(
143             "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm",
144             "xyz.openbmc_project.PLDM.PDR", "FindStateEffecterPDR");
145         VMMMethod.append(TID, entityType,
146                          (uint16_t)PLDM_STATE_SET_SW_TERMINATION_STATUS);
147 
148         auto VMMResponseMsg = bus.call(VMMMethod);
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::exception& e)
166     {
167         std::cerr << "PLDM soft off: Error get VMM PDR,ERROR=" << e.what()
168                   << "\n";
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(sysFwMethod);
191 
192         sysFwResponseMsg.read(sysFwResponse);
193 
194         if (sysFwResponse.size() == 0)
195         {
196             std::cerr
197                 << "No effecter ID has been found that matches the criteria"
198                 << "\n";
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::exception& e)
210     {
211         std::cerr << "PLDM soft off: Error get system firmware PDR,ERROR="
212                   << e.what() << "\n";
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(method);
242 
243         ResponseMsg.read(Response);
244 
245         if (Response.size() == 0)
246         {
247             std::cerr
248                 << "No sensor PDR has been found that matches the criteria"
249                 << "\n";
250             return PLDM_ERROR;
251         }
252 
253         pldm_state_sensor_pdr* pdr;
254         for (auto& rep : Response)
255         {
256             pdr = reinterpret_cast<pldm_state_sensor_pdr*>(rep.data());
257         }
258 
259         if (!pdr)
260         {
261             std::cerr << "Failed to get state sensor PDR.\n";
262             return PLDM_ERROR;
263         }
264 
265         sensorID = pdr->sensor_id;
266 
267         auto compositeSensorCount = pdr->composite_sensor_count;
268         auto possibleStatesStart = pdr->possible_states;
269 
270         for (auto offset = 0; offset < compositeSensorCount; offset++)
271         {
272             auto possibleStates =
273                 reinterpret_cast<state_sensor_possible_states*>(
274                     possibleStatesStart);
275             auto setId = possibleStates->state_set_id;
276             auto possibleStateSize = possibleStates->possible_states_size;
277 
278             if (setId == PLDM_STATE_SET_SW_TERMINATION_STATUS)
279             {
280                 sensorOffset = offset;
281                 break;
282             }
283             possibleStatesStart +=
284                 possibleStateSize + sizeof(setId) + sizeof(possibleStateSize);
285         }
286     }
287     catch (const sdbusplus::exception::exception& e)
288     {
289         std::cerr << "PLDM soft off: Error get State Sensor PDR,ERROR="
290                   << e.what() << "\n";
291         return PLDM_ERROR;
292     }
293 
294     return PLDM_SUCCESS;
295 }
296 
297 int SoftPowerOff::hostSoftOff(sdeventplus::Event& event)
298 {
299     constexpr uint8_t effecterCount = 1;
300     uint8_t mctpEID;
301     uint8_t instanceID;
302 
303     mctpEID = pldm::utils::readHostEID();
304 
305     // Get instanceID
306     try
307     {
308         auto& bus = pldm::utils::DBusHandler::getBus();
309         auto method = bus.new_method_call(
310             "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm",
311             "xyz.openbmc_project.PLDM.Requester", "GetInstanceId");
312         method.append(mctpEID);
313 
314         auto ResponseMsg = bus.call(method);
315 
316         ResponseMsg.read(instanceID);
317     }
318     catch (const sdbusplus::exception::exception& e)
319     {
320         std::cerr << "PLDM soft off: Error get instanceID,ERROR=" << e.what()
321                   << "\n";
322         return PLDM_ERROR;
323     }
324 
325     std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(effecterID) +
326                             sizeof(effecterCount) +
327                             sizeof(set_effecter_state_field)>
328         requestMsg{};
329     auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
330     set_effecter_state_field stateField{
331         PLDM_REQUEST_SET, PLDM_SW_TERM_GRACEFUL_SHUTDOWN_REQUESTED};
332     auto rc = encode_set_state_effecter_states_req(
333         instanceID, effecterID, effecterCount, &stateField, request);
334     if (rc != PLDM_SUCCESS)
335     {
336         std::cerr << "Message encode failure. PLDM error code = " << std::hex
337                   << std::showbase << rc << "\n";
338         return PLDM_ERROR;
339     }
340 
341     // Open connection to MCTP socket
342     int fd = pldm_open();
343     if (-1 == fd)
344     {
345         std::cerr << "Failed to connect to mctp demux daemon"
346                   << "\n";
347         return PLDM_ERROR;
348     }
349 
350     // Add a timer to the event loop, default 30s.
351     auto timerCallback = [=, this](Timer& /*source*/,
352                                    Timer::TimePoint /*time*/) {
353         if (!responseReceived)
354         {
355             std::cerr << "PLDM soft off: ERROR! Can't get the response for the "
356                          "PLDM request msg. Time out!\n"
357                       << "Exit the pldm-softpoweroff\n";
358             exit(-1);
359         }
360         return;
361     };
362     Timer time(event, (Clock(event).now() + std::chrono::seconds{30}),
363                std::chrono::seconds{1}, std::move(timerCallback));
364 
365     // Add a callback to handle EPOLLIN on fd
366     auto callback = [=, this](IO& io, int fd, uint32_t revents) {
367         if (!(revents & EPOLLIN))
368         {
369             return;
370         }
371 
372         uint8_t* responseMsg = nullptr;
373         size_t responseMsgSize{};
374 
375         auto rc = pldm_recv(mctpEID, fd, request->hdr.instance_id, &responseMsg,
376                             &responseMsgSize);
377         if (rc)
378         {
379             return;
380         }
381 
382         std::unique_ptr<uint8_t, decltype(std::free)*> responseMsgPtr{
383             responseMsg, std::free};
384 
385         // We've got the response meant for the PLDM request msg that was
386         // sent out
387         io.set_enabled(Enabled::Off);
388         auto response = reinterpret_cast<pldm_msg*>(responseMsgPtr.get());
389         std::cerr << "Getting the response. PLDM RC = " << std::hex
390                   << std::showbase
391                   << static_cast<uint16_t>(response->payload[0]) << "\n";
392 
393         responseReceived = true;
394 
395         // Start Timer
396         using namespace std::chrono;
397         auto timeMicroseconds =
398             duration_cast<microseconds>(seconds(SOFTOFF_TIMEOUT_SECONDS));
399 
400         auto ret = startTimer(timeMicroseconds);
401         if (ret < 0)
402         {
403             std::cerr << "Failure to start Host soft off wait timer, ERRNO = "
404                       << ret << "Exit the pldm-softpoweroff\n";
405             exit(-1);
406         }
407         else
408         {
409             std::cerr << "Timer started waiting for host soft off, "
410                          "TIMEOUT_IN_SEC = "
411                       << SOFTOFF_TIMEOUT_SECONDS << "\n";
412         }
413         return;
414     };
415     IO io(event, fd, EPOLLIN, std::move(callback));
416 
417     // Send PLDM Request message - pldm_send doesn't wait for response
418     rc = pldm_send(mctpEID, fd, requestMsg.data(), requestMsg.size());
419     if (0 > rc)
420     {
421         std::cerr << "Failed to send message/receive response. RC = " << rc
422                   << ", errno = " << errno << "\n";
423         return PLDM_ERROR;
424     }
425 
426     // Time out or soft off complete
427     while (!isCompleted() && !isTimerExpired())
428     {
429         try
430         {
431             event.run(std::nullopt);
432         }
433         catch (const sdeventplus::SdEventError& e)
434         {
435             std::cerr
436                 << "PLDM host soft off: Failure in processing request.ERROR= "
437                 << e.what() << "\n";
438             return PLDM_ERROR;
439         }
440     }
441 
442     return PLDM_SUCCESS;
443 }
444 
445 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec)
446 {
447     return timer.start(usec);
448 }
449 } // namespace pldm
450