psu-ng: Updates to get VPD data to inventory

Add in code to retrieve the VPD data and populate that data to the D-Bus
inventory properties.

Use the PMBus utilities to retrieve the model (CCIN for IBM power
supplies), part number, serial number, and firmware version information
from the sysfs "files" provided via the device driver.

Only build in IBM VPD data collection and reporting if meson ran with
-Dibm-vpd=true.

Tested:
    Copied SDK build of phospor-psu-monitor with -Dibm-vpd=true to
    Rainier hardware system, verified inventory properties updated/added.

Signed-off-by: Brandon Wyman <bjwyman@gmail.com>
Change-Id: I61688b154ead570e9d9390342596bf7d840f4dce
diff --git a/phosphor-power-supply/power_supply.cpp b/phosphor-power-supply/power_supply.cpp
index 8c80ef3..2b77bcd 100644
--- a/phosphor-power-supply/power_supply.cpp
+++ b/phosphor-power-supply/power_supply.cpp
@@ -1,3 +1,5 @@
+#include "config.h"
+
 #include "power_supply.hpp"
 
 #include "types.hpp"
@@ -5,6 +7,10 @@
 
 #include <xyz/openbmc_project/Common/Device/error.hpp>
 
+#include <chrono>  // sleep_for()
+#include <cstdint> // uint8_t...
+#include <thread>  // sleep_for()
+
 namespace phosphor::power::psu
 {
 
@@ -157,8 +163,13 @@
         if (std::get<bool>(valPropMap->second))
         {
             present = true;
+            // TODO: Immediately trying to read or write the "files" causes read
+            // or write failures.
+            using namespace std::chrono_literals;
+            std::this_thread::sleep_for(20ms);
             onOffConfig(phosphor::pmbus::ON_OFF_CONFIG_CONTROL_PIN_ONLY);
             clearFaults();
+            updateInventory();
         }
         else
         {
@@ -170,4 +181,150 @@
     }
 }
 
+void PowerSupply::updateInventory()
+{
+    using namespace phosphor::pmbus;
+
+#ifdef IBM_VPD
+    std::string ccin;
+    std::string pn;
+    std::string fn;
+    std::string header;
+    std::string sn;
+    std::string version;
+    using PropertyMap =
+        std::map<std::string, std::variant<std::string, std::vector<uint8_t>>>;
+    PropertyMap assetProps;
+    PropertyMap versionProps;
+    PropertyMap ipzvpdDINFProps;
+    PropertyMap ipzvpdVINIProps;
+    using InterfaceMap = std::map<std::string, PropertyMap>;
+    InterfaceMap interfaces;
+    using ObjectMap = std::map<sdbusplus::message::object_path, InterfaceMap>;
+    ObjectMap object;
+#endif
+
+    if (present)
+    {
+        // TODO: non-IBM inventory updates?
+
+#ifdef IBM_VPD
+        try
+        {
+            ccin = pmbusIntf->readString(CCIN, Type::HwmonDeviceDebug);
+            assetProps.emplace(MODEL_PROP, ccin);
+        }
+        catch (ReadFailure& e)
+        {
+            // Ignore the read failure, let pmbus code indicate failure, path...
+            // TODO - ibm918
+            // https://github.com/openbmc/docs/blob/master/designs/vpd-collection.md
+            // The BMC must log errors if any of the VPD cannot be properly
+            // parsed or fails ECC checks.
+        }
+
+        try
+        {
+            pn = pmbusIntf->readString(PART_NUMBER, Type::HwmonDeviceDebug);
+            assetProps.emplace(PN_PROP, pn);
+        }
+        catch (ReadFailure& e)
+        {
+            // Ignore the read failure, let pmbus code indicate failure, path...
+        }
+
+        try
+        {
+            fn = pmbusIntf->readString(FRU_NUMBER, Type::HwmonDeviceDebug);
+        }
+        catch (ReadFailure& e)
+        {
+            // Ignore the read failure, let pmbus code indicate failure, path...
+        }
+
+        try
+        {
+            header =
+                pmbusIntf->readString(SERIAL_HEADER, Type::HwmonDeviceDebug);
+            sn = pmbusIntf->readString(SERIAL_NUMBER, Type::HwmonDeviceDebug);
+            assetProps.emplace(SN_PROP, sn);
+        }
+        catch (ReadFailure& e)
+        {
+            // Ignore the read failure, let pmbus code indicate failure, path...
+        }
+
+        try
+        {
+            version = pmbusIntf->readString(FW_VERSION, Type::HwmonDeviceDebug);
+            versionProps.emplace(VERSION_PROP, version);
+        }
+        catch (ReadFailure& e)
+        {
+            // Ignore the read failure, let pmbus code indicate failure, path...
+        }
+
+        ipzvpdVINIProps.emplace("CC",
+                                std::vector<uint8_t>(ccin.begin(), ccin.end()));
+        ipzvpdVINIProps.emplace("PN",
+                                std::vector<uint8_t>(pn.begin(), pn.end()));
+        ipzvpdVINIProps.emplace("FN",
+                                std::vector<uint8_t>(fn.begin(), fn.end()));
+        std::string header_sn = header + sn + '\0';
+        ipzvpdVINIProps.emplace(
+            "SN", std::vector<uint8_t>(header_sn.begin(), header_sn.end()));
+        std::string description = "IBM PS";
+        ipzvpdVINIProps.emplace(
+            "DR", std::vector<uint8_t>(description.begin(), description.end()));
+
+        // Update the Resource Identifier (RI) keyword
+        // 2 byte FRC: 0x0003
+        // 2 byte RID: 0x1000, 0x1001...
+        std::uint8_t num = std::stoul(
+            inventoryPath.substr(inventoryPath.size() - 1, 1), nullptr, 0);
+        std::vector<uint8_t> ri{0x00, 0x03, 0x10, num};
+        ipzvpdDINFProps.emplace("RI", ri);
+
+        // Fill in the FRU Label (FL) keyword.
+        std::string fl = "E";
+        fl.push_back(inventoryPath.back());
+        fl.resize(FL_KW_SIZE, ' ');
+        ipzvpdDINFProps.emplace("FL",
+                                std::vector<uint8_t>(fl.begin(), fl.end()));
+
+        interfaces.emplace(ASSET_IFACE, std::move(assetProps));
+        interfaces.emplace(VERSION_IFACE, std::move(versionProps));
+        interfaces.emplace(DINF_IFACE, std::move(ipzvpdDINFProps));
+        interfaces.emplace(VINI_IFACE, std::move(ipzvpdVINIProps));
+
+        auto path = inventoryPath.substr(strlen(INVENTORY_OBJ_PATH));
+        object.emplace(path, std::move(interfaces));
+
+        try
+        {
+            auto service =
+                util::getService(INVENTORY_OBJ_PATH, INVENTORY_MGR_IFACE, bus);
+
+            if (service.empty())
+            {
+                log<level::ERR>("Unable to get inventory manager service");
+                return;
+            }
+
+            auto method =
+                bus.new_method_call(service.c_str(), INVENTORY_OBJ_PATH,
+                                    INVENTORY_MGR_IFACE, "Notify");
+
+            method.append(std::move(object));
+
+            auto reply = bus.call(method);
+        }
+        catch (std::exception& e)
+        {
+            log<level::ERR>(e.what(), entry("PATH=%s", inventoryPath.c_str()));
+        }
+#endif
+    }
+}
+
 } // namespace phosphor::power::psu