intel-ipmi-oem: add slot I2C master RW command

This command is used to perform i2c master read/write on PCIE slots.

Tested:
Reading the 0x4e device on slot 2 of riser 1(i2c bus is 17),
the below two commands have the same result:

ipmitool raw 0x3e 0x52 0 0x11 0x4e 8 0
i2cget -f -y 17 0x4e 0 i 8

Signed-off-by: Yong Li <yong.b.li@linux.intel.com>
Change-Id: I03872296345d180571db2205a9a30c08f28b69b6
diff --git a/include/oemcommands.hpp b/include/oemcommands.hpp
index 6852aa9..7e55038 100644
--- a/include/oemcommands.hpp
+++ b/include/oemcommands.hpp
@@ -30,6 +30,7 @@
     cmdDisableBMCSystemReset = 0x42,
     cmdGetBMCResetDisables = 0x43,
     cmdSendEmbeddedFWUpdStatus = 0x44,
+    cmdSlotI2CMasterWriteRead = 0x52,
     cmdSetPowerRestoreDelay = 0x54,
     cmdGetPowerRestoreDelay = 0x55,
     cmdSetFaultIndication = 0x57,
diff --git a/src/manufacturingcommands.cpp b/src/manufacturingcommands.cpp
index a3fcab1..c7ab5cf 100644
--- a/src/manufacturingcommands.cpp
+++ b/src/manufacturingcommands.cpp
@@ -15,6 +15,7 @@
 */
 
 #include <boost/container/flat_map.hpp>
+#include <filesystem>
 #include <fstream>
 #include <ipmid/api.hpp>
 #include <manufacturingcommands.hpp>
@@ -29,6 +30,10 @@
     std::chrono::duration_cast<std::chrono::microseconds>(
         std::chrono::seconds(60)); // 1 minute timeout
 
+static constexpr uint8_t slotAddressTypeBus = 0;
+static constexpr uint8_t slotAddressTypeUniqueid = 1;
+static constexpr uint8_t slotI2CMaxReadSize = 35;
+
 static constexpr const char* callbackMgrService =
     "xyz.openbmc_project.CallbackManager";
 static constexpr const char* callbackMgrIntf =
@@ -676,6 +681,92 @@
     return ipmi::responseSuccess(validData, ethData);
 }
 
+/** @brief implements slot master write read IPMI command which can be used for
+ * low-level I2C/SMBus write, read or write-read access for PCIE slots
+ * @param reserved - skip 6 bit
+ * @param addressType - address type
+ * @param bbSlotNum - baseboard slot number
+ * @param riserSlotNum - riser slot number
+ * @param reserved2 - skip 2 bit
+ * @param slaveAddr - slave address
+ * @param readCount - number of bytes to be read
+ * @param writeData - data to be written
+ *
+ * @returns IPMI completion code plus response data
+ */
+ipmi::RspType<std::vector<uint8_t>>
+    appSlotI2CMasterWriteRead(uint6_t reserved, uint2_t addressType,
+                              uint3_t bbSlotNum, uint3_t riserSlotNum,
+                              uint2_t resvered2, uint8_t slaveAddr,
+                              uint8_t readCount, std::vector<uint8_t> writeData)
+{
+    const size_t writeCount = writeData.size();
+    std::string i2cBus;
+    if (addressType == slotAddressTypeBus)
+    {
+        std::string path = "/dev/i2c-mux/Riser_" +
+                           std::to_string(static_cast<uint8_t>(bbSlotNum)) +
+                           "_Mux/Pcie_Slot_" +
+                           std::to_string(static_cast<uint8_t>(riserSlotNum));
+
+        if (std::filesystem::exists(path) && std::filesystem::is_symlink(path))
+        {
+            i2cBus = std::filesystem::read_symlink(path);
+        }
+        else
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(
+                "Master write read command: Cannot get BusID");
+            return ipmi::responseInvalidFieldRequest();
+        }
+    }
+    else if (addressType == slotAddressTypeUniqueid)
+    {
+        i2cBus = "/dev/i2c-" +
+                 std::to_string(static_cast<uint8_t>(bbSlotNum) |
+                                (static_cast<uint8_t>(riserSlotNum) << 3));
+    }
+    else
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Master write read command: invalid request");
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    // Allow single byte write as it is offset byte to read the data, rest allow
+    // only in MFG mode.
+    if (writeCount > 1)
+    {
+        if (mtm.getAccessLvl() < MtmLvl::mtmAvailable)
+        {
+            return ipmi::responseInsufficientPrivilege();
+        }
+    }
+
+    if (readCount > slotI2CMaxReadSize)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Master write read command: Read count exceeds limit");
+        return ipmi::responseParmOutOfRange();
+    }
+
+    if (!readCount && !writeCount)
+    {
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Master write read command: Read & write count are 0");
+        return ipmi::responseInvalidFieldRequest();
+    }
+
+    std::vector<uint8_t> readBuf(readCount);
+
+    ipmi::Cc retI2C = ipmi::i2cWriteRead(i2cBus, slaveAddr, writeData, readBuf);
+    if (retI2C != ipmi::ccSuccess)
+    {
+        return ipmi::response(retI2C);
+    }
+
+    return ipmi::responseSuccess(readBuf);
+}
 } // namespace ipmi
 
 void register_mtm_commands() __attribute__((constructor));
@@ -709,6 +800,12 @@
             IPMINetfnIntelOEMGeneralCmd::cmdGetManufacturingData),
         ipmi::Privilege::Admin, ipmi::getManufacturingData);
 
+    ipmi::registerHandler(
+        ipmi::prioOemBase, netfunIntelAppOEM,
+        static_cast<ipmi_cmd_t>(
+            IPMINetfnIntelOEMGeneralCmd::cmdSlotI2CMasterWriteRead),
+        ipmi::Privilege::Admin, ipmi::appSlotI2CMasterWriteRead);
+
     ipmi::registerFilter(ipmi::netFnOemOne,
                          [](ipmi::message::Request::ptr request) {
                              return ipmi::mfgFilterMessage(request);