common: handle match for config intf added/removed

When the configuration interface for any device is added or removed as
part of dynamic configuration (via EM) the software manager has to
handle the dbus match and add or remove devices as necessary.

When an update is in progress, the device is not removed,
even if the configuration was removed.

Tested: On Tyan S8030 with bios software updater

For testing add/remove of configuration interfaces, the configuration
was probed on the powersupply FRU.

In case of InterfacesAdded:

```
May 26 14:54:00 s8030-bmc-30303035c0c1 phosphor-bios-software-update[9530]: [config] found configuration interface at xyz.openbmc_project.EntityManager, /xyz/openbmc_project/inventory/system/board/HostSPIFlash/HostSPIFlash
May 26 14:54:09 s8030-bmc-30303035c0c1 phosphor-bios-software-update[9530]: Missing property Name on path /xyz/openbmc_project/inventory/system/board/HostSPIFlash/HostSPIFlash, interface xyz.openbmc_project.Configuration.SPIFlash.MuxOutputs1
May 26 14:54:09 s8030-bmc-30303035c0c1 phosphor-bios-software-update[9530]: Missing property Polarity on path /xyz/openbmc_project/inventory/system/board/HostSPIFlash/HostSPIFlash, interface xyz.openbmc_project.Configuration.SPIFlash.MuxOutputs1
May 26 14:54:09 s8030-bmc-30303035c0c1 phosphor-bios-software-update[9530]: SPI device: 1:0
May 26 14:54:09 s8030-bmc-30303035c0c1 phosphor-bios-software-update[9530]: Found SPI Address 1e630000.spi
May 26 14:54:09 s8030-bmc-30303035c0c1 phosphor-bios-software-update[9530]: SPI Device HostSPIFlash at 1:0 initialized successfully
May 26 14:54:09 s8030-bmc-30303035c0c1 phosphor-bios-software-update[9530]: HostSPIFlash_5930: created dbus interfaces on path /xyz/openbmc_project/software/HostSPIFlash_5930
May 26 14:54:09 s8030-bmc-30303035c0c1 phosphor-bios-software-update[9530]: [Software] enabling update of /xyz/openbmc_project/software/HostSPIFlash_5930 (adding the update interface)
```

```
busctl tree xyz.openbmc_project.Software.BIOS
`- /xyz
  `- /xyz/openbmc_project
    `- /xyz/openbmc_project/software
      `- /xyz/openbmc_project/software/HostSPIFlash_5930
```

In case of InterfacesRemoved:

```
May 26 14:58:23 s8030-bmc-30303035c0c1 phosphor-bios-software-update[9530]: detected interface xyz.openbmc_project.Configuration.SPIFlash removed on /xyz/openbmc_project/inventory/system/board/HostSPIFlash/HostSPIFlash
May 26 14:58:23 s8030-bmc-30303035c0c1 phosphor-bios-software-update[9530]: removing device at /xyz/openbmc_project/inventory/system/board/HostSPIFlash/HostSPIFlash
```

```
 busctl tree xyz.openbmc_project.Software.BIOS
`- /xyz
  `- /xyz/openbmc_project
    `- /xyz/openbmc_project/software
```

Change-Id: I4faeb8e0144408b57767783ba2c5c4f3561f4021
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/common/include/device.hpp b/common/include/device.hpp
index 2a52276..3440a4b 100644
--- a/common/include/device.hpp
+++ b/common/include/device.hpp
@@ -117,6 +117,7 @@
 
     friend update::SoftwareUpdate;
     friend Software;
+    friend manager::SoftwareManager;
 };
 
 }; // namespace phosphor::software::device
diff --git a/common/include/software_manager.hpp b/common/include/software_manager.hpp
index 4cf3419..59d450b 100644
--- a/common/include/software_manager.hpp
+++ b/common/include/software_manager.hpp
@@ -1,6 +1,7 @@
 #pragma once
 
 #include "device.hpp"
+#include "sdbusplus/async/match.hpp"
 
 #include <boost/asio/steady_timer.hpp>
 #include <phosphor-logging/lg2.hpp>
@@ -57,6 +58,18 @@
         const std::string& service, const std::string& path,
         const std::string& interface);
 
+    sdbusplus::async::task<void> handleInterfaceRemoved(
+        const sdbusplus::message::object_path& path);
+
+    sdbusplus::async::task<void> interfaceAddedMatch(
+        std::vector<std::string> interfaces);
+    sdbusplus::async::task<void> interfaceRemovedMatch(
+        std::vector<std::string> interfaces);
+
+    // DBus matches for interfaces added and interfaces removed
+    sdbusplus::async::match configIntfAddedMatch;
+    sdbusplus::async::match configIntfRemovedMatch;
+
     // this is appended to the common prefix to construct the dbus name
     std::string serviceNameSuffix;
 
diff --git a/common/src/software_manager.cpp b/common/src/software_manager.cpp
index 0d0bc17..2173458 100644
--- a/common/src/software_manager.cpp
+++ b/common/src/software_manager.cpp
@@ -1,10 +1,12 @@
 #include "software_manager.hpp"
 
+#include <boost/container/flat_map.hpp>
 #include <phosphor-logging/lg2.hpp>
 #include <sdbusplus/asio/object_server.hpp>
 #include <sdbusplus/async.hpp>
 #include <sdbusplus/async/context.hpp>
 #include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
 #include <xyz/openbmc_project/Association/Definitions/server.hpp>
 #include <xyz/openbmc_project/ObjectMapper/client.hpp>
 #include <xyz/openbmc_project/Software/Version/client.hpp>
@@ -16,9 +18,20 @@
 
 using namespace phosphor::software::manager;
 
+using AsyncMatch = sdbusplus::async::match;
+
+namespace RulesIntf = sdbusplus::bus::match::rules;
+static constexpr auto serviceNameEM = "xyz.openbmc_project.EntityManager";
+
+const auto matchRuleSender = RulesIntf::sender(serviceNameEM);
+const auto matchRulePath = RulesIntf::path("/xyz/openbmc_project/inventory");
+
 SoftwareManager::SoftwareManager(sdbusplus::async::context& ctx,
                                  const std::string& serviceNameSuffix) :
-    ctx(ctx), serviceNameSuffix(serviceNameSuffix),
+    ctx(ctx),
+    configIntfAddedMatch(ctx, RulesIntf::interfacesAdded() + matchRuleSender),
+    configIntfRemovedMatch(ctx, RulesIntf::interfacesRemoved() + matchRulePath),
+    serviceNameSuffix(serviceNameSuffix),
     manager(ctx, sdbusplus::client::xyz::openbmc_project::software::Version<>::
                      namespace_path)
 {
@@ -95,6 +108,9 @@
     const std::vector<std::string>& configurationInterfaces)
 // NOLINTEND(readability-static-accessed-through-instance)
 {
+    ctx.spawn(interfaceAddedMatch(configurationInterfaces));
+    ctx.spawn(interfaceRemovedMatch(configurationInterfaces));
+
     auto client = sdbusplus::client::xyz::openbmc_project::ObjectMapper<>(ctx)
                       .service("xyz.openbmc_project.ObjectMapper")
                       .path("/xyz/openbmc_project/object_mapper");
@@ -153,7 +169,97 @@
         co_return;
     }
 
+    if (devices.contains(optConfig.value().objectPath))
+    {
+        error("Device configured from {PATH} is already known", "PATH",
+              optConfig.value().objectPath);
+        co_return;
+    }
+
     co_await initDevice(service, path, optConfig.value());
 
     co_return;
 }
+
+using BasicVariantType =
+    std::variant<std::vector<std::string>, std::string, int64_t, uint64_t,
+                 double, int32_t, uint32_t, int16_t, uint16_t, uint8_t, bool>;
+using InterfacesMap = boost::container::flat_map<std::string, BasicVariantType>;
+using ConfigMap = boost::container::flat_map<std::string, InterfacesMap>;
+
+// NOLINTBEGIN(readability-static-accessed-through-instance)
+sdbusplus::async::task<void> SoftwareManager::interfaceAddedMatch(
+    std::vector<std::string> interfaces)
+// NOLINTEND(readability-static-accessed-through-instance)
+{
+    while (!ctx.stop_requested())
+    {
+        std::tuple<std::string, ConfigMap> nextResult("", {});
+        nextResult = co_await configIntfAddedMatch
+                         .next<sdbusplus::message::object_path, ConfigMap>();
+
+        auto& [objPath, interfacesMap] = nextResult;
+
+        for (auto& interface : interfaces)
+        {
+            if (interfacesMap.contains(interface))
+            {
+                debug("detected interface {INTF} added on {PATH}", "INTF",
+                      interface, "PATH", objPath);
+
+                co_await handleInterfaceAdded(serviceNameEM, objPath,
+                                              interface);
+            }
+        }
+    }
+}
+
+// NOLINTBEGIN(readability-static-accessed-through-instance)
+sdbusplus::async::task<void> SoftwareManager::interfaceRemovedMatch(
+    std::vector<std::string> interfaces)
+// NOLINTEND(readability-static-accessed-through-instance)
+{
+    while (!ctx.stop_requested())
+    {
+        auto nextResult = co_await configIntfRemovedMatch.next<
+            sdbusplus::message::object_path, std::vector<std::string>>();
+
+        auto& [objPath, interfacesRemoved] = nextResult;
+
+        debug("detected interface removed on {PATH}", "PATH", objPath);
+
+        for (auto& interface : interfaces)
+        {
+            if (std::ranges::find(interfacesRemoved, interface) !=
+                interfacesRemoved.end())
+            {
+                debug("detected interface {INTF} removed on {PATH}", "INTF",
+                      interface, "PATH", objPath);
+                co_await handleInterfaceRemoved(objPath);
+            }
+        }
+    }
+}
+
+sdbusplus::async::task<void> SoftwareManager::handleInterfaceRemoved(
+    const sdbusplus::message::object_path& objPath)
+{
+    if (!devices.contains(objPath))
+    {
+        debug("could not find a device to remove");
+        co_return;
+    }
+
+    if (devices[objPath]->updateInProgress)
+    {
+        // TODO: This code path needs to be cleaned up in the future to
+        // eventually remove the device.
+        debug(
+            "removal of device at {PATH} ignored because of in-progress update",
+            "PATH", objPath.str);
+        co_return;
+    }
+
+    debug("removing device at {PATH}", "PATH", objPath.str);
+    devices.erase(objPath);
+}