xref: /openbmc/pldm/softoff/softoff.cpp (revision 505542588031c26ce7918ad4d35aacf6bc3b80b9)
1 #include "softoff.hpp"
2 
3 #include "common/instance_id.hpp"
4 #include "common/transport.hpp"
5 #include "common/utils.hpp"
6 
7 #include <libpldm/entity.h>
8 #include <libpldm/platform.h>
9 #include <libpldm/state_set.h>
10 
11 #include <phosphor-logging/lg2.hpp>
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 #include <xyz/openbmc_project/State/Host/common.hpp>
18 
19 #include <array>
20 #include <filesystem>
21 #include <fstream>
22 
23 PHOSPHOR_LOG2_USING;
24 
25 namespace pldm
26 {
27 using namespace sdeventplus;
28 using namespace sdeventplus::source;
29 namespace fs = std::filesystem;
30 constexpr auto clockId = sdeventplus::ClockId::RealTime;
31 using Clock = Clock<clockId>;
32 using Timer = Time<clockId>;
33 
34 using sdbusplus::exception::SdBusError;
35 
36 // Shutdown effecter terminus ID, set when we look up the effecter
37 pldm::pdr::TerminusID TID = 0;
38 
39 namespace sdbusRule = sdbusplus::bus::match::rules;
40 
SoftPowerOff(sdbusplus::bus_t & bus,sd_event * event,pldm::InstanceIdDb & instanceIdDb)41 SoftPowerOff::SoftPowerOff(sdbusplus::bus_t& bus, sd_event* event,
42                            pldm::InstanceIdDb& instanceIdDb) :
43     timer(event), instanceIdDb(instanceIdDb)
44 {
45     auto jsonData = parseConfig();
46 
47     if (jsonData.is_discarded())
48     {
49         error("Failed to parse softoff config JSON file");
50         return;
51     }
52 
53     getHostState();
54     if (hasError || completed)
55     {
56         return;
57     }
58     const std::vector<Json> emptyJsonList{};
59     auto entries = jsonData.value("entries", emptyJsonList);
60     for (const auto& entry : entries)
61     {
62         TID = entry.value("tid", 0);
63         pldm::pdr::EntityType entityType = entry.value("entityType", 0);
64         pldm::pdr::StateSetId stateSetId = entry.value("stateSetId", 0);
65 
66         bool effecterFound = getEffecterID(entityType, stateSetId);
67         if (effecterFound)
68         {
69             auto rc = getSensorInfo(entityType, stateSetId);
70             if (rc != PLDM_SUCCESS)
71             {
72                 error("Failed to get Sensor PDRs, response code '{RC}'", "RC",
73                       lg2::hex, rc);
74                 hasError = true;
75                 return;
76             }
77             break;
78         }
79         else
80         {
81             continue;
82         }
83     }
84 
85     // Matches on the pldm StateSensorEvent signal
86     pldmEventSignal = std::make_unique<sdbusplus::bus::match_t>(
87         bus,
88         sdbusRule::type::signal() + sdbusRule::member("StateSensorEvent") +
89             sdbusRule::path("/xyz/openbmc_project/pldm") +
90             sdbusRule::interface("xyz.openbmc_project.PLDM.Event"),
91         std::bind(std::mem_fn(&SoftPowerOff::hostSoftOffComplete), this,
92                   std::placeholders::_1));
93 }
94 
getHostState()95 int SoftPowerOff::getHostState()
96 {
97     try
98     {
99         pldm::utils::PropertyValue propertyValue =
100             pldm::utils::DBusHandler().getDbusPropertyVariant(
101                 "/xyz/openbmc_project/state/host0", "CurrentHostState",
102                 "xyz.openbmc_project.State.Host");
103 
104         if ((std::get<std::string>(propertyValue) !=
105              "xyz.openbmc_project.State.Host.HostState.Running") &&
106             (std::get<std::string>(propertyValue) !=
107              "xyz.openbmc_project.State.Host.HostState.TransitioningToOff"))
108         {
109             // Host state is not "Running", this app should return success
110             completed = true;
111             return PLDM_SUCCESS;
112         }
113     }
114     catch (const std::exception& e)
115     {
116         error(
117             "PLDM remote terminus soft off. Can't get current remote terminus state, error - {ERROR}",
118             "ERROR", e);
119         hasError = true;
120         return PLDM_ERROR;
121     }
122 
123     return PLDM_SUCCESS;
124 }
125 
hostSoftOffComplete(sdbusplus::message_t & msg)126 void SoftPowerOff::hostSoftOffComplete(sdbusplus::message_t& msg)
127 {
128     pldm::pdr::TerminusID msgTID;
129     pldm::pdr::SensorID msgSensorID;
130     pldm::pdr::SensorOffset msgSensorOffset;
131     pldm::pdr::EventState msgEventState;
132     pldm::pdr::EventState msgPreviousEventState;
133 
134     // Read the msg and populate each variable
135     msg.read(msgTID, msgSensorID, msgSensorOffset, msgEventState,
136              msgPreviousEventState);
137 
138     if (msgSensorID == sensorID && msgSensorOffset == sensorOffset &&
139         msgEventState == PLDM_SW_TERM_GRACEFUL_SHUTDOWN && msgTID == TID)
140     {
141         // Receive Graceful shutdown completion event message. Disable the timer
142         auto rc = timer.stop();
143         if (rc < 0)
144         {
145             error(
146                 "Failure to STOP the timer of PLDM soft off, response code '{RC}'",
147                 "RC", rc);
148         }
149 
150         // This marks the completion of pldm soft power off.
151         completed = true;
152     }
153 }
154 
parseConfig()155 Json SoftPowerOff::parseConfig()
156 {
157     fs::path softoffConfigJson(
158         fs::path(SOFTOFF_CONFIG_JSON) / "softoff_config.json");
159 
160     if (!fs::exists(softoffConfigJson) || fs::is_empty(softoffConfigJson))
161     {
162         error(
163             "Failed to parse softoff config JSON file '{PATH}', file does not exist",
164             "PATH", softoffConfigJson);
165         return PLDM_ERROR;
166     }
167 
168     std::ifstream jsonFile(softoffConfigJson);
169     return Json::parse(jsonFile);
170 }
171 
getEffecterID(pldm::pdr::EntityType & entityType,pldm::pdr::StateSetId & stateSetId)172 bool SoftPowerOff::getEffecterID(pldm::pdr::EntityType& entityType,
173                                  pldm::pdr::StateSetId& stateSetId)
174 {
175     auto& bus = pldm::utils::DBusHandler::getBus();
176     try
177     {
178         std::vector<std::vector<uint8_t>> response{};
179         auto method = bus.new_method_call(
180             "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm",
181             "xyz.openbmc_project.PLDM.PDR", "FindStateEffecterPDR");
182         method.append(TID, entityType, stateSetId);
183         auto responseMsg = bus.call(method, dbusTimeout);
184 
185         responseMsg.read(response);
186         if (response.size())
187         {
188             for (auto& rep : response)
189             {
190                 auto softoffPdr = new (rep.data()) pldm_state_effecter_pdr;
191                 effecterID = softoffPdr->effecter_id;
192             }
193         }
194         else
195         {
196             return false;
197         }
198     }
199     catch (const sdbusplus::exception_t& e)
200     {
201         error("Failed to get softPowerOff PDR, error - {ERROR}", "ERROR", e);
202         return false;
203     }
204     return true;
205 }
206 
getSensorInfo(pldm::pdr::EntityType & entityType,pldm::pdr::StateSetId & stateSetId)207 int SoftPowerOff::getSensorInfo(pldm::pdr::EntityType& entityType,
208                                 pldm::pdr::StateSetId& stateSetId)
209 {
210     try
211     {
212         auto& bus = pldm::utils::DBusHandler::getBus();
213         std::vector<std::vector<uint8_t>> Response{};
214         auto method = bus.new_method_call(
215             "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm",
216             "xyz.openbmc_project.PLDM.PDR", "FindStateSensorPDR");
217         method.append(TID, entityType, stateSetId);
218 
219         auto ResponseMsg = bus.call(method, dbusTimeout);
220 
221         ResponseMsg.read(Response);
222 
223         if (Response.size() == 0)
224         {
225             error("No sensor PDR has been found that matches the criteria");
226             return PLDM_ERROR;
227         }
228 
229         pldm_state_sensor_pdr* pdr = nullptr;
230         for (auto& rep : Response)
231         {
232             pdr = new (rep.data()) pldm_state_sensor_pdr;
233             if (!pdr)
234             {
235                 error("Failed to get state sensor PDR.");
236                 return PLDM_ERROR;
237             }
238         }
239 
240         sensorID = pdr->sensor_id;
241 
242         auto compositeSensorCount = pdr->composite_sensor_count;
243         auto possibleStatesStart = pdr->possible_states;
244 
245         for (auto offset = 0; offset < compositeSensorCount; offset++)
246         {
247             auto possibleStates = new (possibleStatesStart)
248                 state_sensor_possible_states;
249             auto setId = possibleStates->state_set_id;
250             auto possibleStateSize = possibleStates->possible_states_size;
251 
252             if (setId == PLDM_STATE_SET_SW_TERMINATION_STATUS)
253             {
254                 sensorOffset = offset;
255                 break;
256             }
257             possibleStatesStart +=
258                 possibleStateSize + sizeof(setId) + sizeof(possibleStateSize);
259         }
260     }
261     catch (const sdbusplus::exception_t& e)
262     {
263         error("Failed to get state sensor PDR during soft-off, error - {ERROR}",
264               "ERROR", e);
265         return PLDM_ERROR;
266     }
267 
268     return PLDM_SUCCESS;
269 }
270 
hostSoftOff(sdeventplus::Event & event)271 int SoftPowerOff::hostSoftOff(sdeventplus::Event& event)
272 {
273     constexpr uint8_t effecterCount = 1;
274     PldmTransport pldmTransport{};
275     uint8_t instanceID;
276     uint8_t mctpEID;
277 
278     mctpEID = pldm::utils::readHostEID();
279     // TODO: fix mapping to work around OpenBMC ecosystem deficiencies
280     pldm_tid_t pldmTID = static_cast<pldm_tid_t>(mctpEID);
281 
282     uint8_t effecterState;
283     auto requestHostTransition =
284         pldm::utils::DBusHandler().getDbusProperty<std::string>(
285             "/xyz/openbmc_project/state/host0", "RequestedHostTransition",
286             sdbusplus::common::xyz::openbmc_project::state::Host::interface);
287     if (requestHostTransition !=
288         "xyz.openbmc_project.State.Host.Transition.Off")
289     {
290         effecterState = PLDM_SW_TERM_GRACEFUL_RESTART_REQUESTED;
291     }
292     else
293     {
294         effecterState = PLDM_SW_TERM_GRACEFUL_SHUTDOWN_REQUESTED;
295     }
296 
297     std::array<uint8_t,
298                sizeof(pldm_msg_hdr) + sizeof(effecterID) +
299                    sizeof(effecterCount) + sizeof(set_effecter_state_field)>
300         requestMsg{};
301     auto request = new (requestMsg.data()) pldm_msg;
302     set_effecter_state_field stateField{PLDM_REQUEST_SET, effecterState};
303     instanceID = instanceIdDb.next(pldmTID);
304     auto rc = encode_set_state_effecter_states_req(
305         instanceID, effecterID, effecterCount, &stateField, request);
306     if (rc != PLDM_SUCCESS)
307     {
308         instanceIdDb.free(pldmTID, instanceID);
309         error(
310             "Failed to encode set state effecter states request message, response code '{RC}'",
311             "RC", lg2::hex, rc);
312         return PLDM_ERROR;
313     }
314 
315     // Add a timer to the event loop, default 30s.
316     auto timerCallback = [=, this](Timer& /*source*/,
317                                    Timer::TimePoint /*time*/) mutable {
318         if (!responseReceived)
319         {
320             instanceIdDb.free(pldmTID, instanceID);
321             error(
322                 "PLDM soft off failed, can't get the response for the PLDM request msg. Time out! Exit the pldm-softpoweroff");
323             exit(-1);
324         }
325         return;
326     };
327     Timer time(event, (Clock(event).now() + std::chrono::seconds{30}),
328                std::chrono::seconds{1}, std::move(timerCallback));
329 
330     // Add a callback to handle EPOLLIN on fd
331     auto callback = [=, &pldmTransport,
332                      this](IO& io, int fd, uint32_t revents) mutable {
333         if (fd != pldmTransport.getEventSource())
334         {
335             return;
336         }
337 
338         if (!(revents & EPOLLIN))
339         {
340             return;
341         }
342 
343         void* responseMsg = nullptr;
344         size_t responseMsgSize{};
345         pldm_tid_t srcTID = pldmTID;
346 
347         auto rc = pldmTransport.recvMsg(pldmTID, responseMsg, responseMsgSize);
348         if (rc)
349         {
350             error(
351                 "Failed to receive pldm data during soft-off, response code '{RC}'",
352                 "RC", rc);
353             return;
354         }
355 
356         std::unique_ptr<void, decltype(std::free)*> responseMsgPtr{
357             responseMsg, std::free};
358 
359         // We've got the response meant for the PLDM request msg that was
360         // sent out
361         io.set_enabled(Enabled::Off);
362         auto response = new (responseMsgPtr.get()) pldm_msg;
363 
364         if (srcTID != pldmTID ||
365             !pldm_msg_hdr_correlate_response(&request->hdr, &response->hdr))
366         {
367             /* This isn't the response we were looking for */
368             return;
369         }
370 
371         /* We have the right response, release the instance ID and process */
372         io.set_enabled(Enabled::Off);
373         instanceIdDb.free(pldmTID, instanceID);
374 
375         if (response->payload[0] != PLDM_SUCCESS)
376         {
377             error("Getting the wrong response, response code '{RC}'", "RC",
378                   response->payload[0]);
379             exit(-1);
380         }
381 
382         responseReceived = true;
383 
384         // Start Timer
385         using namespace std::chrono;
386         auto timeMicroseconds =
387             duration_cast<microseconds>(seconds(SOFTOFF_TIMEOUT_SECONDS));
388 
389         auto ret = startTimer(timeMicroseconds);
390         if (ret < 0)
391         {
392             error(
393                 "Failure to start remote terminus soft off wait timer, Exit the pldm-softpoweroff with response code:{NUM}",
394                 "NUM", ret);
395             exit(-1);
396         }
397         else
398         {
399             error(
400                 "Timer started waiting for remote terminus soft off, timeout in sec '{TIMEOUT_SEC}'",
401                 "TIMEOUT_SEC", SOFTOFF_TIMEOUT_SECONDS);
402         }
403         return;
404     };
405     IO io(event, pldmTransport.getEventSource(), EPOLLIN, std::move(callback));
406 
407     // Asynchronously send the PLDM request
408     rc = pldmTransport.sendMsg(pldmTID, requestMsg.data(), requestMsg.size());
409     if (0 > rc)
410     {
411         instanceIdDb.free(pldmTID, instanceID);
412         error(
413             "Failed to send message/receive response, response code '{RC}' and error - {ERROR}",
414             "RC", rc, "ERROR", errno);
415         return PLDM_ERROR;
416     }
417 
418     // Time out or soft off complete
419     while (!isCompleted() && !isTimerExpired())
420     {
421         try
422         {
423             event.run(std::nullopt);
424         }
425         catch (const sdeventplus::SdEventError& e)
426         {
427             instanceIdDb.free(pldmTID, instanceID);
428             error(
429                 "Failed to process request while remote terminus soft off, error - {ERROR}",
430                 "ERROR", e);
431             return PLDM_ERROR;
432         }
433     }
434 
435     return PLDM_SUCCESS;
436 }
437 
startTimer(const std::chrono::microseconds & usec)438 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec)
439 {
440     return timer.start(usec);
441 }
442 } // namespace pldm
443