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 pldm::pdr::TerminusID TID = 0; // TID will be implemented later. 34 namespace sdbusRule = sdbusplus::bus::match::rules; 35 36 SoftPowerOff::SoftPowerOff(sdbusplus::bus_t& bus, sd_event* event, 37 pldm::InstanceIdDb& instanceIdDb) : 38 bus(bus), 39 timer(event), instanceIdDb(instanceIdDb) 40 { 41 auto jsonData = parseConfig(); 42 43 if (jsonData.is_discarded()) 44 { 45 error("Parsing softoff config JSON file failed"); 46 return; 47 } 48 49 getHostState(); 50 if (hasError || completed) 51 { 52 return; 53 } 54 const std::vector<Json> emptyJsonList{}; 55 auto entries = jsonData.value("entries", emptyJsonList); 56 for (const auto& entry : entries) 57 { 58 TID = entry.value("tid", 0); 59 pldm::pdr::EntityType entityType = entry.value("entityType", 0); 60 pldm::pdr::StateSetId stateSetId = entry.value("stateSetId", 0); 61 62 bool effecterFound = getEffecterID(entityType, stateSetId); 63 if (effecterFound) 64 { 65 auto rc = getSensorInfo(entityType, stateSetId); 66 if (rc != PLDM_SUCCESS) 67 { 68 error("Message get Sensor PDRs error. PLDM error code = {RC}", 69 "RC", lg2::hex, static_cast<int>(rc)); 70 hasError = true; 71 return; 72 } 73 break; 74 } 75 else 76 { 77 continue; 78 } 79 } 80 81 // Matches on the pldm StateSensorEvent signal 82 pldmEventSignal = std::make_unique<sdbusplus::bus::match_t>( 83 bus, 84 sdbusRule::type::signal() + sdbusRule::member("StateSensorEvent") + 85 sdbusRule::path("/xyz/openbmc_project/pldm") + 86 sdbusRule::interface("xyz.openbmc_project.PLDM.Event"), 87 std::bind(std::mem_fn(&SoftPowerOff::hostSoftOffComplete), this, 88 std::placeholders::_1)); 89 } 90 91 int SoftPowerOff::getHostState() 92 { 93 try 94 { 95 pldm::utils::PropertyValue propertyValue = 96 pldm::utils::DBusHandler().getDbusPropertyVariant( 97 "/xyz/openbmc_project/state/host0", "CurrentHostState", 98 "xyz.openbmc_project.State.Host"); 99 100 if ((std::get<std::string>(propertyValue) != 101 "xyz.openbmc_project.State.Host.HostState.Running") && 102 (std::get<std::string>(propertyValue) != 103 "xyz.openbmc_project.State.Host.HostState.TransitioningToOff")) 104 { 105 // Host state is not "Running", this app should return success 106 completed = true; 107 return PLDM_SUCCESS; 108 } 109 } 110 catch (const std::exception& e) 111 { 112 error("PLDM host soft off: Can't get current host state: {ERROR}", 113 "ERROR", e); 114 hasError = true; 115 return PLDM_ERROR; 116 } 117 118 return PLDM_SUCCESS; 119 } 120 121 void SoftPowerOff::hostSoftOffComplete(sdbusplus::message_t& msg) 122 { 123 pldm::pdr::TerminusID msgTID; 124 pldm::pdr::SensorID msgSensorID; 125 pldm::pdr::SensorOffset msgSensorOffset; 126 pldm::pdr::EventState msgEventState; 127 pldm::pdr::EventState msgPreviousEventState; 128 129 // Read the msg and populate each variable 130 msg.read(msgTID, msgSensorID, msgSensorOffset, msgEventState, 131 msgPreviousEventState); 132 133 if (msgSensorID == sensorID && msgSensorOffset == sensorOffset && 134 msgEventState == PLDM_SW_TERM_GRACEFUL_SHUTDOWN) 135 { 136 // Receive Graceful shutdown completion event message. Disable the timer 137 auto rc = timer.stop(); 138 if (rc < 0) 139 { 140 error("PLDM soft off: Failure to STOP the timer. ERRNO={RC}", "RC", 141 rc); 142 } 143 144 // This marks the completion of pldm soft power off. 145 completed = true; 146 } 147 } 148 149 Json SoftPowerOff::parseConfig() 150 { 151 fs::path softoffConfigJson(fs::path(SOFTOFF_CONFIG_JSON) / 152 "softoff_config.json"); 153 154 if (!fs::exists(softoffConfigJson) || fs::is_empty(softoffConfigJson)) 155 { 156 error("Parsing softoff config JSON file failed, File does not exist"); 157 return PLDM_ERROR; 158 } 159 160 std::ifstream jsonFile(softoffConfigJson); 161 return Json::parse(jsonFile); 162 } 163 164 bool SoftPowerOff::getEffecterID(pldm::pdr::EntityType& entityType, 165 pldm::pdr::StateSetId& stateSetId) 166 { 167 auto& bus = pldm::utils::DBusHandler::getBus(); 168 try 169 { 170 std::vector<std::vector<uint8_t>> response{}; 171 auto method = bus.new_method_call( 172 "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm", 173 "xyz.openbmc_project.PLDM.PDR", "FindStateEffecterPDR"); 174 method.append(TID, entityType, stateSetId); 175 auto responseMsg = bus.call(method, dbusTimeout); 176 177 responseMsg.read(response); 178 if (response.size()) 179 { 180 for (auto& rep : response) 181 { 182 auto softoffPdr = 183 reinterpret_cast<pldm_state_effecter_pdr*>(rep.data()); 184 effecterID = softoffPdr->effecter_id; 185 } 186 } 187 else 188 { 189 return false; 190 } 191 } 192 catch (const sdbusplus::exception_t& e) 193 { 194 error("PLDM soft off: Error get softPowerOff PDR,ERROR={ERR_EXCEP}", 195 "ERR_EXCEP", e.what()); 196 return false; 197 } 198 return true; 199 } 200 201 int SoftPowerOff::getSensorInfo(pldm::pdr::EntityType& entityType, 202 pldm::pdr::StateSetId& stateSetId) 203 { 204 try 205 { 206 auto& bus = pldm::utils::DBusHandler::getBus(); 207 std::vector<std::vector<uint8_t>> Response{}; 208 auto method = bus.new_method_call( 209 "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm", 210 "xyz.openbmc_project.PLDM.PDR", "FindStateSensorPDR"); 211 method.append(TID, entityType, stateSetId); 212 213 auto ResponseMsg = bus.call(method, dbusTimeout); 214 215 ResponseMsg.read(Response); 216 217 if (Response.size() == 0) 218 { 219 error("No sensor PDR has been found that matches the criteria"); 220 return PLDM_ERROR; 221 } 222 223 pldm_state_sensor_pdr* pdr = nullptr; 224 for (auto& rep : Response) 225 { 226 pdr = reinterpret_cast<pldm_state_sensor_pdr*>(rep.data()); 227 if (!pdr) 228 { 229 error("Failed to get state sensor PDR."); 230 return PLDM_ERROR; 231 } 232 } 233 234 sensorID = pdr->sensor_id; 235 236 auto compositeSensorCount = pdr->composite_sensor_count; 237 auto possibleStatesStart = pdr->possible_states; 238 239 for (auto offset = 0; offset < compositeSensorCount; offset++) 240 { 241 auto possibleStates = 242 reinterpret_cast<state_sensor_possible_states*>( 243 possibleStatesStart); 244 auto setId = possibleStates->state_set_id; 245 auto possibleStateSize = possibleStates->possible_states_size; 246 247 if (setId == PLDM_STATE_SET_SW_TERMINATION_STATUS) 248 { 249 sensorOffset = offset; 250 break; 251 } 252 possibleStatesStart += possibleStateSize + sizeof(setId) + 253 sizeof(possibleStateSize); 254 } 255 } 256 catch (const sdbusplus::exception_t& e) 257 { 258 error("PLDM soft off: Error get State Sensor PDR,ERROR={ERR_EXCEP}", 259 "ERR_EXCEP", e.what()); 260 return PLDM_ERROR; 261 } 262 263 return PLDM_SUCCESS; 264 } 265 266 int SoftPowerOff::hostSoftOff(sdeventplus::Event& event) 267 { 268 constexpr uint8_t effecterCount = 1; 269 PldmTransport pldmTransport{}; 270 uint8_t instanceID; 271 uint8_t mctpEID; 272 273 mctpEID = pldm::utils::readHostEID(); 274 // TODO: fix mapping to work around OpenBMC ecosystem deficiencies 275 pldm_tid_t pldmTID = static_cast<pldm_tid_t>(mctpEID); 276 277 std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(effecterID) + 278 sizeof(effecterCount) + 279 sizeof(set_effecter_state_field)> 280 requestMsg{}; 281 auto request = reinterpret_cast<pldm_msg*>(requestMsg.data()); 282 set_effecter_state_field stateField{ 283 PLDM_REQUEST_SET, PLDM_SW_TERM_GRACEFUL_SHUTDOWN_REQUESTED}; 284 instanceID = instanceIdDb.next(pldmTID); 285 auto rc = encode_set_state_effecter_states_req( 286 instanceID, effecterID, effecterCount, &stateField, request); 287 if (rc != PLDM_SUCCESS) 288 { 289 instanceIdDb.free(pldmTID, instanceID); 290 error("Message encode failure. PLDM error code = {RC}", "RC", lg2::hex, 291 static_cast<int>(rc)); 292 return PLDM_ERROR; 293 } 294 295 // Add a timer to the event loop, default 30s. 296 auto timerCallback = [=, this](Timer& /*source*/, 297 Timer::TimePoint /*time*/) mutable { 298 if (!responseReceived) 299 { 300 instanceIdDb.free(pldmTID, instanceID); 301 error( 302 "PLDM soft off: ERROR! Can't get the response for the PLDM request msg. Time out! Exit the pldm-softpoweroff"); 303 exit(-1); 304 } 305 return; 306 }; 307 Timer time(event, (Clock(event).now() + std::chrono::seconds{30}), 308 std::chrono::seconds{1}, std::move(timerCallback)); 309 310 // Add a callback to handle EPOLLIN on fd 311 auto callback = [=, &pldmTransport, this](IO& io, int fd, 312 uint32_t revents) mutable { 313 if (fd != pldmTransport.getEventSource()) 314 { 315 return; 316 } 317 318 if (!(revents & EPOLLIN)) 319 { 320 return; 321 } 322 323 void* responseMsg = nullptr; 324 size_t responseMsgSize{}; 325 pldm_tid_t srcTID = pldmTID; 326 327 auto rc = pldmTransport.recvMsg(pldmTID, responseMsg, responseMsgSize); 328 if (rc) 329 { 330 error("Soft off: failed to recv pldm data. PLDM RC = {RC}", "RC", 331 static_cast<int>(rc)); 332 return; 333 } 334 335 std::unique_ptr<void, decltype(std::free)*> responseMsgPtr{responseMsg, 336 std::free}; 337 338 // We've got the response meant for the PLDM request msg that was 339 // sent out 340 io.set_enabled(Enabled::Off); 341 auto response = reinterpret_cast<pldm_msg*>(responseMsgPtr.get()); 342 343 if (srcTID != pldmTID || 344 !pldm_msg_hdr_correlate_response(&request->hdr, &response->hdr)) 345 { 346 /* This isn't the response we were looking for */ 347 return; 348 } 349 350 /* We have the right response, release the instance ID and process */ 351 io.set_enabled(Enabled::Off); 352 instanceIdDb.free(pldmTID, instanceID); 353 354 if (response->payload[0] != PLDM_SUCCESS) 355 { 356 error("Getting the wrong response. PLDM RC = {RC}", "RC", 357 (unsigned)response->payload[0]); 358 exit(-1); 359 } 360 361 responseReceived = true; 362 363 // Start Timer 364 using namespace std::chrono; 365 auto timeMicroseconds = 366 duration_cast<microseconds>(seconds(SOFTOFF_TIMEOUT_SECONDS)); 367 368 auto ret = startTimer(timeMicroseconds); 369 if (ret < 0) 370 { 371 error( 372 "Failure to start Host soft off wait timer, ERRNO = {RET}. Exit the pldm-softpoweroff", 373 "RET", ret); 374 exit(-1); 375 } 376 else 377 { 378 error( 379 "Timer started waiting for host soft off, TIMEOUT_IN_SEC = {TIMEOUT_SEC}", 380 "TIMEOUT_SEC", SOFTOFF_TIMEOUT_SECONDS); 381 } 382 return; 383 }; 384 IO io(event, pldmTransport.getEventSource(), EPOLLIN, std::move(callback)); 385 386 // Asynchronously send the PLDM request 387 rc = pldmTransport.sendMsg(pldmTID, requestMsg.data(), requestMsg.size()); 388 if (0 > rc) 389 { 390 instanceIdDb.free(pldmTID, instanceID); 391 error( 392 "Failed to send message/receive response. RC = {RC}, errno = {ERR}", 393 "RC", static_cast<int>(rc), "ERR", errno); 394 return PLDM_ERROR; 395 } 396 397 // Time out or soft off complete 398 while (!isCompleted() && !isTimerExpired()) 399 { 400 try 401 { 402 event.run(std::nullopt); 403 } 404 catch (const sdeventplus::SdEventError& e) 405 { 406 instanceIdDb.free(pldmTID, instanceID); 407 error( 408 "PLDM host soft off: Failure in processing request.ERROR= {ERR_EXCEP}", 409 "ERR_EXCEP", e.what()); 410 return PLDM_ERROR; 411 } 412 } 413 414 return PLDM_SUCCESS; 415 } 416 417 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec) 418 { 419 return timer.start(usec); 420 } 421 } // namespace pldm 422