hsbp-manager: add drive presence and software version

This adds support for reading the sw version and drive
presence from the hsbp cpld.

Tested:

Version is available in redfish

root@intel-obmc:~# busctl tree xyz.openbmc_project.HsbpManager --no-pager
`-/xyz
  `-/xyz/openbmc_project
    |-/xyz/openbmc_project/inventory
    | `-/xyz/openbmc_project/inventory/item
    |   |-/xyz/openbmc_project/inventory/item/drive
    |   | |-/xyz/openbmc_project/inventory/item/drive/Drive_1
    |   | |-/xyz/openbmc_project/inventory/item/drive/Drive_2
    |   | |-/xyz/openbmc_project/inventory/item/drive/Drive_3
    |   | |-/xyz/openbmc_project/inventory/item/drive/Drive_4
    |   | |-/xyz/openbmc_project/inventory/item/drive/Drive_5
    |   | |-/xyz/openbmc_project/inventory/item/drive/Drive_6
    |   | |-/xyz/openbmc_project/inventory/item/drive/Drive_7
    |   | `-/xyz/openbmc_project/inventory/item/drive/Drive_8
    |   `-/xyz/openbmc_project/inventory/item/hsbp
    |     `-/xyz/openbmc_project/inventory/item/hsbp/J85894_HSBP_1

root@intel-obmc:~# busctl introspect xyz.openbmc_project.HsbpManager /xyz/openbmc_project/inventory/item/hsbp/J85894_HSBP_1 --no-pager
NAME                                 TYPE      SIGNATURE RESULT/VALUE                             FLAGS
org.freedesktop.DBus.Introspectable  interface -         -                                        -
.Introspect                          method    -         s                                        -
org.freedesktop.DBus.Peer            interface -         -                                        -
.GetMachineId                        method    -         s                                        -
.Ping                                method    -         -                                        -
org.freedesktop.DBus.Properties      interface -         -                                        -
.Get                                 method    ss        v                                        -
.GetAll                              method    s         a{sv}                                    -
.Set                                 method    ssv       -                                        -
.PropertiesChanged                   signal    sa{sv}as  -                                        -
xyz.openbmc_project.Inventory.Item   interface -         -                                        -
.Present                             property  b         true                                     emits-change
.PrettyName                          property  s         "J85894 HSBP 1"                          emits-change
xyz.openbmc_project.Software.Version interface -         -                                        -
.Version                             property  s         "00.02.01"                               emits-change
.Purpose                             property  s         "xyz.openbmc_project.Software.Version... emits-change

Change-Id: I73fd2669f4a0e9b8e499107a960f2169f63873e3
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/hsbp-manager/src/hsbp_manager.cpp b/hsbp-manager/src/hsbp_manager.cpp
index 110d32d..e02b76a 100644
--- a/hsbp-manager/src/hsbp_manager.cpp
+++ b/hsbp-manager/src/hsbp_manager.cpp
@@ -23,6 +23,7 @@
 #include <sdbusplus/asio/object_server.hpp>
 #include <sdbusplus/bus/match.hpp>
 #include <string>
+#include <utility>
 
 extern "C" {
 #include <i2c/smbus.h>
@@ -32,18 +33,60 @@
 constexpr const char* configType =
     "xyz.openbmc_project.Configuration.Intel_HSBP_CPLD";
 
+constexpr size_t scanRateSeconds = 5;
+constexpr size_t maxDrives = 8; // only 1 byte alloted
+
 boost::asio::io_context io;
 auto conn = std::make_shared<sdbusplus::asio::connection>(io);
 sdbusplus::asio::object_server objServer(conn);
 
+static std::string zeroPad(const uint8_t val)
+{
+    std::ostringstream version;
+    version << std::setw(2) << std::setfill('0') << static_cast<size_t>(val);
+    return version.str();
+}
+
+struct Drive
+{
+    Drive(size_t driveIndex, bool isPresent, bool isOperational, bool nvme) :
+        isNvme(nvme)
+    {
+        constexpr const char* basePath =
+            "/xyz/openbmc_project/inventory/item/drive/Drive_";
+        itemIface = objServer.add_interface(
+            basePath + std::to_string(driveIndex), inventory::interface);
+        itemIface->register_property("Present", isPresent);
+        itemIface->register_property("PrettyName",
+                                     "Drive " + std::to_string(driveIndex));
+        itemIface->initialize();
+        operationalIface = objServer.add_interface(
+            basePath + std::to_string(driveIndex),
+            "xyz.openbmc_project.State.Decorator.OperationalStatus");
+        operationalIface->register_property("Functional", isOperational);
+        operationalIface->initialize();
+    }
+    ~Drive()
+    {
+        objServer.remove_interface(itemIface);
+        objServer.remove_interface(operationalIface);
+    }
+
+    std::shared_ptr<sdbusplus::asio::dbus_interface> itemIface;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> operationalIface;
+    bool isNvme;
+};
+
 struct Backplane
 {
 
-    Backplane(size_t busIn, size_t addressIn, const std::string& nameIn) :
-        bus(busIn), address(addressIn), name(nameIn)
+    Backplane(size_t busIn, size_t addressIn, size_t backplaneIndexIn,
+              const std::string& nameIn) :
+        bus(busIn),
+        address(addressIn), backplaneIndex(backplaneIndexIn - 1), name(nameIn),
+        timer(std::make_shared<boost::asio::steady_timer>(io))
     {
     }
-
     void run()
     {
         file = open(("/dev/i2c-" + std::to_string(bus)).c_str(), O_RDWR);
@@ -59,33 +102,235 @@
             return;
         }
 
+        if (!getPresent())
+        {
+            std::cerr << "Cannot detect CPLD\n";
+            return;
+        }
+
+        getBootVer(bootVer);
+        getFPGAVer(fpgaVer);
+        getSecurityRev(securityRev);
+        std::string dbusName = boost::replace_all_copy(name, " ", "_");
         hsbpItemIface = objServer.add_interface(
-            "/xyz/openbmc_project/inventory/item/hsbp/" +
-                boost::replace_all_copy(name, " ", "_"),
+            "/xyz/openbmc_project/inventory/item/hsbp/" + dbusName,
             inventory::interface);
-        hsbpItemIface->register_property("Present", present(true));
+        hsbpItemIface->register_property("Present", true);
         hsbpItemIface->register_property("PrettyName", name);
         hsbpItemIface->initialize();
 
-        if (!present())
+        versionIface =
+            objServer.add_interface(hsbpItemIface->get_object_path(),
+                                    "xyz.openbmc_project.Software.Version");
+        versionIface->register_property("Version", zeroPad(bootVer) + "." +
+                                                       zeroPad(fpgaVer) + "." +
+                                                       zeroPad(securityRev));
+        versionIface->register_property(
+            "Purpose",
+            std::string(
+                "xyz.openbmc_project.Software.Version.VersionPurpose.HSBP"));
+        versionIface->initialize();
+        getPresence(presence);
+        getIFDET(ifdet);
+
+        createDrives();
+
+        runTimer();
+    }
+
+    void runTimer()
+    {
+        timer->expires_after(std::chrono::seconds(scanRateSeconds));
+        timer->async_wait([this](boost::system::error_code ec) {
+            if (ec == boost::asio::error::operation_aborted)
+            {
+                // we're being destroyed
+                return;
+            }
+            else if (ec)
+            {
+                std::cerr << "timer error " << ec.message() << "\n";
+                return;
+            }
+            uint8_t curPresence = 0;
+            uint8_t curIFDET = 0;
+            uint8_t curFailed = 0;
+
+            getPresence(curPresence);
+            getIFDET(curIFDET);
+            getFailed(curFailed);
+
+            if (curPresence != presence || curIFDET != ifdet ||
+                curFailed != failed)
+            {
+                presence = curPresence;
+                ifdet = curIFDET;
+                failed = curFailed;
+                updateDrives();
+            }
+            runTimer();
+        });
+    }
+
+    void createDrives()
+    {
+        uint8_t nvme = ifdet ^ presence;
+        for (size_t ii = 0; ii < maxDrives; ii++)
         {
-            // backplane isn't there
-            return;
+            bool isNvme = nvme & (1 << ii);
+            bool isPresent = isNvme || (presence & (1 << ii));
+            bool isFailed = !isPresent || failed & (1 << ii);
+
+            // +1 to convert from 0 based to 1 based
+            size_t driveIndex = (backplaneIndex * maxDrives) + ii + 1;
+            drives.emplace_back(driveIndex, isPresent, !isFailed, isNvme);
         }
     }
 
-    bool present(bool update = false)
+    void updateDrives()
     {
-        static bool present = false;
-        if (update)
+
+        uint8_t nvme = ifdet ^ presence;
+        for (size_t ii = 0; ii < maxDrives; ii++)
         {
-            present = i2c_smbus_read_byte(file) >= 0;
+            bool isNvme = nvme & (1 << ii);
+            bool isPresent = isNvme || (presence & (1 << ii));
+            bool isFailed = !isPresent || failed & (1 << ii);
+
+            Drive& drive = drives[ii];
+            drive.isNvme = isNvme;
+            drive.itemIface->set_property("Present", isPresent);
+            drive.operationalIface->set_property("Functional", !isFailed);
         }
+    }
+
+    bool getPresent()
+    {
+        present = i2c_smbus_read_byte(file) >= 0;
         return present;
     }
+
+    bool getTypeID(uint8_t& val)
+    {
+        constexpr uint8_t addr = 2;
+        int ret = i2c_smbus_read_byte_data(file, addr);
+        if (ret < 0)
+        {
+            std::cerr << "Error " << __FUNCTION__ << "\n";
+            return false;
+        }
+        val = static_cast<uint8_t>(ret);
+        return true;
+    }
+
+    bool getBootVer(uint8_t& val)
+    {
+        constexpr uint8_t addr = 3;
+        int ret = i2c_smbus_read_byte_data(file, addr);
+        if (ret < 0)
+        {
+            std::cerr << "Error " << __FUNCTION__ << "\n";
+            return false;
+        }
+        val = static_cast<uint8_t>(ret);
+        return true;
+    }
+
+    bool getFPGAVer(uint8_t& val)
+    {
+        constexpr uint8_t addr = 4;
+        int ret = i2c_smbus_read_byte_data(file, addr);
+        if (ret < 0)
+        {
+            std::cerr << "Error " << __FUNCTION__ << "\n";
+            return false;
+        }
+        val = static_cast<uint8_t>(ret);
+        return true;
+    }
+
+    bool getSecurityRev(uint8_t& val)
+    {
+        constexpr uint8_t addr = 5;
+        int ret = i2c_smbus_read_byte_data(file, addr);
+        if (ret < 0)
+        {
+            std::cerr << "Error " << __FUNCTION__ << "\n";
+            return false;
+        }
+        val = static_cast<uint8_t>(ret);
+        return true;
+    }
+
+    bool getPresence(uint8_t& val)
+    {
+        // NVMe drives do not assert PRSNTn, and as such do not get reported as
+        // PRESENT in this register
+
+        constexpr uint8_t addr = 8;
+
+        int ret = i2c_smbus_read_byte_data(file, addr);
+        if (ret < 0)
+        {
+            std::cerr << "Error " << __FUNCTION__ << "\n";
+            return false;
+        }
+        // presence is inverted
+        val = static_cast<uint8_t>(~ret);
+        return true;
+    }
+
+    bool getIFDET(uint8_t& val)
+    {
+        // This register is a bitmap of parallel GPIO pins connected to the
+        // IFDETn pin of a drive slot. SATA, SAS, and NVMe drives all assert
+        // IFDETn low when they are inserted into the HSBP.This register, in
+        // combination with the PRESENCE register, are used by the BMC to detect
+        // the presence of NVMe drives.
+
+        constexpr uint8_t addr = 9;
+
+        int ret = i2c_smbus_read_byte_data(file, addr);
+        if (ret < 0)
+        {
+            std::cerr << "Error " << __FUNCTION__ << "\n";
+            return false;
+        }
+        // ifdet is inverted
+        val = static_cast<uint8_t>(~ret);
+        return true;
+    }
+
+    bool getFailed(uint8_t& val)
+    {
+        constexpr uint8_t addr = 0xC;
+        int ret = i2c_smbus_read_byte_data(file, addr);
+        if (ret < 0)
+        {
+            std::cerr << "Error " << __FUNCTION__ << "\n";
+            return false;
+        }
+        val = static_cast<uint8_t>(ret);
+        return true;
+    }
+
+    bool getRebuild(uint8_t& val)
+    {
+        constexpr uint8_t addr = 0xD;
+        int ret = i2c_smbus_read_byte_data(file, addr);
+        if (ret < 0)
+        {
+            std::cerr << "Error " << __FUNCTION__ << "\n";
+            return false;
+        }
+        val = static_cast<uint8_t>(ret);
+        return true;
+    }
+
     ~Backplane()
     {
         objServer.remove_interface(hsbpItemIface);
+        objServer.remove_interface(versionIface);
         if (file >= 0)
         {
             close(file);
@@ -94,13 +339,27 @@
 
     size_t bus;
     size_t address;
-    int file = -1;
+    size_t backplaneIndex;
     std::string name;
+    std::shared_ptr<boost::asio::steady_timer> timer;
+    bool present = false;
+    uint8_t typeId = 0;
+    uint8_t bootVer = 0;
+    uint8_t fpgaVer = 0;
+    uint8_t securityRev = 0;
+    uint8_t funSupported = 0;
+    uint8_t presence = 0;
+    uint8_t ifdet = 0;
+    uint8_t failed = 0;
+
+    int file = -1;
+
     std::string type;
 
     std::shared_ptr<sdbusplus::asio::dbus_interface> hsbpItemIface;
-    std::vector<std::shared_ptr<sdbusplus::asio::dbus_interface>>
-        driveItemIfaces;
+    std::shared_ptr<sdbusplus::asio::dbus_interface> versionIface;
+
+    std::vector<Drive> drives;
 };
 
 std::unordered_map<std::string, Backplane> backplanes;
@@ -135,6 +394,7 @@
                         backplanes.clear();
                         std::optional<size_t> bus;
                         std::optional<size_t> address;
+                        std::optional<size_t> backplaneIndex;
                         std::optional<std::string> name;
                         for (const auto& [key, value] : resp)
                         {
@@ -146,19 +406,24 @@
                             {
                                 address = std::get<uint64_t>(value);
                             }
+                            else if (key == "Index")
+                            {
+                                backplaneIndex = std::get<uint64_t>(value);
+                            }
                             else if (key == "Name")
                             {
                                 name = std::get<std::string>(value);
                             }
                         }
-                        if (!bus || !address || !name)
+                        if (!bus || !address || !name || !backplaneIndex)
                         {
                             std::cerr << "Illegal configuration at " << path
                                       << "\n";
                             return;
                         }
                         const auto& [backplane, status] = backplanes.emplace(
-                            *name, Backplane(*bus, *address, *name));
+                            *name,
+                            Backplane(*bus, *address, *backplaneIndex, *name));
                         backplane->second.run();
                     },
                     owner, path, "org.freedesktop.DBus.Properties", "GetAll",