1 #include "softoff.hpp" 2 3 #include "common/instance_id.hpp" 4 #include "common/transport.hpp" 5 #include "common/utils.hpp" 6 7 #include <libpldm/entity.h> 8 #include <libpldm/platform.h> 9 #include <libpldm/state_set.h> 10 11 #include <phosphor-logging/lg2.hpp> 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 20 PHOSPHOR_LOG2_USING; 21 22 namespace pldm 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 pldm::InstanceIdDb& instanceIdDb) : 35 bus(bus), 36 timer(event), instanceIdDb(instanceIdDb) 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: {ERROR}", 97 "ERROR", e); 98 hasError = true; 99 return PLDM_ERROR; 100 } 101 102 return PLDM_SUCCESS; 103 } 104 105 void SoftPowerOff::hostSoftOffComplete(sdbusplus::message_t& msg) 106 { 107 pldm::pdr::TerminusID msgTID; 108 pldm::pdr::SensorID msgSensorID; 109 pldm::pdr::SensorOffset msgSensorOffset; 110 pldm::pdr::EventState msgEventState; 111 pldm::pdr::EventState msgPreviousEventState; 112 113 // Read the msg and populate each variable 114 msg.read(msgTID, msgSensorID, msgSensorOffset, msgEventState, 115 msgPreviousEventState); 116 117 if (msgSensorID == sensorID && msgSensorOffset == sensorOffset && 118 msgEventState == PLDM_SW_TERM_GRACEFUL_SHUTDOWN) 119 { 120 // Receive Graceful shutdown completion event message. Disable the timer 121 auto rc = timer.stop(); 122 if (rc < 0) 123 { 124 error("PLDM soft off: Failure to STOP the timer. ERRNO={RC}", "RC", 125 rc); 126 } 127 128 // This marks the completion of pldm soft power off. 129 completed = true; 130 } 131 } 132 133 int SoftPowerOff::getEffecterID() 134 { 135 auto& bus = pldm::utils::DBusHandler::getBus(); 136 137 // VMM is a logical entity, so the bit 15 in entity type is set. 138 pdr::EntityType entityType = PLDM_ENTITY_VIRTUAL_MACHINE_MANAGER | 0x8000; 139 140 try 141 { 142 std::vector<std::vector<uint8_t>> VMMResponse{}; 143 auto VMMMethod = bus.new_method_call( 144 "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm", 145 "xyz.openbmc_project.PLDM.PDR", "FindStateEffecterPDR"); 146 VMMMethod.append(TID, entityType, 147 (uint16_t)PLDM_STATE_SET_SW_TERMINATION_STATUS); 148 149 auto VMMResponseMsg = bus.call(VMMMethod, dbusTimeout); 150 151 VMMResponseMsg.read(VMMResponse); 152 if (VMMResponse.size() != 0) 153 { 154 for (auto& rep : VMMResponse) 155 { 156 auto VMMPdr = 157 reinterpret_cast<pldm_state_effecter_pdr*>(rep.data()); 158 effecterID = VMMPdr->effecter_id; 159 } 160 } 161 else 162 { 163 VMMPdrExist = false; 164 } 165 } 166 catch (const sdbusplus::exception_t& e) 167 { 168 error("PLDM soft off: Error get VMM PDR,ERROR={ERR_EXCEP}", "ERR_EXCEP", 169 e.what()); 170 VMMPdrExist = false; 171 } 172 173 if (VMMPdrExist) 174 { 175 return PLDM_SUCCESS; 176 } 177 178 // If the Virtual Machine Manager PDRs doesn't exist, go find the System 179 // Firmware PDRs. 180 // System Firmware is a logical entity, so the bit 15 in entity type is set 181 entityType = PLDM_ENTITY_SYS_FIRMWARE | 0x8000; 182 try 183 { 184 std::vector<std::vector<uint8_t>> sysFwResponse{}; 185 auto sysFwMethod = bus.new_method_call( 186 "xyz.openbmc_project.PLDM", "/xyz/openbmc_project/pldm", 187 "xyz.openbmc_project.PLDM.PDR", "FindStateEffecterPDR"); 188 sysFwMethod.append(TID, entityType, 189 (uint16_t)PLDM_STATE_SET_SW_TERMINATION_STATUS); 190 191 auto sysFwResponseMsg = bus.call(sysFwMethod, dbusTimeout); 192 193 sysFwResponseMsg.read(sysFwResponse); 194 195 if (sysFwResponse.size() == 0) 196 { 197 error("No effecter ID has been found that matches the criteria"); 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 error("PLDM soft off: Error get system firmware PDR,ERROR={ERR_EXCEP}", 211 "ERR_EXCEP", e.what()); 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, dbusTimeout); 241 242 ResponseMsg.read(Response); 243 244 if (Response.size() == 0) 245 { 246 error("No sensor PDR has been found that matches the criteria"); 247 return PLDM_ERROR; 248 } 249 250 pldm_state_sensor_pdr* pdr = nullptr; 251 for (auto& rep : Response) 252 { 253 pdr = reinterpret_cast<pldm_state_sensor_pdr*>(rep.data()); 254 if (!pdr) 255 { 256 error("Failed to get state sensor PDR."); 257 return PLDM_ERROR; 258 } 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 += possibleStateSize + sizeof(setId) + 280 sizeof(possibleStateSize); 281 } 282 } 283 catch (const sdbusplus::exception_t& e) 284 { 285 error("PLDM soft off: Error get State Sensor PDR,ERROR={ERR_EXCEP}", 286 "ERR_EXCEP", e.what()); 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 PldmTransport pldmTransport{}; 297 uint8_t instanceID; 298 uint8_t mctpEID; 299 300 mctpEID = pldm::utils::readHostEID(); 301 // TODO: fix mapping to work around OpenBMC ecosystem deficiencies 302 pldm_tid_t pldmTID = static_cast<pldm_tid_t>(mctpEID); 303 304 std::array<uint8_t, sizeof(pldm_msg_hdr) + sizeof(effecterID) + 305 sizeof(effecterCount) + 306 sizeof(set_effecter_state_field)> 307 requestMsg{}; 308 auto request = reinterpret_cast<pldm_msg*>(requestMsg.data()); 309 set_effecter_state_field stateField{ 310 PLDM_REQUEST_SET, PLDM_SW_TERM_GRACEFUL_SHUTDOWN_REQUESTED}; 311 instanceID = instanceIdDb.next(pldmTID); 312 auto rc = encode_set_state_effecter_states_req( 313 instanceID, effecterID, effecterCount, &stateField, request); 314 if (rc != PLDM_SUCCESS) 315 { 316 instanceIdDb.free(pldmTID, instanceID); 317 error("Message encode failure. PLDM error code = {RC}", "RC", lg2::hex, 318 static_cast<int>(rc)); 319 return PLDM_ERROR; 320 } 321 322 // Add a timer to the event loop, default 30s. 323 auto timerCallback = [=, this](Timer& /*source*/, 324 Timer::TimePoint /*time*/) mutable { 325 if (!responseReceived) 326 { 327 instanceIdDb.free(pldmTID, instanceID); 328 error( 329 "PLDM soft off: ERROR! Can't get the response for the PLDM request msg. Time out! Exit the pldm-softpoweroff"); 330 exit(-1); 331 } 332 return; 333 }; 334 Timer time(event, (Clock(event).now() + std::chrono::seconds{30}), 335 std::chrono::seconds{1}, std::move(timerCallback)); 336 337 // Add a callback to handle EPOLLIN on fd 338 auto callback = [=, &pldmTransport, this](IO& io, int fd, 339 uint32_t revents) mutable { 340 if (fd != pldmTransport.getEventSource()) 341 { 342 return; 343 } 344 345 if (!(revents & EPOLLIN)) 346 { 347 return; 348 } 349 350 void* responseMsg = nullptr; 351 size_t responseMsgSize{}; 352 pldm_tid_t srcTID = pldmTID; 353 354 auto rc = pldmTransport.recvMsg(pldmTID, responseMsg, responseMsgSize); 355 if (rc) 356 { 357 error("Soft off: failed to recv pldm data. PLDM RC = {RC}", "RC", 358 static_cast<int>(rc)); 359 return; 360 } 361 362 std::unique_ptr<void, decltype(std::free)*> responseMsgPtr{responseMsg, 363 std::free}; 364 365 // We've got the response meant for the PLDM request msg that was 366 // sent out 367 io.set_enabled(Enabled::Off); 368 auto response = reinterpret_cast<pldm_msg*>(responseMsgPtr.get()); 369 370 if (srcTID != pldmTID || 371 !pldm_msg_hdr_correlate_response(&request->hdr, &response->hdr)) 372 { 373 /* This isn't the response we were looking for */ 374 return; 375 } 376 377 /* We have the right response, release the instance ID and process */ 378 io.set_enabled(Enabled::Off); 379 instanceIdDb.free(pldmTID, instanceID); 380 381 if (response->payload[0] != PLDM_SUCCESS) 382 { 383 error("Getting the wrong response. PLDM RC = {RC}", "RC", 384 (unsigned)response->payload[0]); 385 exit(-1); 386 } 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 error( 399 "Failure to start Host soft off wait timer, ERRNO = {RET}. Exit the pldm-softpoweroff", 400 "RET", ret); 401 exit(-1); 402 } 403 else 404 { 405 error( 406 "Timer started waiting for host soft off, TIMEOUT_IN_SEC = {TIMEOUT_SEC}", 407 "TIMEOUT_SEC", SOFTOFF_TIMEOUT_SECONDS); 408 } 409 return; 410 }; 411 IO io(event, pldmTransport.getEventSource(), EPOLLIN, std::move(callback)); 412 413 // Asynchronously send the PLDM request 414 rc = pldmTransport.sendMsg(pldmTID, requestMsg.data(), requestMsg.size()); 415 if (0 > rc) 416 { 417 instanceIdDb.free(pldmTID, instanceID); 418 error( 419 "Failed to send message/receive response. RC = {RC}, errno = {ERR}", 420 "RC", static_cast<int>(rc), "ERR", errno); 421 return PLDM_ERROR; 422 } 423 424 // Time out or soft off complete 425 while (!isCompleted() && !isTimerExpired()) 426 { 427 try 428 { 429 event.run(std::nullopt); 430 } 431 catch (const sdeventplus::SdEventError& e) 432 { 433 instanceIdDb.free(pldmTID, instanceID); 434 error( 435 "PLDM host soft off: Failure in processing request.ERROR= {ERR_EXCEP}", 436 "ERR_EXCEP", e.what()); 437 return PLDM_ERROR; 438 } 439 } 440 441 return PLDM_SUCCESS; 442 } 443 444 int SoftPowerOff::startTimer(const std::chrono::microseconds& usec) 445 { 446 return timer.start(usec); 447 } 448 } // namespace pldm 449