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