From 57dc7fbdd1dceb8c82512dae754bf4a8370a4010 Mon Sep 17 00:00:00 2001 From: roly Date: Fri, 7 Feb 2025 14:42:30 +0800 Subject: [PATCH] Add power cycle trigger async firmware update --- chassishandler.cpp | 216 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 216 insertions(+) diff --git a/chassishandler.cpp b/chassishandler.cpp index 89d3e00..8e68ac5 100644 --- a/chassishandler.cpp +++ b/chassishandler.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -813,6 +814,196 @@ ipmi::RspType<> ipmiSetChassisCap(bool intrusion, bool fpLockout, return ipmi::responseSuccess(); } +//------------------------------------------------ +// Is power on (check power state before async update) +//------------------------------------------------ +bool isPowerOn() +{ + std::variant msg; + sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; + try + { + auto method = bus.new_method_call("xyz.openbmc_project.State.Chassis", + "/xyz/openbmc_project/state/chassis0", + "org.freedesktop.DBus.Properties", + "Get"); + method.append("xyz.openbmc_project.State.Chassis"); + method.append("CurrentPowerState"); + auto reply = bus.call(method); + reply.read(msg); + return std::get(msg).find("Off") != std::string::npos + ? false + : true; + } + catch (const std::exception& /*ex*/) + { + log("Can not get power status"); + return true; + } +} + +//------------------------------------------------ +// Async update start (return status or no value)) +//------------------------------------------------ +std::string asyncUpdateStart() +{ + sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; + std::string asyncStatus{}; + auto method = bus.new_method_call( + "xyz.openbmc_project.Software.AsyncUpdate", + "/xyz/openbmc_project/Software/AsyncUpdate", + "xyz.openbmc_project.Software.AsyncUpdate", "Start"); + try + { + auto reply = bus.call(method); + reply.read(asyncStatus); + return asyncStatus; + } + catch (const sdbusplus::exception::SdBusError& /*ex*/) + { + log("Error in starting async update"); + return ""; + } +} + +//------------------------------------------------ +// Get async update status (return status or no value) +//------------------------------------------------ +std::string asyncUpdateStatus() +{ + sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; + try + { + auto status = ipmi::getDbusProperty( + bus, "xyz.openbmc_project.Software.AsyncUpdate", + "/xyz/openbmc_project/Software/AsyncUpdate", + "xyz.openbmc_project.Software.AsyncUpdate", "AsyncUpdateStatus"); + return std::get(status); + } + catch (const sdbusplus::exception::SdBusError& /*ex*/) + { + log("Can not get async update status"); + return ""; + } +} + +int initiateHostStateTransition(ipmi::Context::ptr& ctx, + State::Host::Transition transition); +int initiateChassisStateTransition(ipmi::Context::ptr& ctx, + State::Chassis::Transition transition); +//--------------------------------------------------------------------------- +// if power cycle wait for power on +//--------------------------------------------------------------------------- +void waitForUpdateDone() +{ + static boost::asio::steady_timer timer(*getIoContext()); + timer.expires_after(boost::asio::chrono::seconds(15)); + timer.async_wait([&](const boost::system::error_code& ec) { + if (ec) + { + return; + } + if (asyncUpdateStatus() != "Idle") + { + waitForUpdateDone(); + return; + } + sdbusplus::bus::bus bus{ipmid_get_sd_bus_connection()}; + try + { + auto method = + bus.new_method_call("xyz.openbmc_project.State.Host", + "/xyz/openbmc_project/state/host0", + "org.freedesktop.DBus.Properties", "Set"); + + method.append("xyz.openbmc_project.State.Host"); + method.append("RequestedHostTransition"); + method.append(std::variant{ + "xyz.openbmc_project.State.Host.Transition.On"}); + bus.call_noreply(method); + } + catch (const sdbusplus::exception::SdBusError& /*ex*/) + { + log("Can not power On after async update"); + } + }); +} +//--------------------------------------------------------------------------- +// wait for power off to start update +//--------------------------------------------------------------------------- +void waitForStartUpdate(bool doPowerOn) +{ + static boost::asio::steady_timer timer(*getIoContext()); + timer.expires_after(boost::asio::chrono::seconds(15)); + timer.async_wait( + [doPowerOn](const boost::system::error_code& ec) { + if (ec) + { + return; + } + if (isPowerOn()) // power status is not "off" + { + waitForStartUpdate(doPowerOn); + return; + } + try + { + asyncUpdateStart(); + if (doPowerOn) + { + waitForUpdateDone(); + } + } + catch (const sdbusplus::exception::SdBusError& /*ex*/) + { + log("Can not start update"); + } + }); +} + +enum class PowerControlAsyncStatus +{ + active, + running, + idle +}; + +//--------------------------------------------------------------------------- +// hook host power control to trigger async update +// return true if async-update is not in progress +//--------------------------------------------------------------------------- +PowerControlAsyncStatus + processPowerControlHost(ipmi::Context::ptr& ctx, + State::Host::Transition transition) +{ + std::string asyncStatus = asyncUpdateStatus(); + auto doPowerOn = isPowerOn(); + switch (transition) + { + case State::Host::Transition::Reboot: + if (asyncStatus == "Waiting") + { + initiateChassisStateTransition(ctx, + State::Chassis::Transition::Off); + waitForStartUpdate(doPowerOn); + return PowerControlAsyncStatus::active; + } + else if (asyncStatus == "Running") + { + return PowerControlAsyncStatus::running; + } + break; + case State::Host::Transition::On: + if (asyncStatus == "Running") + { + return PowerControlAsyncStatus::running; + } + break; + default: + break; + } + return PowerControlAsyncStatus::idle; +} //------------------------------------------ // Calls into Host State Manager Dbus object //------------------------------------------ @@ -1363,9 +1554,18 @@ ipmi::RspType<> ipmiChassisControl(ipmi::Context::ptr& ctx, uint8_t chassisControl) { int rc = 0; + PowerControlAsyncStatus asyncUpdateStatus = PowerControlAsyncStatus::idle; switch (chassisControl) { case CMD_POWER_ON: + asyncUpdateStatus = + processPowerControlHost(ctx, State::Host::Transition::On); + if (asyncUpdateStatus != PowerControlAsyncStatus::idle) + { + log( + "Power-on opertion is not allowed during the upgrade process"); + return ipmi::responseCommandNotAvailable(); + } rc = initiateHostStateTransition(ctx, State::Host::Transition::On); break; case CMD_POWER_OFF: @@ -1377,6 +1577,22 @@ ipmi::RspType<> ipmiChassisControl(ipmi::Context::ptr& ctx, ctx, State::Host::Transition::ForceWarmReboot); break; case CMD_POWER_CYCLE: + asyncUpdateStatus = + processPowerControlHost(ctx, State::Host::Transition::Reboot); + if (asyncUpdateStatus == PowerControlAsyncStatus::active) + { + log( + "Power-cycle operation will activate the async-update, let the async-update flow to do the power-cycle"); + rc = ipmi::ccSuccess; + break; + } + else if (asyncUpdateStatus == PowerControlAsyncStatus::running) + { + log( + "Power-cycle operation is not allowed during the upgrade"); + rc = ipmi::ccCommandNotAvailable; + break; + } rc = initiateHostStateTransition(ctx, State::Host::Transition::Reboot); break; -- 2.25.1