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