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), 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 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 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 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 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 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 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 425 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec) 426 { 427 return timer.start(usec); 428 } 429 } // namespace pldm 430