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