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