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 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 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 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 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 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 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 274 int SoftPowerOff::hostSoftOff(sdeventplus::Event& event) 275 { 276 constexpr uint8_t effecterCount = 1; 277 PldmTransport pldmTransport{}; 278 uint8_t mctpEID; 279 280 mctpEID = pldm::utils::readHostEID(); 281 // TODO: fix mapping to work around OpenBMC ecosystem deficiencies 282 pldm_tid_t pldmTID = static_cast<pldm_tid_t>(mctpEID); 283 284 uint8_t effecterState; 285 auto requestHostTransition = 286 pldm::utils::DBusHandler().getDbusProperty<std::string>( 287 "/xyz/openbmc_project/state/host0", 288 HostState::property_names::requested_host_transition, 289 sdbusplus::common::xyz::openbmc_project::state::Host::interface); 290 if (requestHostTransition != 291 "xyz.openbmc_project.State.Host.Transition.Off") 292 { 293 effecterState = PLDM_SW_TERM_GRACEFUL_RESTART_REQUESTED; 294 } 295 else 296 { 297 effecterState = PLDM_SW_TERM_GRACEFUL_SHUTDOWN_REQUESTED; 298 } 299 300 std::array<uint8_t, 301 sizeof(pldm_msg_hdr) + sizeof(effecterID) + 302 sizeof(effecterCount) + sizeof(set_effecter_state_field)> 303 requestMsg{}; 304 auto request = new (requestMsg.data()) pldm_msg; 305 set_effecter_state_field stateField{PLDM_REQUEST_SET, effecterState}; 306 auto instanceIdResult = instanceIdDb.next(pldmTID); 307 if (!instanceIdResult) 308 { 309 return PLDM_ERROR; 310 } 311 auto instanceID = instanceIdResult.value(); 312 auto rc = encode_set_state_effecter_states_req( 313 instanceID, effecterID, effecterCount, &stateField, request); 314 if (rc != PLDM_SUCCESS) 315 { 316 instanceIdDb.free(pldmTID, instanceID); 317 error( 318 "Failed to encode set state effecter states request message, response code '{RC}'", 319 "RC", lg2::hex, rc); 320 return PLDM_ERROR; 321 } 322 323 // Add a timer to the event loop, default 30s. 324 auto timerCallback = [=, this](Timer& /*source*/, 325 Timer::TimePoint /*time*/) mutable { 326 if (!responseReceived) 327 { 328 instanceIdDb.free(pldmTID, instanceID); 329 error( 330 "PLDM soft off failed, can't get the response for the PLDM request msg. Time out! Exit the pldm-softpoweroff"); 331 exit(-1); 332 } 333 return; 334 }; 335 Timer time(event, (Clock(event).now() + std::chrono::seconds{30}), 336 std::chrono::seconds{1}, std::move(timerCallback)); 337 338 // Add a callback to handle EPOLLIN on fd 339 auto callback = [=, &pldmTransport, 340 this](IO& io, int fd, uint32_t revents) mutable { 341 if (fd != pldmTransport.getEventSource()) 342 { 343 return; 344 } 345 346 if (!(revents & EPOLLIN)) 347 { 348 return; 349 } 350 351 void* responseMsg = nullptr; 352 size_t responseMsgSize{}; 353 pldm_tid_t srcTID = pldmTID; 354 355 auto rc = pldmTransport.recvMsg(pldmTID, responseMsg, responseMsgSize); 356 if (rc) 357 { 358 error( 359 "Failed to receive pldm data during soft-off, response code '{RC}'", 360 "RC", rc); 361 return; 362 } 363 364 std::unique_ptr<void, decltype(std::free)*> responseMsgPtr{ 365 responseMsg, std::free}; 366 367 // We've got the response meant for the PLDM request msg that was 368 // sent out 369 io.set_enabled(Enabled::Off); 370 auto response = new (responseMsgPtr.get()) pldm_msg; 371 372 if (srcTID != pldmTID || 373 !pldm_msg_hdr_correlate_response(&request->hdr, &response->hdr)) 374 { 375 /* This isn't the response we were looking for */ 376 return; 377 } 378 379 /* We have the right response, release the instance ID and process */ 380 io.set_enabled(Enabled::Off); 381 instanceIdDb.free(pldmTID, instanceID); 382 383 if (response->payload[0] != PLDM_SUCCESS) 384 { 385 error("Getting the wrong response, response code '{RC}'", "RC", 386 response->payload[0]); 387 exit(-1); 388 } 389 390 responseReceived = true; 391 392 // Start Timer 393 using namespace std::chrono; 394 auto timeMicroseconds = 395 duration_cast<microseconds>(seconds(SOFTOFF_TIMEOUT_SECONDS)); 396 397 auto ret = startTimer(timeMicroseconds); 398 if (ret < 0) 399 { 400 error( 401 "Failure to start remote terminus soft off wait timer, Exit the pldm-softpoweroff with response code:{NUM}", 402 "NUM", ret); 403 exit(-1); 404 } 405 else 406 { 407 error( 408 "Timer started waiting for remote terminus soft off, timeout in sec '{TIMEOUT_SEC}'", 409 "TIMEOUT_SEC", SOFTOFF_TIMEOUT_SECONDS); 410 } 411 return; 412 }; 413 IO io(event, pldmTransport.getEventSource(), EPOLLIN, std::move(callback)); 414 415 // Asynchronously send the PLDM request 416 rc = pldmTransport.sendMsg(pldmTID, requestMsg.data(), requestMsg.size()); 417 if (0 > rc) 418 { 419 instanceIdDb.free(pldmTID, instanceID); 420 error( 421 "Failed to send message/receive response, response code '{RC}' and error - {ERROR}", 422 "RC", rc, "ERROR", errno); 423 return PLDM_ERROR; 424 } 425 426 // Time out or soft off complete 427 while (!isCompleted() && !isTimerExpired()) 428 { 429 try 430 { 431 event.run(std::nullopt); 432 } 433 catch (const sdeventplus::SdEventError& e) 434 { 435 instanceIdDb.free(pldmTID, instanceID); 436 error( 437 "Failed to process request while remote terminus soft off, error - {ERROR}", 438 "ERROR", e); 439 return PLDM_ERROR; 440 } 441 } 442 443 return PLDM_SUCCESS; 444 } 445 446 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec) 447 { 448 return timer.start(usec); 449 } 450 } // namespace pldm 451