xref: /openbmc/pldm/softoff/softoff.cpp (revision 963c0fdf)
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 
40 SoftPowerOff::SoftPowerOff(sdbusplus::bus_t& bus, sd_event* event,
41                            pldm::InstanceIdDb& instanceIdDb) :
42     bus(bus),
43     timer(event), instanceIdDb(instanceIdDb)
44 {
45     auto jsonData = parseConfig();
46 
47     if (jsonData.is_discarded())
48     {
49         error("Parsing softoff config JSON file failed");
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("Message get Sensor PDRs error. PLDM error code = {RC}",
73                       "RC", lg2::hex, static_cast<int>(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 
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("PLDM host soft off: Can't get current host state: {ERROR}",
117               "ERROR", e);
118         hasError = true;
119         return PLDM_ERROR;
120     }
121 
122     return PLDM_SUCCESS;
123 }
124 
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("PLDM soft off: Failure to STOP the timer. ERRNO={RC}", "RC",
145                   rc);
146         }
147 
148         // This marks the completion of pldm soft power off.
149         completed = true;
150     }
151 }
152 
153 Json SoftPowerOff::parseConfig()
154 {
155     fs::path softoffConfigJson(fs::path(SOFTOFF_CONFIG_JSON) /
156                                "softoff_config.json");
157 
158     if (!fs::exists(softoffConfigJson) || fs::is_empty(softoffConfigJson))
159     {
160         error("Parsing softoff config JSON file failed, File does not exist");
161         return PLDM_ERROR;
162     }
163 
164     std::ifstream jsonFile(softoffConfigJson);
165     return Json::parse(jsonFile);
166 }
167 
168 bool SoftPowerOff::getEffecterID(pldm::pdr::EntityType& entityType,
169                                  pldm::pdr::StateSetId& stateSetId)
170 {
171     auto& bus = pldm::utils::DBusHandler::getBus();
172     try
173     {
174         std::vector<std::vector<uint8_t>> response{};
175         auto method = bus.new_method_call(
176             "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm",
177             "xyz.openbmc_project.PLDM.PDR", "FindStateEffecterPDR");
178         method.append(TID, entityType, stateSetId);
179         auto responseMsg = bus.call(method, dbusTimeout);
180 
181         responseMsg.read(response);
182         if (response.size())
183         {
184             for (auto& rep : response)
185             {
186                 auto softoffPdr =
187                     reinterpret_cast<pldm_state_effecter_pdr*>(rep.data());
188                 effecterID = softoffPdr->effecter_id;
189             }
190         }
191         else
192         {
193             return false;
194         }
195     }
196     catch (const sdbusplus::exception_t& e)
197     {
198         error("PLDM soft off: Error get softPowerOff PDR,ERROR={ERR_EXCEP}",
199               "ERR_EXCEP", e.what());
200         return false;
201     }
202     return true;
203 }
204 
205 int SoftPowerOff::getSensorInfo(pldm::pdr::EntityType& entityType,
206                                 pldm::pdr::StateSetId& stateSetId)
207 {
208     try
209     {
210         auto& bus = pldm::utils::DBusHandler::getBus();
211         std::vector<std::vector<uint8_t>> Response{};
212         auto method = bus.new_method_call(
213             "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm",
214             "xyz.openbmc_project.PLDM.PDR", "FindStateSensorPDR");
215         method.append(TID, entityType, stateSetId);
216 
217         auto ResponseMsg = bus.call(method, dbusTimeout);
218 
219         ResponseMsg.read(Response);
220 
221         if (Response.size() == 0)
222         {
223             error("No sensor PDR has been found that matches the criteria");
224             return PLDM_ERROR;
225         }
226 
227         pldm_state_sensor_pdr* pdr = nullptr;
228         for (auto& rep : Response)
229         {
230             pdr = reinterpret_cast<pldm_state_sensor_pdr*>(rep.data());
231             if (!pdr)
232             {
233                 error("Failed to get state sensor PDR.");
234                 return PLDM_ERROR;
235             }
236         }
237 
238         sensorID = pdr->sensor_id;
239 
240         auto compositeSensorCount = pdr->composite_sensor_count;
241         auto possibleStatesStart = pdr->possible_states;
242 
243         for (auto offset = 0; offset < compositeSensorCount; offset++)
244         {
245             auto possibleStates =
246                 reinterpret_cast<state_sensor_possible_states*>(
247                     possibleStatesStart);
248             auto setId = possibleStates->state_set_id;
249             auto possibleStateSize = possibleStates->possible_states_size;
250 
251             if (setId == PLDM_STATE_SET_SW_TERMINATION_STATUS)
252             {
253                 sensorOffset = offset;
254                 break;
255             }
256             possibleStatesStart += possibleStateSize + sizeof(setId) +
257                                    sizeof(possibleStateSize);
258         }
259     }
260     catch (const sdbusplus::exception_t& e)
261     {
262         error("PLDM soft off: Error get State Sensor PDR,ERROR={ERR_EXCEP}",
263               "ERR_EXCEP", e.what());
264         return PLDM_ERROR;
265     }
266 
267     return PLDM_SUCCESS;
268 }
269 
270 int SoftPowerOff::hostSoftOff(sdeventplus::Event& event)
271 {
272     constexpr uint8_t effecterCount = 1;
273     PldmTransport pldmTransport{};
274     uint8_t instanceID;
275     uint8_t mctpEID;
276 
277     mctpEID = pldm::utils::readHostEID();
278     // TODO: fix mapping to work around OpenBMC ecosystem deficiencies
279     pldm_tid_t pldmTID = static_cast<pldm_tid_t>(mctpEID);
280 
281     std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(effecterID) +
282                             sizeof(effecterCount) +
283                             sizeof(set_effecter_state_field)>
284         requestMsg{};
285     auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
286     set_effecter_state_field stateField{
287         PLDM_REQUEST_SET, PLDM_SW_TERM_GRACEFUL_SHUTDOWN_REQUESTED};
288     instanceID = instanceIdDb.next(pldmTID);
289     auto rc = encode_set_state_effecter_states_req(
290         instanceID, effecterID, effecterCount, &stateField, request);
291     if (rc != PLDM_SUCCESS)
292     {
293         instanceIdDb.free(pldmTID, instanceID);
294         error("Message encode failure. PLDM error code = {RC}", "RC", lg2::hex,
295               static_cast<int>(rc));
296         return PLDM_ERROR;
297     }
298 
299     // Add a timer to the event loop, default 30s.
300     auto timerCallback = [=, this](Timer& /*source*/,
301                                    Timer::TimePoint /*time*/) mutable {
302         if (!responseReceived)
303         {
304             instanceIdDb.free(pldmTID, instanceID);
305             error(
306                 "PLDM soft off: ERROR! Can't get the response for the PLDM request msg. Time out! Exit the pldm-softpoweroff");
307             exit(-1);
308         }
309         return;
310     };
311     Timer time(event, (Clock(event).now() + std::chrono::seconds{30}),
312                std::chrono::seconds{1}, std::move(timerCallback));
313 
314     // Add a callback to handle EPOLLIN on fd
315     auto callback = [=, &pldmTransport, this](IO& io, int fd,
316                                               uint32_t revents) mutable {
317         if (fd != pldmTransport.getEventSource())
318         {
319             return;
320         }
321 
322         if (!(revents & EPOLLIN))
323         {
324             return;
325         }
326 
327         void* responseMsg = nullptr;
328         size_t responseMsgSize{};
329         pldm_tid_t srcTID = pldmTID;
330 
331         auto rc = pldmTransport.recvMsg(pldmTID, responseMsg, responseMsgSize);
332         if (rc)
333         {
334             error("Soft off: failed to recv pldm data. PLDM RC = {RC}", "RC",
335                   static_cast<int>(rc));
336             return;
337         }
338 
339         std::unique_ptr<void, decltype(std::free)*> responseMsgPtr{responseMsg,
340                                                                    std::free};
341 
342         // We've got the response meant for the PLDM request msg that was
343         // sent out
344         io.set_enabled(Enabled::Off);
345         auto response = reinterpret_cast<pldm_msg*>(responseMsgPtr.get());
346 
347         if (srcTID != pldmTID ||
348             !pldm_msg_hdr_correlate_response(&request->hdr, &response->hdr))
349         {
350             /* This isn't the response we were looking for */
351             return;
352         }
353 
354         /* We have the right response, release the instance ID and process */
355         io.set_enabled(Enabled::Off);
356         instanceIdDb.free(pldmTID, instanceID);
357 
358         if (response->payload[0] != PLDM_SUCCESS)
359         {
360             error("Getting the wrong response. PLDM RC = {RC}", "RC",
361                   (unsigned)response->payload[0]);
362             exit(-1);
363         }
364 
365         responseReceived = true;
366 
367         // Start Timer
368         using namespace std::chrono;
369         auto timeMicroseconds =
370             duration_cast<microseconds>(seconds(SOFTOFF_TIMEOUT_SECONDS));
371 
372         auto ret = startTimer(timeMicroseconds);
373         if (ret < 0)
374         {
375             error(
376                 "Failure to start Host soft off wait timer, ERRNO = {RET}. Exit the pldm-softpoweroff",
377                 "RET", ret);
378             exit(-1);
379         }
380         else
381         {
382             error(
383                 "Timer started waiting for host soft off, TIMEOUT_IN_SEC = {TIMEOUT_SEC}",
384                 "TIMEOUT_SEC", SOFTOFF_TIMEOUT_SECONDS);
385         }
386         return;
387     };
388     IO io(event, pldmTransport.getEventSource(), EPOLLIN, std::move(callback));
389 
390     // Asynchronously send the PLDM request
391     rc = pldmTransport.sendMsg(pldmTID, requestMsg.data(), requestMsg.size());
392     if (0 > rc)
393     {
394         instanceIdDb.free(pldmTID, instanceID);
395         error(
396             "Failed to send message/receive response. RC = {RC}, errno = {ERR}",
397             "RC", static_cast<int>(rc), "ERR", errno);
398         return PLDM_ERROR;
399     }
400 
401     // Time out or soft off complete
402     while (!isCompleted() && !isTimerExpired())
403     {
404         try
405         {
406             event.run(std::nullopt);
407         }
408         catch (const sdeventplus::SdEventError& e)
409         {
410             instanceIdDb.free(pldmTID, instanceID);
411             error(
412                 "PLDM host soft off: Failure in processing request.ERROR= {ERR_EXCEP}",
413                 "ERR_EXCEP", e.what());
414             return PLDM_ERROR;
415         }
416     }
417 
418     return PLDM_SUCCESS;
419 }
420 
421 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec)
422 {
423     return timer.start(usec);
424 }
425 } // namespace pldm
426