oem-ibm: Maintain bootside Mapping and Set bios attribute

This commit adds code to maintain a mapping between running,
non-running and Temp, Perm side which would help the bmc
and remote PLDM terminus determine at any point in time if the
code update was successful and that remote PLDM terminus knows
exactly which side it is running on.

In the event the code update failed, then for that case
the new added bios attribute "fw_boot_side", which will
be set in the event. If BMC tries to boot from the new
"Temp" side (other side), and if it fails, then BMC must
update fw_boot_side to "Perm".

Change-Id: I9f1edad1e36850742aba88d93f8cf0fc8b9d8c8d
Signed-off-by: Archana Kakani <archana.kakani@ibm.com>
diff --git a/common/utils.cpp b/common/utils.cpp
index 06ae48b..7406e1f 100644
--- a/common/utils.cpp
+++ b/common/utils.cpp
@@ -4,7 +4,6 @@
 #include <libpldm/pldm_types.h>
 #include <linux/mctp.h>
 
-#include <phosphor-logging/lg2.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>
 #include <xyz/openbmc_project/Logging/Create/client.hpp>
 #include <xyz/openbmc_project/ObjectMapper/client.hpp>
@@ -910,5 +909,49 @@
     return effecterIDs;
 }
 
+void setBiosAttr(const PendingAttributesList& biosAttrList)
+{
+    static constexpr auto SYSTEMD_PROPERTY_INTERFACE =
+        "org.freedesktop.DBus.Properties";
+    constexpr auto biosConfigPath = "/xyz/openbmc_project/bios_config/manager";
+    constexpr auto biosConfigIntf = "xyz.openbmc_project.BIOSConfig.Manager";
+
+    for (const auto& [attrName, biosAttrDetails] : biosAttrList)
+    {
+        auto& bus = DBusHandler::getBus();
+        try
+        {
+            auto service = pldm::utils::DBusHandler().getService(
+                biosConfigPath, biosConfigIntf);
+            auto method =
+                bus.new_method_call(service.c_str(), biosConfigPath,
+                                    SYSTEMD_PROPERTY_INTERFACE, "Set");
+            method.append(biosConfigIntf, "PendingAttributes",
+                          std::variant<PendingAttributesList>(biosAttrList));
+            bus.call_noreply(method, dbusTimeout);
+        }
+        catch (const sdbusplus::exception::SdBusError& e)
+        {
+            AttributeType attrType;
+            AttributeValue attrValue;
+            std::tie(attrType, attrValue) = biosAttrDetails;
+            if (attrType ==
+                "xyz.openbmc_project.BIOSConfig.Manager.AttributeType.Integer")
+            {
+                info(
+                    "Error setting the value {VALUE} to bios attribute {BIOS_ATTR}: {ERR_EXCEP}",
+                    "VALUE", std::get<int64_t>(attrValue), "BIOS_ATTR",
+                    attrName, "ERR_EXCEP", e);
+            }
+            else
+            {
+                info(
+                    "Error setting the value {VALUE} to bios attribute {BIOS_ATTR}: {ERR_EXCEP}",
+                    "VALUE", std::get<std::string>(attrValue), "BIOS_ATTR",
+                    attrName, "ERR_EXCEP", e);
+            }
+        }
+    }
+}
 } // namespace utils
 } // namespace pldm
diff --git a/common/utils.hpp b/common/utils.hpp
index 23db119..c031af1 100644
--- a/common/utils.hpp
+++ b/common/utils.hpp
@@ -12,7 +12,9 @@
 #include <unistd.h>
 
 #include <nlohmann/json.hpp>
+#include <phosphor-logging/lg2.hpp>
 #include <sdbusplus/server.hpp>
+#include <xyz/openbmc_project/BIOSConfig/Manager/common.hpp>
 #include <xyz/openbmc_project/Inventory/Manager/client.hpp>
 #include <xyz/openbmc_project/Logging/Entry/server.hpp>
 #include <xyz/openbmc_project/ObjectMapper/client.hpp>
@@ -32,6 +34,8 @@
         std::chrono::seconds(DBUS_TIMEOUT))
         .count();
 
+PHOSPHOR_LOG2_USING;
+
 namespace pldm
 {
 namespace utils
@@ -202,12 +206,20 @@
 using PropertyMap = std::map<std::string, PropertyValue>;
 using InterfaceMap = std::map<std::string, PropertyMap>;
 using ObjectValueTree = std::map<sdbusplus::message::object_path, InterfaceMap>;
+using AttributeName = std::string;
+using AttributeType = std::string;
+using AttributeValue = std::variant<std::string, int64_t>;
+using PendingAttributesList = std::vector<
+    std::pair<AttributeName, std::tuple<AttributeType, AttributeValue>>>;
 
 using SensorPDR = std::vector<uint8_t>;
 using SensorPDRs = std::vector<SensorPDR>;
 using EffecterPDR = std::vector<uint8_t>;
 using EffecterPDRs = std::vector<EffecterPDR>;
 
+constexpr auto EnumAttribute =
+    "xyz.openbmc_project.BIOSConfig.Manager.AttributeType.Enumeration";
+
 /**
  * @brief The interface for DBusHandler
  */
@@ -687,5 +699,53 @@
 std::optional<uint32_t> fruFieldParserU32(const uint8_t* value,
                                           const uint8_t& length);
 
+/** @brief Method to get the value from a bios attribute
+ *
+ *  @param[in] dbusAttrName - the bios attribute name from
+ *             which the value must be retrieved
+ *
+ *  @return the attribute value
+ */
+template <typename T>
+std::optional<T> getBiosAttrValue(const std::string& dbusAttrName)
+{
+    constexpr auto biosConfigPath = "/xyz/openbmc_project/bios_config/manager";
+    constexpr auto biosConfigIntf = sdbusplus::common::xyz::openbmc_project::
+        bios_config::Manager::interface;
+
+    std::string var1;
+    std::variant<std::string, int64_t> var2, var3;
+    auto& bus = DBusHandler::getBus();
+    try
+    {
+        auto service = pldm::utils::DBusHandler().getService(biosConfigPath,
+                                                             biosConfigIntf);
+        auto method = bus.new_method_call(service.c_str(), biosConfigPath,
+                                          biosConfigIntf, "GetAttribute");
+        method.append(dbusAttrName);
+        auto reply = bus.call(method, dbusTimeout);
+        reply.read(var1, var2, var3);
+        if (auto ptr = std::get_if<T>(&var2))
+        {
+            return *ptr;
+        }
+    }
+    catch (const sdbusplus::exception::SdBusError& e)
+    {
+        info("Error getting the bios attribute {BIOS_ATTR}: {ERR_EXCEP}",
+             "BIOS_ATTR", dbusAttrName, "ERR_EXCEP", e);
+    }
+
+    return std::nullopt;
+}
+
+/** @brief Method to set the specified bios attribute with
+ *         specified value
+ *
+ *  @param[in] PendingAttributesList - the list of bios attribute and values
+ *             to be set
+ */
+void setBiosAttr(const PendingAttributesList& biosAttrList);
+
 } // namespace utils
 } // namespace pldm
diff --git a/oem/ibm/libpldmresponder/inband_code_update.cpp b/oem/ibm/libpldmresponder/inband_code_update.cpp
index 8acfe56..855258d 100644
--- a/oem/ibm/libpldmresponder/inband_code_update.cpp
+++ b/oem/ibm/libpldmresponder/inband_code_update.cpp
@@ -7,6 +7,7 @@
 #include <arpa/inet.h>
 #include <libpldm/entity.h>
 
+#include <nlohmann/json.hpp>
 #include <phosphor-logging/lg2.hpp>
 #include <sdbusplus/server.hpp>
 #include <xyz/openbmc_project/Dump/NewDump/server.hpp>
@@ -39,6 +40,9 @@
 /** @brief The file name of the hostfw image */
 constexpr auto hostfwImageName = "image-hostfw";
 
+/** @brief The filename of the file where bootside data will be saved */
+constexpr auto bootSideFileName = "bootSide";
+
 /** @brief The path to the code update tarball file */
 auto tarImagePath = fs::path(imageDirPath) / tarImageName;
 
@@ -49,6 +53,15 @@
  *         manager */
 auto updateImagePath = fs::path("/tmp/images") / tarImageName;
 
+/** @brief Current boot side */
+constexpr auto bootSideAttrName = "fw_boot_side_current";
+
+/** @brief Next boot side */
+constexpr auto bootNextSideAttrName = "fw_boot_side";
+
+/** @brief The filepath of file where bootside data will be saved */
+auto bootSideDirPath = fs::path("/var/lib/pldm/") / bootSideFileName;
+
 std::string CodeUpdate::fetchCurrentBootSide()
 {
     return currBootSide;
@@ -67,14 +80,23 @@
 
 int CodeUpdate::setNextBootSide(const std::string& nextSide)
 {
+    info("setNextBootSide, nextSide={NXT_SIDE}", "NXT_SIDE", nextSide);
+    pldm_boot_side_data pldmBootSideData = readBootSideFile();
+    currBootSide =
+        (pldmBootSideData.current_boot_side == "Perm" ? Pside : Tside);
     nextBootSide = nextSide;
+    pldmBootSideData.next_boot_side = (nextSide == Pside ? "Perm" : "Temp");
     std::string objPath{};
     if (nextBootSide == currBootSide)
     {
+        info(
+            "Current bootside is same as next boot side, setting priority of running version 0");
         objPath = runningVersion;
     }
     else
     {
+        info(
+            "Current bootside is not same as next boot side, setting priority of non running version 0");
         objPath = nonRunningVersion;
     }
     if (objPath.empty())
@@ -83,6 +105,26 @@
         return PLDM_PLATFORM_INVALID_STATE_VALUE;
     }
 
+    try
+    {
+        auto priorityPropValue = dBusIntf->getDbusPropertyVariant(
+            objPath.c_str(), "Priority", redundancyIntf);
+        const auto& priorityValue = std::get<uint8_t>(priorityPropValue);
+        if (priorityValue == 0)
+        {
+            // Requested next boot side is already set
+            return PLDM_SUCCESS;
+        }
+    }
+    catch (const std::exception& e)
+    {
+        // Alternate side may not be present due to a failed code update
+        error("Alternate side may not be present due to a failed code update. "
+              "ERROR: {ERR}",
+              "ERR", e);
+        return PLDM_PLATFORM_INVALID_STATE_VALUE;
+    }
+
     pldm::utils::DBusMapping dbusMapping{objPath, redundancyIntf, "Priority",
                                          "uint8_t"};
     uint8_t val = 0;
@@ -97,6 +139,7 @@
               "PATH", objPath, "ERROR", e);
         return PLDM_ERROR;
     }
+    writeBootSideFile(pldmBootSideData);
     return PLDM_SUCCESS;
 }
 
@@ -152,6 +195,7 @@
 
 void CodeUpdate::setVersions()
 {
+    PendingAttributesList biosAttrList;
     static constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
     static constexpr auto functionalObjPath =
         "/xyz/openbmc_project/software/functional";
@@ -186,6 +230,94 @@
                 break;
             }
         }
+        if (!fs::exists(bootSideDirPath))
+        {
+            pldm_boot_side_data pldmBootSideData;
+            std::string nextBootSideBiosValue = "Temp";
+            auto attributeValue = getBiosAttrValue<std::string>("fw_boot_side");
+
+            // We enter this path during Genesis boot/boot after Factory reset.
+            // PLDM waits for Entity manager to populate System Type. After
+            // receiving system Type from EM it populates the bios attributes
+            // specific to that system We do not have bios attributes populated
+            // when we reach here so setting it to default value of the
+            // attribute as mentioned in the json files.
+            if (attributeValue.has_value())
+            {
+                nextBootSideBiosValue = attributeValue.value();
+            }
+            else
+            {
+                info(
+                    "Boot side is not initialized yet, so setting default value");
+                nextBootSideBiosValue = "Temp";
+            }
+            pldmBootSideData.current_boot_side = nextBootSideBiosValue;
+            pldmBootSideData.next_boot_side = nextBootSideBiosValue;
+            pldmBootSideData.running_version_object = runningVersion;
+
+            writeBootSideFile(pldmBootSideData);
+            biosAttrList.emplace_back(std::make_pair(
+                bootSideAttrName,
+                std::make_tuple(EnumAttribute,
+                                pldmBootSideData.current_boot_side)));
+            biosAttrList.push_back(std::make_pair(
+                bootNextSideAttrName,
+                std::make_tuple(EnumAttribute,
+                                pldmBootSideData.next_boot_side)));
+            setBiosAttr(biosAttrList);
+        }
+        else
+        {
+            pldm_boot_side_data pldmBootSideData = readBootSideFile();
+            if (pldmBootSideData.running_version_object != runningVersion)
+            {
+                info(
+                    "BMC have booted with the new image runningPath={RUNN_PATH}",
+                    "RUNN_PATH", runningVersion.c_str());
+                info("Previous Image was: {RUNN_VERS}", "RUNN_VERS",
+                     pldmBootSideData.running_version_object);
+                auto current_boot_side =
+                    (pldmBootSideData.current_boot_side == "Temp" ? "Perm"
+                                                                  : "Temp");
+                pldmBootSideData.current_boot_side = current_boot_side;
+                pldmBootSideData.next_boot_side = current_boot_side;
+                pldmBootSideData.running_version_object = runningVersion;
+                writeBootSideFile(pldmBootSideData);
+                biosAttrList.emplace_back(std::make_pair(
+                    bootSideAttrName,
+                    std::make_tuple(EnumAttribute,
+                                    pldmBootSideData.current_boot_side)));
+                biosAttrList.push_back(std::make_pair(
+                    bootNextSideAttrName,
+                    std::make_tuple(EnumAttribute,
+                                    pldmBootSideData.next_boot_side)));
+                setBiosAttr(biosAttrList);
+            }
+            else
+            {
+                info(
+                    "BMC have booted with the previous image runningPath={RUNN_PATH}",
+                    "RUNN_PATH", pldmBootSideData.running_version_object);
+                pldm_boot_side_data pldmBootSideData = readBootSideFile();
+                pldmBootSideData.next_boot_side =
+                    pldmBootSideData.current_boot_side;
+                writeBootSideFile(pldmBootSideData);
+                biosAttrList.emplace_back(std::make_pair(
+                    bootSideAttrName,
+                    std::make_tuple(EnumAttribute,
+                                    pldmBootSideData.current_boot_side)));
+                biosAttrList.push_back(std::make_pair(
+                    bootNextSideAttrName,
+                    std::make_tuple(EnumAttribute,
+                                    pldmBootSideData.next_boot_side)));
+                setBiosAttr(biosAttrList);
+            }
+            currBootSide =
+                (pldmBootSideData.current_boot_side == "Temp" ? Tside : Pside);
+            nextBootSide =
+                (pldmBootSideData.next_boot_side == "Temp" ? Tside : Pside);
+        }
     }
     catch (const std::exception& e)
     {
@@ -328,11 +460,92 @@
 
 void CodeUpdate::processRenameEvent()
 {
+    info("Processing Rename Event");
+
+    PendingAttributesList biosAttrList;
+    pldm_boot_side_data pldmBootSideData = readBootSideFile();
+    pldmBootSideData.current_boot_side = "Perm";
+    pldmBootSideData.next_boot_side = "Perm";
+
     currBootSide = Pside;
+    nextBootSide = Pside;
+
     auto sensorId = getBootSideRenameStateSensor();
+    info("Received sendor id for rename {ID}", "ID", sensorId);
     sendStateSensorEvent(sensorId, PLDM_STATE_SENSOR_STATE, 0,
                          PLDM_OEM_IBM_BOOT_SIDE_RENAME_STATE_RENAMED,
                          PLDM_OEM_IBM_BOOT_SIDE_RENAME_STATE_NOT_RENAMED);
+    writeBootSideFile(pldmBootSideData);
+    biosAttrList.emplace_back(std::make_pair(
+        bootSideAttrName,
+        std::make_tuple(EnumAttribute, pldmBootSideData.current_boot_side)));
+    biosAttrList.push_back(std::make_pair(
+        bootNextSideAttrName,
+        std::make_tuple(EnumAttribute, pldmBootSideData.next_boot_side)));
+    setBiosAttr(biosAttrList);
+}
+
+void CodeUpdate::writeBootSideFile(const pldm_boot_side_data& pldmBootSideData)
+{
+    try
+    {
+        fs::create_directories(bootSideDirPath.parent_path());
+        std::ofstream writeFile(bootSideDirPath, std::ios::out);
+        if (!writeFile.is_open())
+        {
+            error("Failed to open bootside file {FILE} for writing", "FILE",
+                  bootSideDirPath.string());
+            return;
+        }
+
+        nlohmann::json data;
+        data["CurrentBootSide"] = pldmBootSideData.current_boot_side;
+        data["NextBootSide"] = pldmBootSideData.next_boot_side;
+        data["RunningObject"] = pldmBootSideData.running_version_object;
+
+        try
+        {
+            writeFile << data.dump(4);
+        }
+        catch (const nlohmann::json::exception& e)
+        {
+            error("JSON serialization for BootSide failed: {ERROR}", "ERROR",
+                  e);
+            return;
+        }
+
+        writeFile.close();
+    }
+    catch (const std::exception& e)
+    {
+        error("Error {ERROR] while writing bootside file: {FILE}", "ERROR", e,
+              "FILE", bootSideDirPath);
+    }
+}
+
+pldm_boot_side_data CodeUpdate::readBootSideFile()
+{
+    pldm_boot_side_data pldmBootSideDataRead{};
+
+    std::ifstream readFile(bootSideDirPath.string(), std::ios::in);
+
+    if (!readFile)
+    {
+        error("Failed to read Bootside file");
+        return pldmBootSideDataRead;
+    }
+
+    nlohmann::json jsonBootSideData;
+    readFile >> jsonBootSideData;
+
+    pldm_boot_side_data data;
+    data.current_boot_side = jsonBootSideData.value("CurrentBootSide", "");
+    data.next_boot_side = jsonBootSideData.value("NextBootSide", "");
+    data.running_version_object = jsonBootSideData.value("RunningObject", "");
+
+    readFile.close();
+
+    return pldmBootSideDataRead;
 }
 
 void CodeUpdate::processPriorityChangeNotification(
diff --git a/oem/ibm/libpldmresponder/inband_code_update.hpp b/oem/ibm/libpldmresponder/inband_code_update.hpp
index 9f67553..c29fe64 100644
--- a/oem/ibm/libpldmresponder/inband_code_update.hpp
+++ b/oem/ibm/libpldmresponder/inband_code_update.hpp
@@ -19,6 +19,24 @@
 static constexpr auto redundancyIntf =
     "xyz.openbmc_project.Software.RedundancyPriority";
 
+/**
+ * @struct pldm_boot_side_data
+ * @brief Holds PLDM current/next boot side and the boot side which has the
+ * active image.
+ *
+ * This structure stores the current and next boot side identifiers,
+ * as well as the running version information.
+ * The running version value is an alias to the currently active boot side.
+ * It contains either 'a' or 'b', mapping to the two available boot sides
+ * based on which side is active.
+ */
+struct pldm_boot_side_data
+{
+    std::string current_boot_side;
+    std::string next_boot_side;
+    std::string running_version_object;
+};
+
 /** @class CodeUpdate
  *
  *  @brief This class performs the necessary operation in pldm for
@@ -195,6 +213,20 @@
      * rename event notification to host when code update is initiated*/
     void processRenameEvent();
 
+    /* @brief Method to write the bootside information into mapping
+     *        file which would be stored on bmc
+     * @param[in] pldmBootSideData - bootside information such as
+     *        current boot side and running version
+     */
+    void writeBootSideFile(const pldm_boot_side_data& pldmBootSideData);
+
+    /* @brief Method to read the mapping file containing bootside
+     *        which is stored on bmc and stores that information
+     *        in a structure
+     * @return pldm_boot_side_data - the structure which holds information
+     * regarding bootside information */
+    pldm_boot_side_data readBootSideFile();
+
     virtual ~CodeUpdate() {}
 
   private:
diff --git a/oem/ibm/libpldmresponder/oem_ibm_handler.cpp b/oem/ibm/libpldmresponder/oem_ibm_handler.cpp
index cb2862d..15ab665 100644
--- a/oem/ibm/libpldmresponder/oem_ibm_handler.cpp
+++ b/oem/ibm/libpldmresponder/oem_ibm_handler.cpp
@@ -452,7 +452,7 @@
     codeUpdate->setFirmwareUpdateSensor(sensorId);
     sensorId =
         findStateSensorId(repo.getPdr(), 0, PLDM_OEM_IBM_ENTITY_FIRMWARE_UPDATE,
-                          ENTITY_INSTANCE_0, 0, PLDM_OEM_IBM_BOOT_SIDE_RENAME);
+                          ENTITY_INSTANCE_0, 1, PLDM_OEM_IBM_BOOT_SIDE_RENAME);
     codeUpdate->setBootSideRenameStateSensor(sensorId);
 }
 
diff --git a/oem/ibm/libpldmresponder/oem_ibm_handler.hpp b/oem/ibm/libpldmresponder/oem_ibm_handler.hpp
index 907f2cb..895c9f2 100644
--- a/oem/ibm/libpldmresponder/oem_ibm_handler.hpp
+++ b/oem/ibm/libpldmresponder/oem_ibm_handler.hpp
@@ -26,6 +26,24 @@
 using AssociatedEntityMap = std::map<ObjectPath, pldm_entity>;
 namespace oem_ibm_platform
 {
+using AttributeName = std::string;
+using AttributeType = std::string;
+using ReadonlyStatus = bool;
+using DisplayName = std::string;
+using Description = std::string;
+using MenuPath = std::string;
+using CurrentValue = std::variant<int64_t, std::string>;
+using DefaultValue = std::variant<int64_t, std::string>;
+using OptionString = std::string;
+using OptionValue = std::variant<int64_t, std::string>;
+using Option = std::vector<std::tuple<OptionString, OptionValue>>;
+using BIOSTableObj =
+    std::tuple<AttributeType, ReadonlyStatus, DisplayName, Description,
+               MenuPath, CurrentValue, DefaultValue, Option>;
+using BaseBIOSTable = std::map<AttributeName, BIOSTableObj>;
+using PendingObj = std::tuple<AttributeType, CurrentValue>;
+using PendingAttributes = std::map<AttributeName, PendingObj>;
+
 constexpr uint16_t ENTITY_INSTANCE_0 = 0;
 constexpr uint16_t ENTITY_INSTANCE_1 = 1;
 
@@ -147,6 +165,39 @@
                     }
                 }
             });
+        updateBIOSMatch = std::make_unique<sdbusplus::bus::match_t>(
+            pldm::utils::DBusHandler::getBus(),
+            propertiesChanged("/xyz/openbmc_project/bios_config/manager",
+                              "xyz.openbmc_project.BIOSConfig.Manager"),
+            [codeUpdate](sdbusplus::message_t& msg) {
+                constexpr auto propertyName = "PendingAttributes";
+                using Value =
+                    std::variant<std::string, PendingAttributes, BaseBIOSTable>;
+                using Properties = std::map<pldm::utils::DbusProp, Value>;
+                Properties props{};
+                std::string intf;
+                msg.read(intf, props);
+                auto valPropMap = props.find(propertyName);
+                if (valPropMap == props.end())
+                {
+                    return;
+                }
+
+                PendingAttributes pendingAttributes =
+                    std::get<PendingAttributes>(valPropMap->second);
+                for (auto it : pendingAttributes)
+                {
+                    if (it.first == "fw_boot_side")
+                    {
+                        auto& [attributeType, attributevalue] = it.second;
+                        std::string nextBootSideAttr =
+                            std::get<std::string>(attributevalue);
+                        std::string nextBootSide =
+                            (nextBootSideAttr == "Perm" ? Pside : Tside);
+                        codeUpdate->setNextBootSide(nextBootSide);
+                    }
+                }
+            });
     }
 
     int getOemStateSensorReadingsHandler(
@@ -360,6 +411,9 @@
     pldm::requester::Handler<pldm::requester::Request>* handler;
 
     /** @brief D-Bus property changed signal match */
+    std::unique_ptr<sdbusplus::bus::match_t> updateBIOSMatch;
+
+    /** @brief D-Bus property changed signal match */
     std::unique_ptr<sdbusplus::bus::match_t> hostOffMatch;
 
     /** @brief D-Bus property changed signal match */