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), 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 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 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 155 Json SoftPowerOff::parseConfig() 156 { 157 fs::path softoffConfigJson(fs::path(SOFTOFF_CONFIG_JSON) / 158 "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 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 = 191 reinterpret_cast<pldm_state_effecter_pdr*>(rep.data()); 192 effecterID = softoffPdr->effecter_id; 193 } 194 } 195 else 196 { 197 return false; 198 } 199 } 200 catch (const sdbusplus::exception_t& e) 201 { 202 error("Failed to get softPowerOff PDR, error - {ERROR}", "ERROR", e); 203 return false; 204 } 205 return true; 206 } 207 208 int SoftPowerOff::getSensorInfo(pldm::pdr::EntityType& entityType, 209 pldm::pdr::StateSetId& stateSetId) 210 { 211 try 212 { 213 auto& bus = pldm::utils::DBusHandler::getBus(); 214 std::vector<std::vector<uint8_t>> Response{}; 215 auto method = bus.new_method_call( 216 "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm", 217 "xyz.openbmc_project.PLDM.PDR", "FindStateSensorPDR"); 218 method.append(TID, entityType, stateSetId); 219 220 auto ResponseMsg = bus.call(method, dbusTimeout); 221 222 ResponseMsg.read(Response); 223 224 if (Response.size() == 0) 225 { 226 error("No sensor PDR has been found that matches the criteria"); 227 return PLDM_ERROR; 228 } 229 230 pldm_state_sensor_pdr* pdr = nullptr; 231 for (auto& rep : Response) 232 { 233 pdr = reinterpret_cast<pldm_state_sensor_pdr*>(rep.data()); 234 if (!pdr) 235 { 236 error("Failed to get state sensor PDR."); 237 return PLDM_ERROR; 238 } 239 } 240 241 sensorID = pdr->sensor_id; 242 243 auto compositeSensorCount = pdr->composite_sensor_count; 244 auto possibleStatesStart = pdr->possible_states; 245 246 for (auto offset = 0; offset < compositeSensorCount; offset++) 247 { 248 auto possibleStates = 249 reinterpret_cast<state_sensor_possible_states*>( 250 possibleStatesStart); 251 auto setId = possibleStates->state_set_id; 252 auto possibleStateSize = possibleStates->possible_states_size; 253 254 if (setId == PLDM_STATE_SET_SW_TERMINATION_STATUS) 255 { 256 sensorOffset = offset; 257 break; 258 } 259 possibleStatesStart += possibleStateSize + sizeof(setId) + 260 sizeof(possibleStateSize); 261 } 262 } 263 catch (const sdbusplus::exception_t& e) 264 { 265 error("Failed to get state sensor PDR during soft-off, error - {ERROR}", 266 "ERROR", e); 267 return PLDM_ERROR; 268 } 269 270 return PLDM_SUCCESS; 271 } 272 273 int SoftPowerOff::hostSoftOff(sdeventplus::Event& event) 274 { 275 constexpr uint8_t effecterCount = 1; 276 PldmTransport pldmTransport{}; 277 uint8_t instanceID; 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 std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(effecterID) + 285 sizeof(effecterCount) + 286 sizeof(set_effecter_state_field)> 287 requestMsg{}; 288 auto request = reinterpret_cast<pldm_msg*>(requestMsg.data()); 289 set_effecter_state_field stateField{ 290 PLDM_REQUEST_SET, PLDM_SW_TERM_GRACEFUL_SHUTDOWN_REQUESTED}; 291 instanceID = instanceIdDb.next(pldmTID); 292 auto rc = encode_set_state_effecter_states_req( 293 instanceID, effecterID, effecterCount, &stateField, request); 294 if (rc != PLDM_SUCCESS) 295 { 296 instanceIdDb.free(pldmTID, instanceID); 297 error( 298 "Failed to encode set state effecter states request message, response code '{RC}'", 299 "RC", lg2::hex, rc); 300 return PLDM_ERROR; 301 } 302 303 // Add a timer to the event loop, default 30s. 304 auto timerCallback = [=, this](Timer& /*source*/, 305 Timer::TimePoint /*time*/) mutable { 306 if (!responseReceived) 307 { 308 instanceIdDb.free(pldmTID, instanceID); 309 error( 310 "PLDM soft off failed, can't get the response for the PLDM request msg. Time out! Exit the pldm-softpoweroff"); 311 exit(-1); 312 } 313 return; 314 }; 315 Timer time(event, (Clock(event).now() + std::chrono::seconds{30}), 316 std::chrono::seconds{1}, std::move(timerCallback)); 317 318 // Add a callback to handle EPOLLIN on fd 319 auto callback = [=, &pldmTransport, this](IO& io, int fd, 320 uint32_t revents) mutable { 321 if (fd != pldmTransport.getEventSource()) 322 { 323 return; 324 } 325 326 if (!(revents & EPOLLIN)) 327 { 328 return; 329 } 330 331 void* responseMsg = nullptr; 332 size_t responseMsgSize{}; 333 pldm_tid_t srcTID = pldmTID; 334 335 auto rc = pldmTransport.recvMsg(pldmTID, responseMsg, responseMsgSize); 336 if (rc) 337 { 338 error( 339 "Failed to receive pldm data during soft-off, response code '{RC}'", 340 "RC", rc); 341 return; 342 } 343 344 std::unique_ptr<void, decltype(std::free)*> responseMsgPtr{responseMsg, 345 std::free}; 346 347 // We've got the response meant for the PLDM request msg that was 348 // sent out 349 io.set_enabled(Enabled::Off); 350 auto response = reinterpret_cast<pldm_msg*>(responseMsgPtr.get()); 351 352 if (srcTID != pldmTID || 353 !pldm_msg_hdr_correlate_response(&request->hdr, &response->hdr)) 354 { 355 /* This isn't the response we were looking for */ 356 return; 357 } 358 359 /* We have the right response, release the instance ID and process */ 360 io.set_enabled(Enabled::Off); 361 instanceIdDb.free(pldmTID, instanceID); 362 363 if (response->payload[0] != PLDM_SUCCESS) 364 { 365 error("Getting the wrong response, response code '{RC}'", "RC", 366 response->payload[0]); 367 exit(-1); 368 } 369 370 responseReceived = true; 371 372 // Start Timer 373 using namespace std::chrono; 374 auto timeMicroseconds = 375 duration_cast<microseconds>(seconds(SOFTOFF_TIMEOUT_SECONDS)); 376 377 auto ret = startTimer(timeMicroseconds); 378 if (ret < 0) 379 { 380 error( 381 "Failure to start remote terminus soft off wait timer, Exit the pldm-softpoweroff with response code:{NUM}", 382 "NUM", ret); 383 exit(-1); 384 } 385 else 386 { 387 error( 388 "Timer started waiting for remote terminus soft off, timeout in sec '{TIMEOUT_SEC}'", 389 "TIMEOUT_SEC", SOFTOFF_TIMEOUT_SECONDS); 390 } 391 return; 392 }; 393 IO io(event, pldmTransport.getEventSource(), EPOLLIN, std::move(callback)); 394 395 // Asynchronously send the PLDM request 396 rc = pldmTransport.sendMsg(pldmTID, requestMsg.data(), requestMsg.size()); 397 if (0 > rc) 398 { 399 instanceIdDb.free(pldmTID, instanceID); 400 error( 401 "Failed to send message/receive response, response code '{RC}' and error - {ERROR}", 402 "RC", rc, "ERROR", errno); 403 return PLDM_ERROR; 404 } 405 406 // Time out or soft off complete 407 while (!isCompleted() && !isTimerExpired()) 408 { 409 try 410 { 411 event.run(std::nullopt); 412 } 413 catch (const sdeventplus::SdEventError& e) 414 { 415 instanceIdDb.free(pldmTID, instanceID); 416 error( 417 "Failed to process request while remote terminus soft off, error - {ERROR}", 418 "ERROR", e); 419 return PLDM_ERROR; 420 } 421 } 422 423 return PLDM_SUCCESS; 424 } 425 426 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec) 427 { 428 return timer.start(usec); 429 } 430 } // namespace pldm 431