fw update: common code
new daemons to implement the flow as described in
https://github.com/openbmc/docs/blob/master/designs/code-update.md
- common/
common code folder
- common update flow
- base class for the device specific update daemons
The new daemons are all following the generic template of Code Updater
daemon as outlined in the design.
The idea is that they are separate daemons (per device, as outlined in
the design) but share all the code that's not device specific.
Tested: next patch in series
Change-Id: If2438b8506aceb8c5313ec13a0bf7cb68f3cc279
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/common/include/device.hpp b/common/include/device.hpp
new file mode 100644
index 0000000..2bfcac7
--- /dev/null
+++ b/common/include/device.hpp
@@ -0,0 +1,102 @@
+#pragma once
+
+#include "common/pldm/package_parser.hpp"
+#include "device_config.hpp"
+#include "software.hpp"
+
+#include <sdbusplus/async/context.hpp>
+#include <xyz/openbmc_project/Association/Definitions/aserver.hpp>
+#include <xyz/openbmc_project/Software/Activation/aserver.hpp>
+#include <xyz/openbmc_project/Software/Update/aserver.hpp>
+#include <xyz/openbmc_project/Software/Version/aserver.hpp>
+
+#include <string>
+
+using ActivationInterface =
+ sdbusplus::common::xyz::openbmc_project::software::Activation;
+
+class SoftwareManager;
+
+class Device
+{
+ public:
+ Device(sdbusplus::async::context& ctx, bool dryRun,
+ const DeviceConfig& deviceConfig, SoftwareManager* parent);
+
+ virtual ~Device() = default;
+ Device(const Device&) = delete;
+ Device& operator=(const Device&) = delete;
+ Device(Device&&) = delete;
+ Device& operator=(Device&&) = delete;
+
+ // we provide the pointer to the dbus interface so the device specific
+ // implementation can give percentage progress updates
+ // @param image raw fw image without pldm header
+ // @param image_size size of 'image'
+ // @param activationProgress dbus interface for progress reporting
+ // @returns true if update was applied successfully.
+ // Should also return true if update was applied
+ // successfully, but the host failed to power
+ // on.
+ virtual sdbusplus::async::task<bool> updateDevice(
+ const uint8_t* image, size_t image_size,
+ std::unique_ptr<SoftwareActivationProgress>& activationProgress) = 0;
+
+ // @returns the object path of the inventory item which
+ // can be associated with this device.
+ virtual sdbusplus::async::task<std::string>
+ getInventoryItemObjectPath() = 0;
+
+ // Returns the apply times for updates which are supported by the device
+ // Override this if your device deviates from the default set of apply
+ // times.
+ virtual std::set<RequestedApplyTimes> allowedApplyTimes();
+
+ // @param image the memory fd with the pldm package
+ // @param applyTime when the update should be applied
+ // @param swid the software id to use
+ // @returns true if update was successfull
+ sdbusplus::async::task<bool> startUpdateAsync(
+ sdbusplus::message::unix_fd image, RequestedApplyTimes applyTime,
+ std::unique_ptr<Software> softwareUpdate);
+
+ // Value of 'Type' field for the configuration in EM exposes record
+ std::string getEMConfigType() const;
+
+ // software instance, identified by its swid
+ // The specific derived class also owns its dbus interfaces,
+ // which are destroyed when the instance is deleted.
+ std::unique_ptr<Software> softwareCurrent;
+
+ protected:
+ // Resets the device, in whichever way is appropriate for the device.
+ // The reset must be capable to apply the firmware update which was done
+ // by 'deviceSpecificUpdateFunction', in case that function did not already
+ // apply it. This method is optional to implement for that reason.
+ virtual void resetDevice();
+
+ // The common configuration that all devices share.
+ // We get this from EM configuration.
+ DeviceConfig config;
+
+ SoftwareManager* parent;
+ bool dryRun;
+
+ sdbusplus::async::context& ctx;
+
+ private:
+ // @param pldm_pkg raw pldm package
+ // @param pldm_pkg_size size of 'pldm_pkg'
+ // @param applyTime when the update should be applied
+ // @returns the return value of the device specific
+ // update function
+ sdbusplus::async::task<bool> continueUpdateWithMappedPackage(
+ void* pldm_pkg,
+ const std::shared_ptr<pldm::fw_update::PackageParser>& packageParser,
+ sdbusplus::common::xyz::openbmc_project::software::ApplyTime::
+ RequestedApplyTimes applyTime,
+ const std::unique_ptr<Software>& softwareUpdate);
+
+ friend SoftwareUpdate;
+ friend Software;
+};
diff --git a/common/include/device_config.hpp b/common/include/device_config.hpp
new file mode 100644
index 0000000..365e5bb
--- /dev/null
+++ b/common/include/device_config.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+
+/* This class represents the device software configuration we get from
+ * entity-manager via dbus. Each Code Updater can create its own configuration
+ * class that inherits from this to store additional properties from their
+ * device configuration, like bus/address/...
+ */
+class DeviceConfig
+{
+ public:
+ DeviceConfig(uint32_t vendorIANA, const std::string& compatible,
+ const std::string& configType, const std::string& name);
+
+ // https://gerrit.openbmc.org/c/openbmc/docs/+/74653
+ // properties from the configuration
+ const uint32_t vendorIANA; // "0x0000A015", 4 bytes as per PLDM spec
+
+ const std::string
+ compatibleHardware; // e.g.
+ // "com.meta.Hardware.Yosemite4.MedusaBoard.CPLD.LCMX02_2000HC"
+
+ // 'Name' field from the EM config
+ const std::string configName;
+
+ // 'Type' field from the EM config
+ const std::string configType;
+};
diff --git a/common/include/software.hpp b/common/include/software.hpp
new file mode 100644
index 0000000..6b96f09
--- /dev/null
+++ b/common/include/software.hpp
@@ -0,0 +1,124 @@
+#pragma once
+
+#include "software_update.hpp"
+
+#include <sdbusplus/async/context.hpp>
+#include <xyz/openbmc_project/Association/Definitions/aserver.hpp>
+#include <xyz/openbmc_project/Software/Activation/aserver.hpp>
+#include <xyz/openbmc_project/Software/ActivationBlocksTransition/aserver.hpp>
+#include <xyz/openbmc_project/Software/ActivationProgress/aserver.hpp>
+#include <xyz/openbmc_project/Software/Version/aserver.hpp>
+
+#include <string>
+
+// Need to declare this class to initialize the protected members of our base
+// class. This prevents "Conditional jump or move depends on uninitialised
+// value(s)" when properties are updated for the first time.
+class SoftwareActivationProgress :
+ public sdbusplus::aserver::xyz::openbmc_project::software::
+ ActivationProgress<Software>
+{
+ public:
+ SoftwareActivationProgress(sdbusplus::async::context& ctx,
+ const char* objPath);
+};
+
+using SoftwareActivationBlocksTransition = sdbusplus::aserver::xyz::
+ openbmc_project::software::ActivationBlocksTransition<Software>;
+
+using SoftwareVersion =
+ sdbusplus::aserver::xyz::openbmc_project::software::Version<Software>;
+using SoftwareActivation =
+ sdbusplus::aserver::xyz::openbmc_project::software::Activation<Software>;
+using SoftwareAssociationDefinitions =
+ sdbusplus::aserver::xyz::openbmc_project::association::Definitions<
+ Software>;
+
+class Device;
+
+// This represents a software version running on the device.
+class Software : private SoftwareActivation
+{
+ public:
+ Software(sdbusplus::async::context& ctx, const std::string& swid,
+ Device& parent);
+
+ // @returns object path of this software
+ sdbusplus::message::object_path getObjectPath() const;
+
+ // The software id
+ const std::string swid;
+
+ // Set the activation status of this software
+ // @param activation The activation status
+ void setActivation(sdbusplus::common::xyz::openbmc_project::software::
+ Activation::Activations activation);
+
+ // Add / remove the 'ActivationBlocksTransition' dbus interface.
+ // This dbus interface is only needed during the update process.
+ // @param enabled determines if the dbus interface should be there
+ void setActivationBlocksTransition(bool enabled);
+
+ // This is only required during the activation of the new fw
+ // and is deleted again afterwards.
+ // This member is public since the device specific update function
+ // needs to update the progress.
+ std::unique_ptr<SoftwareActivationProgress> optSoftwareActivationProgress =
+ nullptr;
+
+ // This should populate 'optSoftwareUpdate'
+ // @param allowedApplyTimes When updates to this Version can be
+ // applied
+ void enableUpdate(const std::set<RequestedApplyTimes>& allowedApplyTimes);
+
+ // This should populate 'optSoftwareVersion'
+ // @param version the version string
+ void setVersion(const std::string& versionStr);
+
+ // This should populate 'optSoftwareAssociationDefinitions'
+ // @param isRunning if the software version is currently running
+ // on the device
+ // @param isActivating if the software version is currently
+ // activating (not yet running) on the device
+ sdbusplus::async::task<> setAssociationDefinitionsRunningActivating(
+ bool isRunning, bool isActivating);
+
+ // @returns this->parent
+ Device& getParentDevice();
+
+ protected:
+ // @returns a random software id (swid) for that device
+ static std::string getRandomSoftwareId(Device& parent);
+
+ private:
+ // @returns the object path for the a software with that swid
+ static std::string getObjPathFromSwid(const std::string& swid);
+
+ // The device we are associated to, meaning this software is either running
+ // on the device, or could potentially run on that device (update).
+ Device& parent;
+
+ // Dbus interface to prevent power state transition during update.
+ std::unique_ptr<SoftwareActivationBlocksTransition>
+ optActivationBlocksTransition = nullptr;
+
+ // The software update dbus interface is not always present.
+ // It is constructed if the software version is able to be updated.
+ // For the new software version, this interface is constructed after the
+ // update has taken effect
+ std::unique_ptr<SoftwareUpdate> optSoftwareUpdate = nullptr;
+
+ // We do not know the software version until we parse the PLDM package.
+ // Since the Activation interface needs to be available
+ // before then, this is nullptr until we get to know the version.
+ std::unique_ptr<SoftwareVersion> optSoftwareVersion = nullptr;
+
+ // This represents our association to the inventory item in case
+ // this software is currently on the device.
+ std::unique_ptr<SoftwareAssociationDefinitions>
+ optSoftwareAssociationDefinitions = nullptr;
+
+ sdbusplus::async::context& ctx;
+
+ friend SoftwareUpdate;
+};
diff --git a/common/include/software_manager.hpp b/common/include/software_manager.hpp
new file mode 100644
index 0000000..fd3a467
--- /dev/null
+++ b/common/include/software_manager.hpp
@@ -0,0 +1,127 @@
+#pragma once
+
+#include "device.hpp"
+#include "sdbusplus/async/proxy.hpp"
+
+#include <boost/asio/steady_timer.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/async/context.hpp>
+#include <sdbusplus/timer.hpp>
+
+#include <string>
+
+// This is the base class for the code updater
+// Every code updater can inherit from this
+class SoftwareManager
+{
+ public:
+ SoftwareManager(sdbusplus::async::context& ctx,
+ const std::string& busNameSuffix, bool dryRun);
+
+ // @param state desired powerstate (true means on)
+ // @returns true on success
+ sdbusplus::async::task<bool> setHostPowerstate(bool state);
+
+ // @returns true when powered
+ // @returns std::nullopt on failure to query power state
+ sdbusplus::async::task<std::optional<bool>> getHostPowerstate();
+
+ // Fetches initial configuration from dbus.
+ // This should be called once by a code updater at startup.
+ // It will call 'getInitialConfigurationSingleDevice' for each device
+ // configured
+ // @param configurationInterfaces the dbus interfaces from which to fetch
+ // configuration
+ sdbusplus::async::task<> getInitialConfiguration(
+ const std::vector<std::string>& configurationInterfaces);
+
+ // request the bus name on dbus after all configuration has been parsed
+ // and the devices have been initialized
+ // @returns the name on dbus
+ std::string setupBusName();
+
+ // set of devices found through configuration and probing
+ std::set<std::unique_ptr<Device>> devices;
+
+ protected:
+ const bool dryRun;
+
+ template <typename T>
+ sdbusplus::async::task<std::optional<T>>
+ dbusGetRequiredConfigurationProperty(
+ const std::string& service, const std::string& path,
+ const std::string& property, DeviceConfig& config)
+ {
+ std::string configIface =
+ "xyz.openbmc_project.Configuration." + config.configType;
+ std::optional<T> res = co_await dbusGetRequiredProperty<T>(
+ service, path, configIface, property);
+ co_return res;
+ }
+
+ // this variant also logs the error
+ template <typename T>
+ sdbusplus::async::task<std::optional<T>> dbusGetRequiredProperty(
+ const std::string& service, const std::string& path,
+ const std::string& intf, const std::string& property)
+ {
+ std::optional<T> opt =
+ co_await dbusGetProperty<T>(service, path, intf, property);
+ if (!opt.has_value())
+ {
+ lg2::error(
+ "[config] missing property {PROPERTY} on path {PATH}, interface {INTF}",
+ "PROPERTY", property, "PATH", path, "INTF", intf);
+ }
+ co_return opt;
+ }
+
+ template <typename T>
+ sdbusplus::async::task<std::optional<T>>
+ dbusGetProperty(const std::string& service, const std::string& path,
+ const std::string& intf, const std::string& property)
+ {
+ auto client =
+ sdbusplus::async::proxy().service(service).path(path).interface(
+ "org.freedesktop.DBus.Properties");
+
+ try
+ {
+ std::variant<T> result = co_await client.call<std::variant<T>>(
+ ctx, "Get", intf, property);
+
+ T res = std::get<T>(result);
+ co_return res;
+ }
+ catch (std::exception& e)
+ {
+ co_return std::nullopt;
+ }
+ }
+
+ // This function receives a dbus name and object path for a single device,
+ // which was configured.
+ // The component code updater overrides this function and may create a
+ // device instance internally, or reject the configuration as invalid.
+ // @param service The dbus name where our configuration is
+ // @param config The common configuration properties which are shared
+ // by all devices.
+ // Also includes the object path to fetch other
+ // configuration properties.
+ virtual sdbusplus::async::task<> getInitialConfigurationSingleDevice(
+ const std::string& service, const std::string& path,
+ DeviceConfig& config) = 0;
+
+ sdbusplus::async::context& ctx;
+
+ private:
+ // this is appended to the common prefix to construct the dbus name
+ std::string busNameSuffix;
+
+ sdbusplus::server::manager_t manager;
+
+ friend Software;
+ friend Device;
+};
diff --git a/common/include/software_update.hpp b/common/include/software_update.hpp
new file mode 100644
index 0000000..9080005
--- /dev/null
+++ b/common/include/software_update.hpp
@@ -0,0 +1,30 @@
+#pragma once
+
+#include <sdbusplus/async/context.hpp>
+#include <xyz/openbmc_project/Software/Update/aserver.hpp>
+
+class Software;
+
+using RequestedApplyTimes = sdbusplus::common::xyz::openbmc_project::software::
+ ApplyTime::RequestedApplyTimes;
+
+class SoftwareUpdate :
+ public sdbusplus::aserver::xyz::openbmc_project::software::Update<
+ SoftwareUpdate>
+{
+ public:
+ SoftwareUpdate(sdbusplus::async::context& ctx, const char* path,
+ Software& software,
+ const std::set<RequestedApplyTimes>& allowedApplyTimes);
+
+ Software& software;
+
+ auto method_call(start_update_t su, auto image, auto applyTime)
+ -> sdbusplus::async::task<start_update_t::return_type>;
+
+ auto set_property(allowed_apply_times_t aat, auto value) -> bool;
+ auto get_property(allowed_apply_times_t aat) const;
+
+ private:
+ const std::set<RequestedApplyTimes> allowedApplyTimes;
+};