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 <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 namespace pldm 22 { 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 std::cerr 46 << "pldm-softpoweroff: effecter to initiate softoff not found \n"; 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 std::cerr << "Message get Sensor PDRs error. PLDM error code = " 59 << std::hex << std::showbase << rc << "\n"; 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 std::cerr << "PLDM host soft off: Can't get current host state.\n"; 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 std::cerr << "PLDM soft off: Failure to STOP the timer. ERRNO=" 123 << rc << "\n"; 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); 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 std::cerr << "PLDM soft off: Error get VMM PDR,ERROR=" << e.what() 167 << "\n"; 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); 190 191 sysFwResponseMsg.read(sysFwResponse); 192 193 if (sysFwResponse.size() == 0) 194 { 195 std::cerr 196 << "No effecter ID has been found that matches the criteria" 197 << "\n"; 198 return PLDM_ERROR; 199 } 200 201 for (auto& rep : sysFwResponse) 202 { 203 auto sysFwPdr = 204 reinterpret_cast<pldm_state_effecter_pdr*>(rep.data()); 205 effecterID = sysFwPdr->effecter_id; 206 } 207 } 208 catch (const sdbusplus::exception_t& e) 209 { 210 std::cerr << "PLDM soft off: Error get system firmware PDR,ERROR=" 211 << e.what() << "\n"; 212 completed = true; 213 return PLDM_ERROR; 214 } 215 216 return PLDM_SUCCESS; 217 } 218 219 int SoftPowerOff::getSensorInfo() 220 { 221 pldm::pdr::EntityType entityType; 222 223 entityType = VMMPdrExist ? PLDM_ENTITY_VIRTUAL_MACHINE_MANAGER 224 : PLDM_ENTITY_SYS_FIRMWARE; 225 226 // The Virtual machine manager/System firmware is logical entity, so bit 15 227 // need to be set. 228 entityType = entityType | 0x8000; 229 230 try 231 { 232 auto& bus = pldm::utils::DBusHandler::getBus(); 233 std::vector<std::vector<uint8_t>> Response{}; 234 auto method = bus.new_method_call( 235 "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm", 236 "xyz.openbmc_project.PLDM.PDR", "FindStateSensorPDR"); 237 method.append(TID, entityType, 238 (uint16_t)PLDM_STATE_SET_SW_TERMINATION_STATUS); 239 240 auto ResponseMsg = bus.call(method); 241 242 ResponseMsg.read(Response); 243 244 if (Response.size() == 0) 245 { 246 std::cerr 247 << "No sensor PDR has been found that matches the criteria" 248 << "\n"; 249 return PLDM_ERROR; 250 } 251 252 pldm_state_sensor_pdr* pdr; 253 for (auto& rep : Response) 254 { 255 pdr = reinterpret_cast<pldm_state_sensor_pdr*>(rep.data()); 256 if (!pdr) 257 { 258 std::cerr << "Failed to get state sensor PDR.\n"; 259 return PLDM_ERROR; 260 } 261 } 262 263 sensorID = pdr->sensor_id; 264 265 auto compositeSensorCount = pdr->composite_sensor_count; 266 auto possibleStatesStart = pdr->possible_states; 267 268 for (auto offset = 0; offset < compositeSensorCount; offset++) 269 { 270 auto possibleStates = 271 reinterpret_cast<state_sensor_possible_states*>( 272 possibleStatesStart); 273 auto setId = possibleStates->state_set_id; 274 auto possibleStateSize = possibleStates->possible_states_size; 275 276 if (setId == PLDM_STATE_SET_SW_TERMINATION_STATUS) 277 { 278 sensorOffset = offset; 279 break; 280 } 281 possibleStatesStart += 282 possibleStateSize + sizeof(setId) + sizeof(possibleStateSize); 283 } 284 } 285 catch (const sdbusplus::exception_t& e) 286 { 287 std::cerr << "PLDM soft off: Error get State Sensor PDR,ERROR=" 288 << e.what() << "\n"; 289 return PLDM_ERROR; 290 } 291 292 return PLDM_SUCCESS; 293 } 294 295 int SoftPowerOff::hostSoftOff(sdeventplus::Event& event) 296 { 297 constexpr uint8_t effecterCount = 1; 298 uint8_t mctpEID; 299 uint8_t instanceID; 300 301 mctpEID = pldm::utils::readHostEID(); 302 303 // Get instanceID 304 try 305 { 306 auto& bus = pldm::utils::DBusHandler::getBus(); 307 auto method = bus.new_method_call( 308 "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm", 309 "xyz.openbmc_project.PLDM.Requester", "GetInstanceId"); 310 method.append(mctpEID); 311 312 auto ResponseMsg = bus.call(method); 313 314 ResponseMsg.read(instanceID); 315 } 316 catch (const sdbusplus::exception_t& e) 317 { 318 std::cerr << "PLDM soft off: Error get instanceID,ERROR=" << e.what() 319 << "\n"; 320 return PLDM_ERROR; 321 } 322 323 std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(effecterID) + 324 sizeof(effecterCount) + 325 sizeof(set_effecter_state_field)> 326 requestMsg{}; 327 auto request = reinterpret_cast<pldm_msg*>(requestMsg.data()); 328 set_effecter_state_field stateField{ 329 PLDM_REQUEST_SET, PLDM_SW_TERM_GRACEFUL_SHUTDOWN_REQUESTED}; 330 auto rc = encode_set_state_effecter_states_req( 331 instanceID, effecterID, effecterCount, &stateField, request); 332 if (rc != PLDM_SUCCESS) 333 { 334 std::cerr << "Message encode failure. PLDM error code = " << std::hex 335 << std::showbase << rc << "\n"; 336 return PLDM_ERROR; 337 } 338 339 // Open connection to MCTP socket 340 int fd = pldm_open(); 341 if (-1 == fd) 342 { 343 std::cerr << "Failed to connect to mctp demux daemon" 344 << "\n"; 345 return PLDM_ERROR; 346 } 347 348 // Add a timer to the event loop, default 30s. 349 auto timerCallback = [=, this](Timer& /*source*/, 350 Timer::TimePoint /*time*/) { 351 if (!responseReceived) 352 { 353 std::cerr 354 << "PLDM soft off: ERROR! Can't get the response for the PLDM request msg. Time out!\n" 355 << "Exit the pldm-softpoweroff\n"; 356 exit(-1); 357 } 358 return; 359 }; 360 Timer time(event, (Clock(event).now() + std::chrono::seconds{30}), 361 std::chrono::seconds{1}, std::move(timerCallback)); 362 363 // Add a callback to handle EPOLLIN on fd 364 auto callback = [=, this](IO& io, int fd, uint32_t revents) { 365 if (!(revents & EPOLLIN)) 366 { 367 return; 368 } 369 370 uint8_t* responseMsg = nullptr; 371 size_t responseMsgSize{}; 372 373 auto rc = pldm_recv(mctpEID, fd, request->hdr.instance_id, &responseMsg, 374 &responseMsgSize); 375 if (rc) 376 { 377 std::cerr << "Soft off: failed to recv pldm data. PLDM RC = " << rc 378 << "\n"; 379 return; 380 } 381 382 std::unique_ptr<uint8_t, decltype(std::free)*> responseMsgPtr{ 383 responseMsg, std::free}; 384 385 // We've got the response meant for the PLDM request msg that was 386 // sent out 387 io.set_enabled(Enabled::Off); 388 auto response = reinterpret_cast<pldm_msg*>(responseMsgPtr.get()); 389 if (response->payload[0] != PLDM_SUCCESS) 390 { 391 std::cerr << "Getting the wrong response. PLDM RC = " 392 << (unsigned)response->payload[0] << "\n"; 393 exit(-1); 394 } 395 396 responseReceived = true; 397 398 // Start Timer 399 using namespace std::chrono; 400 auto timeMicroseconds = 401 duration_cast<microseconds>(seconds(SOFTOFF_TIMEOUT_SECONDS)); 402 403 auto ret = startTimer(timeMicroseconds); 404 if (ret < 0) 405 { 406 std::cerr << "Failure to start Host soft off wait timer, ERRNO = " 407 << ret << "Exit the pldm-softpoweroff\n"; 408 exit(-1); 409 } 410 else 411 { 412 std::cerr 413 << "Timer started waiting for host soft off, TIMEOUT_IN_SEC = " 414 << SOFTOFF_TIMEOUT_SECONDS << "\n"; 415 } 416 return; 417 }; 418 IO io(event, fd, EPOLLIN, std::move(callback)); 419 420 // Send PLDM Request message - pldm_send doesn't wait for response 421 rc = pldm_send(mctpEID, fd, requestMsg.data(), requestMsg.size()); 422 if (0 > rc) 423 { 424 std::cerr << "Failed to send message/receive response. RC = " << rc 425 << ", errno = " << errno << "\n"; 426 return PLDM_ERROR; 427 } 428 429 // Time out or soft off complete 430 while (!isCompleted() && !isTimerExpired()) 431 { 432 try 433 { 434 event.run(std::nullopt); 435 } 436 catch (const sdeventplus::SdEventError& e) 437 { 438 std::cerr 439 << "PLDM host soft off: Failure in processing request.ERROR= " 440 << e.what() << "\n"; 441 return PLDM_ERROR; 442 } 443 } 444 445 return PLDM_SUCCESS; 446 } 447 448 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec) 449 { 450 return timer.start(usec); 451 } 452 } // namespace pldm 453