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