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/source/io.hpp> 15 #include <sdeventplus/source/time.hpp> 16 17 #include <array> 18 #include <iostream> 19 20 namespace pldm 21 { 22 23 using namespace sdeventplus; 24 using namespace sdeventplus::source; 25 constexpr auto clockId = sdeventplus::ClockId::RealTime; 26 using Clock = Clock<clockId>; 27 using Timer = Time<clockId>; 28 29 using sdbusplus::exception::SdBusError; 30 31 constexpr pldm::pdr::TerminusID TID = 0; // TID will be implemented later. 32 namespace sdbusRule = sdbusplus::bus::match::rules; 33 34 SoftPowerOff::SoftPowerOff(sdbusplus::bus::bus& bus, sd_event* event) : 35 bus(bus), timer(event) 36 { 37 auto rc = getHostState(); 38 if (hasError || completed) 39 { 40 return; 41 } 42 43 rc = getEffecterID(); 44 if (completed) 45 { 46 std::cerr 47 << "pldm-softpoweroff: effecter to initiate softoff not found \n"; 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 std::cerr << "Message get Sensor PDRs error. PLDM " 60 "error code = " 61 << std::hex << std::showbase << rc << "\n"; 62 hasError = true; 63 return; 64 } 65 66 // Matches on the pldm StateSensorEvent signal 67 pldmEventSignal = std::make_unique<sdbusplus::bus::match_t>( 68 bus, 69 sdbusRule::type::signal() + sdbusRule::member("StateSensorEvent") + 70 sdbusRule::path("/xyz/openbmc_project/pldm") + 71 sdbusRule::interface("xyz.openbmc_project.PLDM.Event"), 72 std::bind(std::mem_fn(&SoftPowerOff::hostSoftOffComplete), this, 73 std::placeholders::_1)); 74 } 75 76 int SoftPowerOff::getHostState() 77 { 78 try 79 { 80 pldm::utils::PropertyValue propertyValue = 81 pldm::utils::DBusHandler().getDbusPropertyVariant( 82 "/xyz/openbmc_project/state/host0", "CurrentHostState", 83 "xyz.openbmc_project.State.Host"); 84 85 if (std::get<std::string>(propertyValue) != 86 "xyz.openbmc_project.State.Host.HostState.Running") 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::message& 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 SdBusError& 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 SdBusError& 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 } 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 += 277 possibleStateSize + sizeof(setId) + sizeof(possibleStateSize); 278 } 279 } 280 catch (const SdBusError& e) 281 { 282 std::cerr << "PLDM soft off: Error get State Sensor PDR,ERROR=" 283 << e.what() << "\n"; 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); 308 309 ResponseMsg.read(instanceID); 310 } 311 catch (const SdBusError& e) 312 { 313 std::cerr << "PLDM soft off: Error get instanceID,ERROR=" << e.what() 314 << "\n"; 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 std::cerr << "Message encode failure. PLDM error code = " << std::hex 330 << std::showbase << rc << "\n"; 331 return PLDM_ERROR; 332 } 333 334 // Open connection to MCTP socket 335 int fd = pldm_open(); 336 if (-1 == fd) 337 { 338 std::cerr << "Failed to connect to mctp demux daemon" 339 << "\n"; 340 return PLDM_ERROR; 341 } 342 343 // Add a timer to the event loop, default 30s. 344 auto timerCallback = [=](Timer& /*source*/, Timer::TimePoint /*time*/) { 345 if (!responseReceived) 346 { 347 std::cerr << "PLDM soft off: ERROR! Can't get the response for the " 348 "PLDM request msg. Time out!\n" 349 << "Exit the pldm-softpoweroff\n"; 350 exit(-1); 351 } 352 return; 353 }; 354 Timer time(event, (Clock(event).now() + std::chrono::seconds{30}), 355 std::chrono::seconds{1}, std::move(timerCallback)); 356 357 // Add a callback to handle EPOLLIN on fd 358 auto callback = [=](IO& io, int fd, uint32_t revents) { 359 if (!(revents & EPOLLIN)) 360 { 361 return; 362 } 363 364 uint8_t* responseMsg = nullptr; 365 size_t responseMsgSize{}; 366 367 auto rc = pldm_recv(mctpEID, fd, request->hdr.instance_id, &responseMsg, 368 &responseMsgSize); 369 if (rc) 370 { 371 return; 372 } 373 374 std::unique_ptr<uint8_t, decltype(std::free)*> responseMsgPtr{ 375 responseMsg, std::free}; 376 377 // We've got the response meant for the PLDM request msg that was 378 // sent out 379 io.set_enabled(Enabled::Off); 380 auto response = reinterpret_cast<pldm_msg*>(responseMsgPtr.get()); 381 std::cerr << "Getting the response. PLDM RC = " << std::hex 382 << std::showbase 383 << static_cast<uint16_t>(response->payload[0]) << "\n"; 384 385 responseReceived = true; 386 387 // Start Timer 388 using namespace std::chrono; 389 auto timeMicroseconds = 390 duration_cast<microseconds>(seconds(SOFTOFF_TIMEOUT_SECONDS)); 391 392 auto ret = startTimer(timeMicroseconds); 393 if (ret < 0) 394 { 395 std::cerr << "Failure to start Host soft off wait timer, ERRNO = " 396 << ret << "Exit the pldm-softpoweroff\n"; 397 exit(-1); 398 } 399 else 400 { 401 std::cerr << "Timer started waiting for host soft off, " 402 "TIMEOUT_IN_SEC = " 403 << SOFTOFF_TIMEOUT_SECONDS << "\n"; 404 } 405 return; 406 }; 407 IO io(event, fd, EPOLLIN, std::move(callback)); 408 409 // Send PLDM Request message - pldm_send doesn't wait for response 410 rc = pldm_send(mctpEID, fd, requestMsg.data(), requestMsg.size()); 411 if (0 > rc) 412 { 413 std::cerr << "Failed to send message/receive response. RC = " << rc 414 << ", errno = " << errno << "\n"; 415 return PLDM_ERROR; 416 } 417 418 // Time out or soft off complete 419 while (!isCompleted() && !isTimerExpired()) 420 { 421 try 422 { 423 event.run(std::nullopt); 424 } 425 catch (const sdeventplus::SdEventError& e) 426 { 427 std::cerr 428 << "PLDM host soft off: Failure in processing request.ERROR= " 429 << e.what() << "\n"; 430 return PLDM_ERROR; 431 } 432 } 433 434 return PLDM_SUCCESS; 435 } 436 437 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec) 438 { 439 return timer.start(usec); 440 } 441 } // namespace pldm 442