Implement a Command To Get PSU Version

Implement an IPMI command to get PSU version.

Tested:
Run ipmitool raw 0x30 0xef
returns: 03 32 01 03 32 01

Signed-off-by: Cheng C Yang <cheng.c.yang@linux.intel.com>
Change-Id: I087b7786b7dd669f5b1eecb8a9bc9647362fd207
diff --git a/src/oemcommands.cpp b/src/oemcommands.cpp
index 8dcb66b..07f6f22 100644
--- a/src/oemcommands.cpp
+++ b/src/oemcommands.cpp
@@ -3265,6 +3265,109 @@
     return ipmi::responseSuccess();
 }
 
+using BasicVariantType =
+    std::variant<std::vector<std::string>, std::vector<uint64_t>, std::string,
+                 int64_t, uint64_t, double, int32_t, uint32_t, int16_t,
+                 uint16_t, uint8_t, bool>;
+using PropertyMapType =
+    boost::container::flat_map<std::string, BasicVariantType>;
+static constexpr const std::array<const char*, 1> psuPresenceTypes = {
+    "xyz.openbmc_project.Configuration.PSUPresence"};
+int getPSUAddress(ipmi::Context::ptr ctx, uint8_t& bus,
+                  std::vector<uint64_t>& addrTable)
+{
+    boost::system::error_code ec;
+    GetSubTreeType subtree = ctx->bus->yield_method_call<GetSubTreeType>(
+        ctx->yield, ec, "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree",
+        "/xyz/openbmc_project/inventory/system", 3, psuPresenceTypes);
+    if (ec)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Failed to set dbus property to cold redundancy");
+        return -1;
+    }
+    for (const auto& object : subtree)
+    {
+        std::string pathName = object.first;
+        for (const auto& serviceIface : object.second)
+        {
+            std::string serviceName = serviceIface.first;
+
+            ec.clear();
+            PropertyMapType propMap =
+                ctx->bus->yield_method_call<PropertyMapType>(
+                    ctx->yield, ec, serviceName, pathName,
+                    "org.freedesktop.DBus.Properties", "GetAll",
+                    "xyz.openbmc_project.Configuration.PSUPresence");
+            if (ec)
+            {
+                phosphor::logging::log<phosphor::logging::level::ERR>(
+                    "Failed to set dbus property to cold redundancy");
+                return -1;
+            }
+            auto psuBus = std::get_if<uint64_t>(&propMap["Bus"]);
+            auto psuAddress =
+                std::get_if<std::vector<uint64_t>>(&propMap["Address"]);
+
+            if (psuBus == nullptr || psuAddress == nullptr)
+            {
+                std::cerr << "error finding necessary "
+                             "entry in configuration\n";
+                return -1;
+            }
+            bus = static_cast<uint8_t>(*psuBus);
+            addrTable = *psuAddress;
+            return 0;
+        }
+    }
+    return -1;
+}
+
+static const constexpr uint8_t addrOffset = 8;
+static const constexpr uint8_t psuRevision = 0xd9;
+static const constexpr uint8_t defaultPSUBus = 7;
+// Second Minor, Primary Minor, Major
+static const constexpr size_t verLen = 3;
+ipmi::RspType<std::vector<uint8_t>> ipmiOEMGetPSUVersion(ipmi::Context::ptr ctx)
+{
+    uint8_t bus = defaultPSUBus;
+    std::vector<uint64_t> addrTable;
+    std::vector<uint8_t> result;
+    if (getPSUAddress(ctx, bus, addrTable))
+    {
+        std::cerr << "Failed to get PSU bus and address\n";
+        return ipmi::responseResponseError();
+    }
+
+    for (const auto& slaveAddr : addrTable)
+    {
+        std::vector<uint8_t> writeData = {psuRevision};
+        std::vector<uint8_t> readBuf(verLen);
+        uint8_t addr = static_cast<uint8_t>(slaveAddr) + addrOffset;
+        std::string i2cBus = "/dev/i2c-" + std::to_string(bus);
+
+        auto retI2C = ipmi::i2cWriteRead(i2cBus, addr, writeData, readBuf);
+        if (retI2C != ipmi::ccSuccess)
+        {
+            for (size_t idx = 0; idx < verLen; idx++)
+            {
+                result.emplace_back(0x00);
+            }
+        }
+        else
+        {
+            for (const uint8_t& data : readBuf)
+            {
+                result.emplace_back(data);
+            }
+        }
+    }
+
+    return ipmi::responseSuccess(result);
+}
+
 static void registerOEMFunctions(void)
 {
     phosphor::logging::log<phosphor::logging::level::INFO>(
@@ -3429,6 +3532,10 @@
 
     registerHandler(prioOemBase, netFnChassis, chassis::cmdSetSystemBootOptions,
                     Privilege::Operator, ipmiOemSetBootOptions);
+
+    registerHandler(prioOemBase, intel::netFnGeneral,
+                    intel::general::cmdGetPSUVersion, Privilege::User,
+                    ipmiOEMGetPSUVersion);
 }
 
 } // namespace ipmi