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