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