oemcommands: Implement OEM Get Reading command

Newly implement OEM Get Reading command API. It implements for
"Inlet Air Temperature" Reading type.

Tested:
Verified on single node system using Oem get reading commands.
Command:  ipmitool raw 0x30 0xe2 0x00 0x00 0x00 //OEM Get reading
Response: Unable to send RAW command (channel=0x0 netfn=0x30 lun=0x0
          cmd=0xe2 rsp=0xc9): Parameter out of range
Command:  ipmitool raw 0x30 0xe2 0x10 0x0 0x0 //OEM Get reading
Response: 10 1a 00
Command:  ipmitool raw 0x30 0xe2 0x20 0x0 0x0 //OEM Get reading
Response: Unable to send RAW command (channel=0x0 netfn=0x30 lun=0x0
          cmd=0xe2 rsp=0xc9): Parameter out of range
Command:  ipmitool raw 0x30 0xe2 0x30 0x0 0x0
Response: Unable to send RAW command (channel=0x0 netfn=0x30 lun=0x0
          cmd=0xe2 rsp=0xcc): Invalid data field in request

Signed-off-by: srikanta mondal <srikantax.mondal@intel.com>
Change-Id: I6450fe95b4ef52eed4784561180d1da667d30360
diff --git a/src/oemcommands.cpp b/src/oemcommands.cpp
index f532884..78f4f82 100644
--- a/src/oemcommands.cpp
+++ b/src/oemcommands.cpp
@@ -69,6 +69,10 @@
 static constexpr const char* oemNmiEnabledObjPathProp = "Enabled";
 
 static constexpr const char* dimmOffsetFile = "/var/lib/ipmi/ipmi_dimms.json";
+static constexpr const char* multiNodeObjPath =
+    "/xyz/openbmc_project/MultiNode/Status";
+static constexpr const char* multiNodeIntf =
+    "xyz.openbmc_project.Chassis.MultiNode";
 
 enum class NmiSource : uint8_t
 {
@@ -3535,6 +3539,111 @@
     return ipmi::responseSuccess(result);
 }
 
+std::optional<uint8_t> getMultiNodeInfoPresence(ipmi::Context::ptr ctx,
+                                                const std::string& name)
+{
+    Value dbusValue = 0;
+    std::string serviceName;
+
+    boost::system::error_code ec =
+        ipmi::getService(ctx, multiNodeIntf, multiNodeObjPath, serviceName);
+
+    if (ec)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Failed to perform Multinode getService.");
+        return std::nullopt;
+    }
+
+    ec = ipmi::getDbusProperty(ctx, serviceName, multiNodeObjPath,
+                               multiNodeIntf, name, dbusValue);
+    if (ec)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Failed to perform Multinode get property");
+        return std::nullopt;
+    }
+
+    auto multiNodeVal = std::get_if<uint8_t>(&dbusValue);
+    if (!multiNodeVal)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "getMultiNodeInfoPresence: error to get multinode");
+        return std::nullopt;
+    }
+    return *multiNodeVal;
+}
+
+/** @brief implements OEM get reading command
+ *  @param domain ID
+ *  @param reading Type
+ *    - 00h = platform Power Consumption
+ *    - 01h = inlet Air Temp
+ *    - 02h = icc_TDC from PECI
+ *  @param reserved, write as 0000h
+ *
+ *  @returns IPMI completion code plus response data
+ *  - response
+ *     - domain ID
+ *     - reading Type
+ *       - 00h = platform Power Consumption
+ *       - 01h = inlet Air Temp
+ *       - 02h = icc_TDC from PECI
+ *     - reading
+ */
+ipmi::RspType<uint4_t, // domain ID
+              uint4_t, // reading Type
+              uint16_t // reading Value
+              >
+    ipmiOEMGetReading(ipmi::Context::ptr ctx, uint4_t domainId,
+                      uint4_t readingType, uint16_t reserved)
+{
+    constexpr uint8_t platformPower = 0;
+    constexpr uint8_t inletAirTemp = 1;
+    constexpr uint8_t iccTdc = 2;
+
+    if ((static_cast<uint8_t>(readingType) > iccTdc) || domainId || reserved)
+    {
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // This command should run only from multi-node product.
+    // For all other platforms this command will return invalid.
+
+    std::optional<uint8_t> nodeInfo =
+        getMultiNodeInfoPresence(ctx, "NodePresence");
+    if (!nodeInfo || !*nodeInfo)
+    {
+        return ipmi::responseInvalidCommand();
+    }
+
+    uint16_t oemReadingValue = 0;
+    if (static_cast<uint8_t>(readingType) == inletAirTemp)
+    {
+        double value = 0;
+        boost::system::error_code ec = ipmi::getDbusProperty(
+            ctx, "xyz.openbmc_project.HwmonTempSensor",
+            "/xyz/openbmc_project/sensors/temperature/Inlet_BRD_Temp",
+            "xyz.openbmc_project.Sensor.Value", "Value", value);
+        if (ec)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Failed to get BMC Get OEM temperature",
+                phosphor::logging::entry("EXCEPTION=%s", ec.message().c_str()));
+            return ipmi::responseUnspecifiedError();
+        }
+        // Take the Inlet temperature
+        oemReadingValue = static_cast<uint16_t>(value);
+    }
+    else
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Currently Get OEM Reading support only for Inlet Air Temp");
+        return ipmi::responseParmOutOfRange();
+    }
+    return ipmi::responseSuccess(domainId, readingType, oemReadingValue);
+}
+
 /** @brief implements the maximum size of
  *  bridgeable messages used between KCS and
  *  IPMB interfacesget security mode command.
@@ -3720,6 +3829,10 @@
     registerHandler(prioOemBase, intel::netFnGeneral,
                     intel::general::cmdGetBufferSize, Privilege::User,
                     ipmiOEMGetBufferSize);
+
+    registerHandler(prioOemBase, intel::netFnGeneral,
+                    intel::general::cmdOEMGetReading, Privilege::User,
+                    ipmiOEMGetReading);
 }
 
 } // namespace ipmi