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 sensorID = pdr->sensor_id; 262 263 auto compositeSensorCount = pdr->composite_sensor_count; 264 auto possibleStatesStart = pdr->possible_states; 265 266 for (auto offset = 0; offset < compositeSensorCount; offset++) 267 { 268 auto possibleStates = 269 reinterpret_cast<state_sensor_possible_states*>( 270 possibleStatesStart); 271 auto setId = possibleStates->state_set_id; 272 auto possibleStateSize = possibleStates->possible_states_size; 273 274 if (setId == PLDM_STATE_SET_SW_TERMINATION_STATUS) 275 { 276 sensorOffset = offset; 277 break; 278 } 279 possibleStatesStart += 280 possibleStateSize + sizeof(setId) + sizeof(possibleStateSize); 281 } 282 } 283 catch (const SdBusError& e) 284 { 285 std::cerr << "PLDM soft off: Error get State Sensor PDR,ERROR=" 286 << e.what() << "\n"; 287 return PLDM_ERROR; 288 } 289 290 return PLDM_SUCCESS; 291 } 292 293 int SoftPowerOff::hostSoftOff(sdeventplus::Event& event) 294 { 295 constexpr uint8_t effecterCount = 1; 296 uint8_t mctpEID; 297 uint8_t instanceID; 298 299 mctpEID = pldm::utils::readHostEID(); 300 301 // Get instanceID 302 try 303 { 304 auto& bus = pldm::utils::DBusHandler::getBus(); 305 auto method = bus.new_method_call( 306 "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm", 307 "xyz.openbmc_project.PLDM.Requester", "GetInstanceId"); 308 method.append(mctpEID); 309 310 auto ResponseMsg = bus.call(method); 311 312 ResponseMsg.read(instanceID); 313 } 314 catch (const SdBusError& e) 315 { 316 std::cerr << "PLDM soft off: Error get instanceID,ERROR=" << e.what() 317 << "\n"; 318 return PLDM_ERROR; 319 } 320 321 std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(effecterID) + 322 sizeof(effecterCount) + 323 sizeof(set_effecter_state_field)> 324 requestMsg{}; 325 auto request = reinterpret_cast<pldm_msg*>(requestMsg.data()); 326 set_effecter_state_field stateField{ 327 PLDM_REQUEST_SET, PLDM_SW_TERM_GRACEFUL_SHUTDOWN_REQUESTED}; 328 auto rc = encode_set_state_effecter_states_req( 329 instanceID, effecterID, effecterCount, &stateField, request); 330 if (rc != PLDM_SUCCESS) 331 { 332 std::cerr << "Message encode failure. PLDM error code = " << std::hex 333 << std::showbase << rc << "\n"; 334 return PLDM_ERROR; 335 } 336 337 // Open connection to MCTP socket 338 int fd = pldm_open(); 339 if (-1 == fd) 340 { 341 std::cerr << "Failed to connect to mctp demux daemon" 342 << "\n"; 343 return PLDM_ERROR; 344 } 345 346 // Add a timer to the event loop, default 30s. 347 auto timerCallback = [=](Timer& /*source*/, Timer::TimePoint /*time*/) { 348 if (!responseReceived) 349 { 350 std::cerr << "PLDM soft off: ERROR! Can't get the response for the " 351 "PLDM request msg. Time out!\n" 352 << "Exit the pldm-softpoweroff\n"; 353 exit(-1); 354 } 355 return; 356 }; 357 Timer time(event, (Clock(event).now() + std::chrono::seconds{30}), 358 std::chrono::seconds{1}, std::move(timerCallback)); 359 360 // Add a callback to handle EPOLLIN on fd 361 auto callback = [=](IO& io, int fd, uint32_t revents) { 362 if (!(revents & EPOLLIN)) 363 { 364 return; 365 } 366 367 uint8_t* responseMsg = nullptr; 368 size_t responseMsgSize{}; 369 370 auto rc = pldm_recv(mctpEID, fd, request->hdr.instance_id, &responseMsg, 371 &responseMsgSize); 372 if (rc) 373 { 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 std::cerr << "Getting the response. PLDM RC = " << std::hex 385 << std::showbase 386 << static_cast<uint16_t>(response->payload[0]) << "\n"; 387 388 responseReceived = true; 389 390 // Start Timer 391 using namespace std::chrono; 392 auto timeMicroseconds = 393 duration_cast<microseconds>(seconds(SOFTOFF_TIMEOUT_SECONDS)); 394 395 auto ret = startTimer(timeMicroseconds); 396 if (ret < 0) 397 { 398 std::cerr << "Failure to start Host soft off wait timer, ERRNO = " 399 << ret << "Exit the pldm-softpoweroff\n"; 400 exit(-1); 401 } 402 else 403 { 404 std::cerr << "Timer started waiting for host soft off, " 405 "TIMEOUT_IN_SEC = " 406 << SOFTOFF_TIMEOUT_SECONDS << "\n"; 407 } 408 return; 409 }; 410 IO io(event, fd, EPOLLIN, std::move(callback)); 411 412 // Send PLDM Request message - pldm_send doesn't wait for response 413 rc = pldm_send(mctpEID, fd, requestMsg.data(), requestMsg.size()); 414 if (rc != PLDM_REQUESTER_SUCCESS) 415 { 416 std::cerr << "Failed to send message/receive response. RC = " << rc 417 << ", errno = " << errno << "\n"; 418 return PLDM_ERROR; 419 } 420 421 // Time out or soft off complete 422 while (!isCompleted() && !isTimerExpired()) 423 { 424 try 425 { 426 event.run(std::nullopt); 427 } 428 catch (const sdeventplus::SdEventError& e) 429 { 430 std::cerr 431 << "PLDM host soft off: Failure in processing request.ERROR= " 432 << e.what() << "\n"; 433 return PLDM_ERROR; 434 } 435 } 436 437 return PLDM_SUCCESS; 438 } 439 440 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec) 441 { 442 return timer.start(usec); 443 } 444 } // namespace pldm 445