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