PEL: Read the motherboard CCIN

The 'CCIN' VPD field from the motherboard will go into the SRC PEL
section, so add a DataInterface API for it.  That class will read it
from the VINI record and CC keyword in the motherboard VPD.

First, it has to find the motherboard by finding the D-Bus object that
has the motherboard inventory item interface.  If that object doesn't
exist yet, add an interfacesAdded watch for it.  When the object is
found, add a PropertyWatcher watch for the CC property on the VINI
interface.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I143e35405dbcd3ebec4fd9b31972b8de8e0019c5
diff --git a/extensions/openpower-pels/data_interface.cpp b/extensions/openpower-pels/data_interface.cpp
index 351805c..f64609a 100644
--- a/extensions/openpower-pels/data_interface.cpp
+++ b/extensions/openpower-pels/data_interface.cpp
@@ -34,6 +34,7 @@
 {
 constexpr auto objectMapper = "/xyz/openbmc_project/object_mapper";
 constexpr auto systemInv = "/xyz/openbmc_project/inventory/system";
+constexpr auto baseInv = "/xyz/openbmc_project/inventory";
 constexpr auto bmcState = "/xyz/openbmc_project/state/bmc0";
 constexpr auto chassisState = "/xyz/openbmc_project/state/chassis0";
 constexpr auto hostState = "/xyz/openbmc_project/state/host0";
@@ -53,15 +54,20 @@
 constexpr auto bmcState = "xyz.openbmc_project.State.BMC";
 constexpr auto chassisState = "xyz.openbmc_project.State.Chassis";
 constexpr auto hostState = "xyz.openbmc_project.State.Host";
+constexpr auto invMotherboard =
+    "xyz.openbmc_project.Inventory.Item.Board.Motherboard";
+constexpr auto viniRecordVPD = "com.ibm.ipzvpd.VINI";
 } // namespace interface
 
 using namespace sdbusplus::xyz::openbmc_project::State::OperatingSystem::server;
+using sdbusplus::exception::SdBusError;
 
 DataInterface::DataInterface(sdbusplus::bus::bus& bus) : _bus(bus)
 {
     readBMCFWVersion();
     readServerFWVersion();
     readBMCFWVersionID();
+    readMotherboardCCIN();
 
     // Watch both the Model and SN properties on the system's Asset iface
     _properties.emplace_back(std::make_unique<InterfaceWatcher<DataInterface>>(
@@ -169,6 +175,23 @@
     reply.read(value);
 }
 
+DBusPathList DataInterface::getPaths(const DBusInterfaceList& interfaces) const
+{
+
+    auto method = _bus.new_method_call(
+        service_name::objectMapper, object_path::objectMapper,
+        interface::objectMapper, "GetSubTreePaths");
+
+    method.append(std::string{"/"}, 0, interfaces);
+
+    auto reply = _bus.call(method);
+
+    DBusPathList paths;
+    reply.read(paths);
+
+    return paths;
+}
+
 DBusService DataInterface::getService(const std::string& objectPath,
                                       const std::string& interface) const
 {
@@ -252,5 +275,56 @@
     _bmcFWVersionID = getOSReleaseValue("VERSION_ID").value_or("");
 }
 
+void DataInterface::readMotherboardCCIN()
+{
+    try
+    {
+        // First, try to find the motherboard
+        auto motherboards = getPaths({interface::invMotherboard});
+        if (motherboards.empty())
+        {
+            throw std::runtime_error("No motherboards yet");
+        }
+
+        // Found it, so now get the CCIN
+        _properties.emplace_back(
+            std::make_unique<PropertyWatcher<DataInterface>>(
+                _bus, motherboards.front(), interface::viniRecordVPD, "CC",
+                *this,
+                [this](const auto& ccin) { this->setMotherboardCCIN(ccin); }));
+    }
+    catch (const std::exception& e)
+    {
+        // No motherboard in the inventory yet - watch for it
+        _inventoryIfacesAddedMatch = std::make_unique<sdbusplus::bus::match_t>(
+            _bus, match_rules::interfacesAdded(object_path::baseInv),
+            std::bind(std::mem_fn(&DataInterface::motherboardIfaceAdded), this,
+                      std::placeholders::_1));
+    }
+}
+
+void DataInterface::motherboardIfaceAdded(sdbusplus::message::message& msg)
+{
+    sdbusplus::message::object_path path;
+    DBusInterfaceMap interfaces;
+
+    msg.read(path, interfaces);
+
+    // This is watching the whole inventory, so check if it's what we want
+    if (interfaces.find(interface::invMotherboard) == interfaces.end())
+    {
+        return;
+    }
+
+    // Done watching for any new inventory interfaces
+    _inventoryIfacesAddedMatch.reset();
+
+    // Watch the motherboard CCIN, using the service from this signal
+    // for the initial property read.
+    _properties.emplace_back(std::make_unique<PropertyWatcher<DataInterface>>(
+        _bus, path, interface::viniRecordVPD, "CC", msg.get_sender(), *this,
+        [this](const auto& ccin) { this->setMotherboardCCIN(ccin); }));
+}
+
 } // namespace pels
 } // namespace openpower
diff --git a/extensions/openpower-pels/data_interface.hpp b/extensions/openpower-pels/data_interface.hpp
index 19da482..6caf2b8 100644
--- a/extensions/openpower-pels/data_interface.hpp
+++ b/extensions/openpower-pels/data_interface.hpp
@@ -214,6 +214,16 @@
         return _hostState;
     }
 
+    /**
+     * @brief Returns the motherboard CCIN
+     *
+     * @return std::string The motherboard CCIN
+     */
+    virtual std::string getMotherboardCCIN() const
+    {
+        return _motherboardCCIN;
+    }
+
   protected:
     /**
      * @brief Sets the host on/off state and runs any
@@ -309,6 +319,11 @@
      * @brief The host state property
      */
     std::string _hostState;
+
+    /**
+     * @brief The motherboard CCIN
+     */
+    std::string _motherboardCCIN;
 };
 
 /**
@@ -397,6 +412,44 @@
     void readBMCFWVersionID();
 
     /**
+     * @brief Reads the motherboard CCIN and puts it into _motherboardCCIN.
+     *
+     * It finds the motherboard first, possibly having to wait for it to
+     * show up.
+     */
+    void readMotherboardCCIN();
+
+    /**
+     * @brief Finds all D-Bus paths that contain any of the interfaces
+     *        passed in, by using GetSubTreePaths.
+     *
+     * @param[in] interfaces - The desired interfaces
+     *
+     * @return The D-Bus paths.
+     */
+    DBusPathList getPaths(const DBusInterfaceList& interfaces) const;
+
+    /**
+     * @brief The interfacesAdded callback used on the inventory to
+     *        find the D-Bus object that has the motherboard interface.
+     *        When the motherboard is found, it then adds a PropertyWatcher
+     *        for the motherboard CCIN.
+     */
+    void motherboardIfaceAdded(sdbusplus::message::message& msg);
+
+    /**
+     * @brief Set the motherboard CCIN from the DBus variant that
+     *        contains it.
+     *
+     * @param[in] ccin - The CCIN variant, a vector<uint8_t>.
+     */
+    void setMotherboardCCIN(const DBusValue& ccin)
+    {
+        const auto& c = std::get<std::vector<uint8_t>>(ccin);
+        _motherboardCCIN = std::string{c.begin(), c.end()};
+    }
+
+    /**
      * @brief The D-Bus property or interface watchers that have callbacks
      *        registered that will set members in this class when
      *        they change.
@@ -407,6 +460,14 @@
      * @brief The sdbusplus bus object for making D-Bus calls.
      */
     sdbusplus::bus::bus& _bus;
+
+    /**
+     * @brief The interfacesAdded match object used to wait for inventory
+     *        interfaces to show up, so that the object with the motherboard
+     *        interface can be found.  After it is found, this object is
+     *        deleted.
+     */
+    std::unique_ptr<sdbusplus::bus::match_t> _inventoryIfacesAddedMatch;
 };
 
 } // namespace pels