From bfa8b769aa57a3e25299640d36dd16d3037d5859 Mon Sep 17 00:00:00 2001 From: roly Date: Mon, 6 Jan 2025 18:11:52 +0800 Subject: [PATCH] Sypport applyoption and updatetarget parameter --- activation.cpp | 11 +- activation.hpp | 38 ++++ image_async_main.cpp | 360 +++++++++++++++++++++++++++++++++++ item_updater.cpp | 26 ++- meson.build | 9 + meson_options.txt | 5 + obmc-async-update.service.in | 11 ++ static/flash.cpp | 77 ++++++-- 8 files changed, 513 insertions(+), 24 deletions(-) create mode 100755 image_async_main.cpp create mode 100755 obmc-async-update.service.in diff --git a/activation.cpp b/activation.cpp index f866461..7fb4038 100644 --- a/activation.cpp +++ b/activation.cpp @@ -175,14 +175,14 @@ auto Activation::activation(Activations value) -> Activations #else // STATIC_LAYOUT - if (parent.runningImageSlot == 0) - { + // if (parent.runningImageSlot == 0) + // { // On primary, update it as before onFlashWriteSuccess(); return softwareServer::Activation::activation( softwareServer::Activation::Activations::Active); - } - // On secondary, wait for the service to complete + // } + // // On secondary, wait for the service to complete #endif } else @@ -198,6 +198,7 @@ void Activation::onFlashWriteSuccess() activationBlocksTransition.reset(nullptr); activationProgress.reset(nullptr); + applyOptions.reset(); rwVolumeCreated = false; roVolumeCreated = false; @@ -291,6 +292,8 @@ auto Activation::requestedActivation(RequestedActivations value) softwareServer::Activation::RequestedActivations::Active)) { if ((softwareServer::Activation::activation() == + softwareServer::Activation::Activations::AddQueue) || + (softwareServer::Activation::activation() == softwareServer::Activation::Activations::Ready) || (softwareServer::Activation::activation() == softwareServer::Activation::Activations::Failed)) diff --git a/activation.hpp b/activation.hpp index 7d10472..0c6d8b5 100644 --- a/activation.hpp +++ b/activation.hpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #ifdef WANT_SIGNATURE_VERIFY #include @@ -39,6 +41,10 @@ using RedundancyPriorityInherit = sdbusplus::server::object_t< sdbusplus::xyz::openbmc_project::Software::server::RedundancyPriority>; using ActivationProgressInherit = sdbusplus::server::object_t< sdbusplus::xyz::openbmc_project::Software::server::ActivationProgress>; +using UpdateTargetInherit = sdbusplus::server::object::object< + sdbusplus::xyz::openbmc_project::Software::server::UpdateTarget>; +using ApplyOptionsInherit = sdbusplus::server::object::object< + sdbusplus::xyz::openbmc_project::Software::server::ApplyOptions>; constexpr auto applyTimeImmediate = "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.Immediate"; @@ -166,6 +172,32 @@ class ActivationProgress : public ActivationProgressInherit } }; +class UpdateTarget : public UpdateTargetInherit +{ + public: + /** @brief Constructs UpdateTarget + * + * @param[in] bus - The Dbus bus object + * @param[in] path - The Dbus object path + */ + UpdateTarget(sdbusplus::bus::bus& bus, const std::string& path) : + UpdateTargetInherit(bus, path.c_str(), action::emit_interface_added) + {} +}; + +class ApplyOptions : public ApplyOptionsInherit +{ + public: + /** @brief Constructs ApplyOptions + * + * @param[in] bus - The Dbus bus object + * @param[in] path - The Dbus object path + */ + ApplyOptions(sdbusplus::bus::bus& bus, const std::string& path) : + ApplyOptionsInherit(bus, path.c_str(), action::emit_interface_added) + {} +}; + /** @class Activation * @brief OpenBMC activation software management implementation. * @details A concrete implementation for @@ -328,6 +360,12 @@ class Activation : public ActivationInherit, public Flash /** @brief Persistent ActivationProgress dbus object */ std::unique_ptr activationProgress; + /** @brief Persistent UpdateTarget dbus object */ + std::unique_ptr updateTarget; + + /** @brief Persistent ApplyOptions dbus object */ + std::unique_ptr applyOptions; + /** @brief Used to subscribe to dbus systemd signals **/ sdbusplus::bus::match_t systemdSignals; diff --git a/image_async_main.cpp b/image_async_main.cpp new file mode 100755 index 0000000..bd17564 --- /dev/null +++ b/image_async_main.cpp @@ -0,0 +1,360 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace phosphor::logging; +using DbusVariant = std::variant, std::string, bool>; + +static boost::asio::io_context ioCtx; +static std::shared_ptr conn; +static std::shared_ptr interface; +static std::shared_ptr match; + +static boost::asio::steady_timer checkTimer(ioCtx); +static boost::asio::steady_timer updateTimer(ioCtx); +static std::string status{"Idle"}; + +static std::deque> components; +static bool powerStatus = false; +static std::unique_ptr powerMatch = nullptr; + +static std::string handlerStart(); + +namespace power +{ +const static constexpr char* busname = "xyz.openbmc_project.State.Chassis"; +const static constexpr char* interface = "xyz.openbmc_project.State.Chassis"; +const static constexpr char* path = "/xyz/openbmc_project/state/chassis0"; +const static constexpr char* property = "CurrentPowerState"; +} // namespace power + +namespace properties +{ +constexpr const char* interface = "org.freedesktop.DBus.Properties"; +constexpr const char* get = "Get"; +constexpr const char* set = "Set"; +} // namespace properties + +static void + getPowerStatus(const std::shared_ptr& conn, + size_t retries = 2) +{ + conn->async_method_call( + [conn, retries](boost::system::error_code ec, + const std::variant& state) { + if (ec) + { + if (retries != 0U) + { + auto timer = std::make_shared( + conn->get_io_context()); + timer->expires_after(std::chrono::seconds(15)); + timer->async_wait( + [timer, conn, retries](boost::system::error_code) { + getPowerStatus(conn, retries - 1); + }); + return; + } + + // we commonly come up before power control, we'll capture the + // property change later + lg2::error("error getting power status {ERROR}", "ERROR", + ec.message()); + return; + } + powerStatus = std::get(state).ends_with(".On"); + }, + power::busname, power::path, properties::interface, properties::get, + power::interface, power::property); +} + +void setupPowerMonitor(const std::shared_ptr& conn) +{ + // create a match for powergood changes, first time do a method call to + // cache the correct value + if (powerMatch) + { + return; + } + + powerMatch = std::make_unique( + static_cast(*conn), + "type='signal',interface='" + std::string(properties::interface) + + "',path='" + std::string(power::path) + "',arg0='" + + std::string(power::interface) + "'", + [](sdbusplus::message_t& message) { + std::string objectName; + std::map> values; + message.read(objectName, values); + auto findState = values.find(power::property); + if (findState != values.end()) + { + powerStatus = + std::get(findState->second).ends_with(".Running"); + } + }); + + getPowerStatus(conn); +} + +/** + * @brief remove image form bmc filesystem. + * @param[in] imageID - old image. + * @details if image type is same as in queue, remove old image. + * + */ +void removeExistImage(const std::string& imageID) +{ + auto oldImage = "/xyz/openbmc_project/software/" + imageID; + conn->async_method_call([](const boost::system::error_code& /*ec*/) {}, + "xyz.openbmc_project.Software.BMC.Updater", + oldImage, "xyz.openbmc_project.Object.Delete", + "Delete"); +} + +/** + * @brief Async AddQueue method implementation for dbus. + * @param[in] versionPurpose - VersionPurpose as unique key. + * @param[in] imageID - get from bmc. + * @details REST API + * https:///xyz/openbmc_project/Software/AsyncUpdate/action/AddQueue for for + * web and update-script. + * + */ +static void handlerAddQueue(std::string& versionPurpose, std::string& imageID) +{ + // remove same image from bmc filesystem. + // position is same as before in queue + bool existImage = false; + for (auto& item : components) + { + if (item.first == versionPurpose) + { + removeExistImage(item.second); + item.second = imageID; + existImage = true; + break; + } + } + if (!existImage) + { + components.push_back(std::make_pair(versionPurpose, imageID)); + } + // if Running, keep Running status. + if (status == "Idle") + { + status = "Waiting"; + interface->set_property("AsyncUpdateStatus", status); + } + // set the status of async upgrade to add queue + auto objectPath = "/xyz/openbmc_project/software/" + imageID; + try + { + conn->async_method_call( + [](const boost::system::error_code& /*ec*/) {}, + "xyz.openbmc_project.Software.BMC.Updater", objectPath, + "org.freedesktop.DBus.Properties", "Set", + "xyz.openbmc_project.Software.Activation", "Activation", + DbusVariant( + "xyz.openbmc_project.Software.Activation.Activations.AddQueue")); + } + catch (const sdbusplus::exception::SdBusError& e) + { + lg2::error("Can not to set async upgrade status: {ERROR}", "ERROR", e); + } +} + +/** + * @brief Get power status. + * @return if true means power is off + * false power on + * + */ +static bool isPowerOff() +{ + return powerStatus == false; +} + +/** + * @brief start update. + */ +static void activateImage() +{ + status = "Running"; + interface->set_property("AsyncUpdateStatus", status); + auto imagePair = components.front(); + auto versionId = imagePair.first; + auto image = imagePair.second; + auto currImage = "/xyz/openbmc_project/software/" + image; + try + { + conn->async_method_call( + [](const boost::system::error_code& /*ec*/) {}, + "xyz.openbmc_project.Software.BMC.Updater", currImage, + "org.freedesktop.DBus.Properties", "Set", + "xyz.openbmc_project.Software.Activation", "RequestedActivation", + DbusVariant( + "xyz.openbmc_project.Software.Activation.RequestedActivations.Active")); + } + catch (const sdbusplus::exception::SdBusError& e) + { + lg2::error("Can not activate image: {ERROR}", "ERROR", e); + return; + } +} + +/** + * @brief stop update. + */ +static void interruptImage(std::string& image) +{ + auto currImage = "/xyz/openbmc_project/software/" + image; + try + { + conn->async_method_call( + [](const boost::system::error_code& /*ec*/) {}, + "xyz.openbmc_project.Software.BMC.Updater", currImage, + "org.freedesktop.DBus.Properties", "Set", + "xyz.openbmc_project.Software.Activation", "Activation", + DbusVariant( + "xyz.openbmc_project.Software.Activation.Activations.Interrupted")); + } + catch (const sdbusplus::exception::SdBusError& e) + { + lg2::error("Can not interrupt image: {IMAGE} error: {ERROR}", "IMAGE", + image, "ERROR", e); + return; + } +} + +/** + * @brief dbus method callback. + */ +static std::string handlerStart() +{ + // when queue is empty return idle status + if (components.empty()) + { + if (status == "Running") + { + status = "Idle"; + } + interface->set_property("AsyncUpdateStatus", status); + return status; + } + // queue is not empty start async update ,use timer to check power status + checkTimer.expires_after(boost::asio::chrono::seconds(5)); + checkTimer.async_wait([&](const boost::system::error_code& ec) { + if (ec) + { + return; + } + if (!isPowerOff()) + { + if (status == "Running") + { + lg2::error("power is on, stop async update for queued images."); + for (auto& item : components) + { + lg2::error( + "stop queued image async update, imageID: {IMAGE}, versionID: {VERSION}", + "IMAGE", item.second, "VERSION", item.first); + interruptImage(item.second); + } + components.clear(); + + status = "Idle"; + interface->set_property("AsyncUpdateStatus", status); + return; + } + // power is on recheck power status + handlerStart(); + return; + } + // power is off start async update + activateImage(); + }); + return status; +} + +/** + * @brief dbus match callback. + */ +void matchJobs(sdbusplus::message::message& msg) +{ + uint32_t newStateID{}; + sdbusplus::message::object_path newStateObjPath; + std::string newStateUnit{}; + std::string newStateResult{}; + // Read the msg and populate each variable + msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); + // normal update will be ignore + if (status == "Running") + { + // if newStateUnit exist image ID, means it is a service job + if (newStateUnit.find(components.front().second) != std::string::npos) + { + if (newStateResult == "done") + { + lg2::info("async update image finish successfully ({SERVICE})", + "SERVICE", newStateUnit); + } + else if (newStateResult == "failed") + { + lg2::error("async update image failed ({SERVICE})", "SERVICE", + newStateUnit); + } + // when finish, remove image from queue + components.pop_front(); + // start next + handlerStart(); + } + } +} + +int main(int /*argc*/, char** /*argv*/) +{ + const auto BUS_NAME = "xyz.openbmc_project.Software.AsyncUpdate"; + const auto OBJECT_PATH = "/xyz/openbmc_project/Software/AsyncUpdate"; + const auto INTERFACE_NAME = "xyz.openbmc_project.Software.AsyncUpdate"; + conn = std::make_shared(ioCtx); + conn->request_name(BUS_NAME); + + setupPowerMonitor(conn); + match = std::make_shared( + *conn, + "type='signal',member='JobRemoved',path='/org/freedesktop/systemd1',interface='org.freedesktop.systemd1.Manager'", + matchJobs); + sdbusplus::asio::object_server hostServer = + sdbusplus::asio::object_server(conn); + interface = hostServer.add_interface(OBJECT_PATH, INTERFACE_NAME); + interface->register_property( + "AsyncUpdateStatus", status, + [&](const std::string& requested, std::string& resp) -> int { + status = requested; + resp = requested; + return 1; + }); + interface->register_method("Start", handlerStart); + interface->register_method("AddQueue", handlerAddQueue); + interface->initialize(); + ioCtx.run(); + return 0; +} diff --git a/item_updater.cpp b/item_updater.cpp index 36bf5b3..a09f704 100644 --- a/item_updater.cpp +++ b/item_updater.cpp @@ -179,10 +179,15 @@ void ItemUpdater::createActivation(sdbusplus::message_t& msg) *versionPtr); versions.insert(std::make_pair(versionId, std::move(versionPtr))); - activations.insert(std::make_pair( - versionId, - std::make_unique(bus, path, *this, versionId, - activationState, associations))); + if (version == "Mismatch" || version == "Invalid") + { + activationState = server::Activation::Activations::Invalid; + } + auto activationPtr = std::make_unique( + bus, path, *this, versionId, activationState, associations); + activationPtr->updateTarget = std::make_unique(bus, path); + activationPtr->applyOptions = std::make_unique(bus, path); + activations.insert(std::make_pair(versionId, std::move(activationPtr))); } return; } @@ -768,12 +773,21 @@ void ItemUpdater::resetUbootEnvVars() void ItemUpdater::freeSpace([[maybe_unused]] const Activation& caller) { #ifdef BMC_STATIC_DUAL_IMAGE - // For the golden image case, always remove the version on the primary side + // For dual image case, erase the slot depends on the updateTarget + auto targetSlot = UpdateTarget::TargetSlot::Primary; + if (caller.updateTarget) + { + targetSlot = caller.updateTarget->updateTargetSlot(); + } + auto priorityToErase = targetSlot == UpdateTarget::TargetSlot::Primary ? 0 + : 1; + std::string versionIDtoErase; for (const auto& iter : activations) { if (iter.second->redundancyPriority && - iter.second->redundancyPriority.get()->priority() == 0) + iter.second->redundancyPriority.get()->priority() == + priorityToErase) { versionIDtoErase = iter.second->versionId; break; diff --git a/meson.build b/meson.build index 68f1f91..99d3645 100644 --- a/meson.build +++ b/meson.build @@ -247,6 +247,15 @@ if get_option('sync-bmc-files').enabled() ) endif +if get_option('async-update').allowed() + executable( + 'obmc-async-update', + 'image_async_main.cpp', + dependencies: deps, + install: true + ) + unit_files += 'obmc-async-update.service.in' +endif if (get_option('verify-signature').enabled() or \ get_option('verify-full-signature').enabled()) image_updater_sources += files( diff --git a/meson_options.txt b/meson_options.txt index 2109025..53c61fd 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -40,6 +40,11 @@ option( description: 'Automatic flash side switch on boot', ) +option( + 'async-update', type: 'feature', value: 'enabled', + description: 'Enable Async update.', +) + # Variables option( 'active-bmc-max-allowed', type: 'integer', diff --git a/obmc-async-update.service.in b/obmc-async-update.service.in new file mode 100755 index 0000000..d586a55 --- /dev/null +++ b/obmc-async-update.service.in @@ -0,0 +1,11 @@ +[Unit] +Description=BMC async update + +[Service] +Restart=always +Type=dbus +BusName=xyz.openbmc_project.Software.AsyncUpdate +ExecStart=/usr/bin/obmc-async-update + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/static/flash.cpp b/static/flash.cpp index 74316d1..812b5de 100644 --- a/static/flash.cpp +++ b/static/flash.cpp @@ -31,20 +31,20 @@ using namespace phosphor::software::image; void Activation::flashWrite() { -#ifdef BMC_STATIC_DUAL_IMAGE - if (parent.runningImageSlot != 0) - { - // It's running on the secondary chip, update the primary one - info("Flashing primary flash from secondary, id: {ID}", "ID", - versionId); - auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, - SYSTEMD_INTERFACE, "StartUnit"); - auto serviceFile = FLASH_ALT_SERVICE_TMPL + versionId + ".service"; - method.append(serviceFile, "replace"); - bus.call_noreply(method); - return; - } -#endif +// #ifdef BMC_STATIC_DUAL_IMAGE +// if (parent.runningImageSlot != 0) +// { +// // It's running on the secondary chip, update the primary one +// info("Flashing primary flash from secondary, id: {ID}", "ID", +// versionId); +// auto method = bus.new_method_call(SYSTEMD_BUSNAME, SYSTEMD_PATH, +// SYSTEMD_INTERFACE, "StartUnit"); +// auto serviceFile = FLASH_ALT_SERVICE_TMPL + versionId + ".service"; +// method.append(serviceFile, "replace"); +// bus.call_noreply(method); +// return; +// } +// #endif // For static layout code update, just put images in /run/initramfs. // It expects user to trigger a reboot and an updater script will program // the image to flash during reboot. @@ -57,6 +57,55 @@ void Activation::flashWrite() fs::copy_file(uploadDir / versionId / bmcImage, toPath / bmcImage, fs::copy_options::overwrite_existing, ec); } + + std::string old_prefix = "image-"; + std::string new_prefix = "image-alt-"; + auto targetSlot = UpdateTarget::TargetSlot::Primary; + if (updateTarget) + { + targetSlot = updateTarget->updateTargetSlot(); + } + + if (targetSlot == UpdateTarget::TargetSlot::Both) + { + for (const auto& entry : fs::directory_iterator(toPath)) + { + if (entry.is_regular_file()) + { + std::string old_name = entry.path().filename().string(); + if (old_name.find(old_prefix) == 0 && old_name.find(new_prefix) == std::string::npos) + { + std::string new_name = new_prefix + old_name.substr(old_prefix.length()); + fs::copy_file(entry.path(), entry.path().parent_path() / new_name); + } + } + } + } + else if (targetSlot == UpdateTarget::TargetSlot::Secondary) + { + for (const auto& entry : fs::directory_iterator(toPath)) + { + if (entry.is_regular_file()) + { + std::string old_name = entry.path().filename().string(); + if (old_name.find(old_prefix) == 0 && old_name.find(new_prefix) == std::string::npos) + { + std::string new_name = new_prefix + old_name.substr(old_prefix.length()); + fs::rename(entry.path(), entry.path().parent_path() / new_name); + } + } + } + } + + auto clearConfig = ApplyOptions::ConfigManagement::Keep; + if (applyOptions) + { + clearConfig = applyOptions->clearConfig(); + } + if (clearConfig == ApplyOptions::ConfigManagement::Clear) + { + utils::execute("/sbin/fw_setenv", "openbmconce", "factory-reset"); + } } void Activation::onStateChanges([[maybe_unused]] sdbusplus::message_t& msg) -- 2.25.1