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