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