usb: Update the RequestedActivation property

Subscribe to the add software interface, when an update is detected,
call back the updateActivation method and verify whether it needs
to be updated. If necessary:
1. Set ApplyTime to OnReset to prevent the bmc from restarting
   immediately after the update.
2. Change the RequestedActivation attribute value and start to update
   the image bmc.

Tested: Manually start the phopshor-usb-manager daemon, and saw that
the bmc upgrade file(*.tar) has been copied to /tmp/images and
the update has been triggered, and bmc has not been restarted
immediately.

Signed-off-by: George Liu <liuxiwei@inspur.com>
Change-Id: Ic650e34f8ec61d3c826332d28275db90f9ef348e
diff --git a/subprojects/sdeventplus.wrap b/subprojects/sdeventplus.wrap
new file mode 100644
index 0000000..085bb5e
--- /dev/null
+++ b/subprojects/sdeventplus.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/openbmc/sdeventplus.git
+revision = HEAD
diff --git a/usb/meson.build b/usb/meson.build
index 9ac5876..bc4462a 100644
--- a/usb/meson.build
+++ b/usb/meson.build
@@ -13,6 +13,14 @@
     '../utils.cpp',
     ]
 
+sdeventplus_dep = dependency(
+    'sdeventplus',
+    fallback: [
+        'sdeventplus',
+        'sdeventplus_dep'
+    ],
+)
+
 phosphor_logging_dep = dependency(
     'phosphor-logging',
     fallback: ['phosphor-logging', 'phosphor_logging_dep'],
@@ -26,6 +34,7 @@
         CLI11_dep,
         phosphor_logging_dep,
         sdbusplus_dep,
+        sdeventplus_dep,
     ],
     install: true,
     install_dir: get_option('bindir')
diff --git a/usb/usb_manager.cpp b/usb/usb_manager.cpp
index b002dbd..ed831dd 100644
--- a/usb/usb_manager.cpp
+++ b/usb/usb_manager.cpp
@@ -46,5 +46,79 @@
     return false;
 }
 
+void USBManager::setApplyTime()
+{
+    utils::PropertyValue value =
+        "xyz.openbmc_project.Software.ApplyTime.RequestedApplyTimes.OnReset";
+    constexpr auto objectPath = "/xyz/openbmc_project/software/apply_time";
+    constexpr auto interface = "xyz.openbmc_project.Software.ApplyTime";
+    constexpr auto propertyName = "RequestedApplyTime";
+    try
+    {
+        utils::setProperty(bus, objectPath, interface, propertyName, value);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Failed to set RequestedApplyTime property, ERROR:{ERROR}",
+                   "ERROR", e.what());
+    }
+}
+
+void USBManager::setRequestedActivation(const std::string& path)
+{
+    utils::PropertyValue value =
+        "xyz.openbmc_project.Software.Activation.RequestedActivations.Active";
+    constexpr auto interface = "xyz.openbmc_project.Software.Activation";
+    constexpr auto propertyName = "RequestedActivation";
+    try
+    {
+        utils::setProperty(bus, path, interface, propertyName, value);
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("Failed to set RequestedActivation property, ERROR:{ERROR}",
+                   "ERROR", e.what());
+    }
+
+    return;
+}
+
+void USBManager::updateActivation(sdbusplus::message::message& msg)
+{
+    std::map<std::string, std::map<std::string, std::variant<std::string>>>
+        interfaces;
+    sdbusplus::message::object_path path;
+    msg.read(path, interfaces);
+
+    constexpr auto imageInterface = "xyz.openbmc_project.Software.Activation";
+    constexpr auto readyPro =
+        "xyz.openbmc_project.Software.Activation.Activations.Ready";
+    for (auto& interface : interfaces)
+    {
+        if (interface.first != imageInterface)
+        {
+            continue;
+        }
+
+        try
+        {
+            auto propVal =
+                utils::getProperty(bus, path.str, imageInterface, "Activation");
+            const auto& imageProp = std::get<std::string>(propVal);
+            if (imageProp == readyPro && isUSBCodeUpdate)
+            {
+                setApplyTime();
+                setRequestedActivation(path.str);
+                event.exit(0);
+            }
+        }
+        catch (const std::exception& e)
+        {
+            lg2::error("Failed in getting Activation status, ERROR:{ERROR}",
+                       "ERROR", e.what());
+        }
+    }
+}
+
 } // namespace usb
-} // namespace phosphor
\ No newline at end of file
+} // namespace phosphor
diff --git a/usb/usb_manager.hpp b/usb/usb_manager.hpp
index f3323a8..e67115c 100644
--- a/usb/usb_manager.hpp
+++ b/usb/usb_manager.hpp
@@ -1,6 +1,9 @@
 #pragma once
 
+#include "utils.hpp"
+
 #include <phosphor-logging/lg2.hpp>
+#include <sdeventplus/event.hpp>
 
 #include <filesystem>
 
@@ -9,6 +12,7 @@
 namespace usb
 {
 namespace fs = std::filesystem;
+namespace MatchRules = sdbusplus::bus::match::rules;
 
 class USBManager
 {
@@ -20,8 +24,25 @@
     USBManager& operator=(const USBManager&) = delete;
     USBManager& operator=(USBManager&&) = default;
 
-    explicit USBManager(const fs::path& path) : usbPath(path)
-    {}
+    explicit USBManager(sdbusplus::bus::bus& bus, sdeventplus::Event& event,
+                        const fs::path& path) :
+        bus(bus),
+        event(event), usbPath(path), isUSBCodeUpdate(false),
+        fwUpdateMatcher(bus,
+                        MatchRules::interfacesAdded() +
+                            MatchRules::path("/xyz/openbmc_project/software"),
+                        std::bind(std::mem_fn(&USBManager::updateActivation),
+                                  this, std::placeholders::_1))
+    {
+        if (!run())
+        {
+            lg2::error("Failed to FW Update via USB, usbPath:{USBPATH}",
+                       "USBPATH", usbPath);
+            event.exit(0);
+        }
+
+        isUSBCodeUpdate = true;
+    }
 
     /** @brief Find the first file with a .tar extension according to the USB
      *         file path.
@@ -30,9 +51,38 @@
      */
     bool run();
 
+    /** @brief Creates an Activation D-Bus object.
+     *
+     * @param[in]  msg   - Data associated with subscribed signal
+     */
+    void updateActivation(sdbusplus::message::message& msg);
+
+    /** @brief Set Apply Time to OnReset.
+     *
+     */
+    void setApplyTime();
+
+    /** @brief Method to set the RequestedActivation D-Bus property.
+     *
+     *  @param[in] path  - Update the object path of the firmware
+     */
+    void setRequestedActivation(const std::string& path);
+
   private:
+    /** @brief Persistent sdbusplus DBus bus connection. */
+    sdbusplus::bus::bus& bus;
+
+    /** sd event handler. */
+    sdeventplus::Event& event;
+
     /** The USB path detected. */
     const fs::path& usbPath;
+
+    /** Indicates whether USB codeupdate is going on. */
+    bool isUSBCodeUpdate;
+
+    /** sdbusplus signal match for new image. */
+    sdbusplus::bus::match_t fwUpdateMatcher;
 };
 
 } // namespace usb
diff --git a/usb/usb_manager_main.cpp b/usb/usb_manager_main.cpp
index 143125d..1f3ac81 100644
--- a/usb/usb_manager_main.cpp
+++ b/usb/usb_manager_main.cpp
@@ -2,10 +2,16 @@
 
 #include <CLI/CLI.hpp>
 #include <phosphor-logging/lg2.hpp>
+#include <sdeventplus/event.hpp>
 
 int main(int argc, char** argv)
 {
     namespace fs = std::filesystem;
+    // Dbus constructs
+    auto bus = sdbusplus::bus::new_default();
+
+    // Get a default event loop
+    auto event = sdeventplus::Event::get_default();
 
     std::string fileName{};
 
@@ -22,14 +28,11 @@
     }
 
     fs::path usbPath = fs::path{"/run/media/usb"} / fileName;
-    phosphor::usb::USBManager manager(usbPath);
+    phosphor::usb::USBManager manager(bus, event, usbPath);
 
-    if (!manager.run())
-    {
-        lg2::error("Failed to FW Update via USB, usbPath:{USBPATH}", "USBPATH",
-                   usbPath);
-        return -1;
-    }
+    // Attach the bus to sd_event to service user requests
+    bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
+    event.loop();
 
     return 0;
 }