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