google-ipmi-sys: Add support for PCIE slot to I2C bus mapping

Say the bmc has the following information:
root@xyz:/sys/bus/i2c/devices# ls
0-0070   10-0048  4-0049   5-0062   5-0066   5-0074   i2c-1    i2c-15   i2c-18   i2c-20   i2c-4    i2c-7
1-004e   3-0054   4-004d   5-0063   5-0067   9-004c   i2c-10   i2c-16   i2c-19   i2c-21   i2c-5    i2c-8
1-004f   4-0048   5-0061   5-0065   5-0072   i2c-0    i2c-14   i2c-17   i2c-2    i2c-3    i2c-6    i2c-9
root@xyz:/sys/bus/i2c/devices# cd i2c-18
root@xyz:/sys/bus/i2c/devices/i2c-18# ls
delete_device  i2c-dev        mux_device     name           new_device
of_node        power          subsystem      uevent
root@xyz:/sys/bus/i2c/devices/i2c-18# cd of_node
root@xyz:/sys/bus/i2c/devices/i2c-18/of_node# ls
root@xyz:/sys/bus/i2c/devices/i2c-18/of_node# cat pcie-slot
/pcie-slot/pcie@4root@xyz:/sys/bus/i2c/devices/i2c-18/of_node# cd
root@xyz:~# cd /sys/firmware/devicetree/base
root@xyz:/sys/firmware/devicetree/base# ls
root@xyz:/sys/firmware/devicetree/base# cd pcie-slot/
root@xyz:/sys/firmware/devicetree/base/pcie-slot# ls
name     pcie@1a  pcie@2a  pcie@4  pcie@6
root@xyz:/sys/firmware/devicetree/base/pcie-slot# cd pcie\@4
root@xyz:/sys/firmware/devicetree/base/pcie-slot/pcie@4# ls
name       slot-name
root@xyz:/sys/firmware/devicetree/base/pcie-slot/pcie@4# cat slot-name
slot4root@xyz:/sys/firmware/devicetree/base/pcie-slot/pcie@4#

Then the system can send commands to
1) Get the number of pcie slots.
2) Get information about which i2c bus is routed to which pcie slot.

Tested:
Yes;
1) Sending GetPCIeSlotsCount command; Getting pcie slots count as 4.
root@xyz:~# ipmitool -I dbus raw 0x2e 0x32 0x79 0x2b 0x00 0x04
 79 2b 00 04 04

3) Sending GetPCIeSlotI2cBusMapping command with entry ID 0; expecting success;
received "slot5".
root@xyz:~# ipmitool -I dbus raw 0x2e 0x32 0x79 0x2b 0x00 0x05 0x00
 79 2b 00 05 14 05 73 6c 6f 74 35

4) Sending GetPCIeSlotI2cBusMapping command with entry ID 1; expecting success;
received "slot1".
root@xyz:~# ipmitool -I dbus raw 0x2e 0x32 0x79 0x2b 0x00 0x05 0x01
 79 2b 00 05 0e 05 73 6c 6f 74 31

5) Sending GetPCIeSlotI2cBusMapping command with entry ID 2; expecting success;
received "slot3".
root@xyz:~# ipmitool -I dbus raw 0x2e 0x32 0x79 0x2b 0x00 0x05 0x02
 79 2b 00 05 12 05 73 6c 6f 74 33

6) Sending GetPCIeSlotI2cBusMapping command with entry ID 3; expecting success;
received "slot2".
root@xyz:~# ipmitool -I dbus raw 0x2e 0x32 0x79 0x2b 0x00 0x05 0x03
 79 2b 00 05 10 05 73 6c 6f 74 32

7) Sending GetPCIeSlotI2cBusMapping command with entry ID 4; expecting failure
root@xyz:~# ipmitool -I dbus raw 0x2e 0x32 0x79 0x2b 0x00 0x05 0x04
Unable to send RAW command (channel=0x0 netfn=0x2e lun=0x0 cmd=0x32 rsp=0xc1): Invalid command
root@xyz:~#

Change-Id: Id6e514bb11e8d4a00c615cffea6c2fdb6f5df03e
Signed-off-by: Jaghathiswari Rankappagounder Natarajan <jaghu@google.com>
diff --git a/pcie_i2c.cpp b/pcie_i2c.cpp
new file mode 100644
index 0000000..80d545a
--- /dev/null
+++ b/pcie_i2c.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pcie_i2c.hpp"
+
+#include "main.hpp"
+
+#include <cstdint>
+#include <cstring>
+#include <experimental/filesystem>
+#include <fstream>
+#include <regex>
+#include <sstream>
+#include <string>
+#include <system_error>
+#include <unordered_map>
+
+namespace google
+{
+namespace ipmi
+{
+namespace fs = std::experimental::filesystem;
+
+namespace
+{
+
+#ifndef MAX_IPMI_BUFFER
+#define MAX_IPMI_BUFFER 64
+#endif
+
+std::vector<std::tuple<uint32_t, std::string>> pcie_i2c_map;
+
+std::string read_file(const std::string& file_name)
+{
+    std::ifstream ifs(file_name);
+    std::string file_content;
+    if (!ifs.is_open())
+    {
+        std::fprintf(stderr, "Unable to open file %s.\n", file_name.c_str());
+    }
+    else
+    {
+        if (ifs >> file_content)
+        {
+            // If the last character is a null terminator; remove it.
+            if (!file_content.empty())
+            {
+                char const& back = file_content.back();
+                if (back == '\0')
+                    file_content.pop_back();
+            }
+            return file_content;
+        }
+        else
+        {
+            std::fprintf(stderr, "Unable to read file %s.\n",
+                         file_name.c_str());
+        }
+    }
+    return "";
+}
+
+} // namespace
+
+struct PcieSlotCountRequest
+{
+    uint8_t subcommand;
+} __attribute__((packed));
+
+struct PcieSlotCountReply
+{
+    uint8_t subcommand;
+    uint8_t value;
+} __attribute__((packed));
+
+struct PcieSlotI2cBusMappingRequest
+{
+    uint8_t subcommand;
+    uint8_t entry;
+} __attribute__((packed));
+
+struct PcieSlotI2cBusMappingReply
+{
+    uint8_t subcommand;
+    uint8_t i2c_bus_number;
+    uint8_t pcie_slot_name_len;
+    uint8_t pcie_slot_name[0];
+} __attribute__((packed));
+
+ipmi_ret_t PcieSlotCount(const uint8_t* reqBuf, uint8_t* replyBuf,
+                         size_t* dataLen)
+{
+    if ((*dataLen) < sizeof(struct PcieSlotCountRequest))
+    {
+        std::fprintf(stderr, "Invalid command length: %u\n",
+                     static_cast<uint32_t>(*dataLen));
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    // If there are already entries in the vector, clear them.
+    if (!pcie_i2c_map.empty())
+        pcie_i2c_map.clear();
+
+    // Build a vector with i2c bus to pcie slot mapping.
+    // Iterate through all the devices under "/sys/bus/i2c/devices".
+    for (auto& i2c_dev : fs::directory_iterator("/sys/bus/i2c/devices"))
+    {
+        std::string i2c_dev_path = i2c_dev.path();
+        std::smatch i2c_dev_string_number;
+        std::regex e("(i2c-)(\\d+)");
+        // Check if the device has "i2c-" in its path.
+        if (std::regex_search(i2c_dev_path, i2c_dev_string_number, e))
+        {
+            // Check if the i2c device has "pcie-slot" file under "of-node" dir.
+            std::string pcie_slot_path = i2c_dev_path + "/of_node/pcie-slot";
+            std::string pcie_slot;
+            // Read the "pcie-slot" name from the "pcie-slot" file.
+            pcie_slot = read_file(pcie_slot_path);
+            if (pcie_slot.empty())
+            {
+                continue;
+            }
+            std::string pcie_slot_name;
+            std::string pcie_slot_full_path;
+            // Append the "pcie-slot" name to dts base.
+            pcie_slot_full_path.append("/proc/device-tree");
+            pcie_slot_full_path.append(pcie_slot);
+            // Read the "label" which contains the pcie slot name.
+            pcie_slot_full_path.append("/label");
+            pcie_slot_name = read_file(pcie_slot_full_path);
+            if (pcie_slot_name.empty())
+            {
+                continue;
+            }
+            // Get the i2c bus number from the i2c device path.
+            uint32_t i2c_bus_number = i2c_dev_string_number[2].matched
+                                          ? std::stoi(i2c_dev_string_number[2])
+                                          : 0;
+            // Store the i2c bus number and the pcie slot name in the vector.
+            pcie_i2c_map.push_back(
+                std::make_tuple(i2c_bus_number, pcie_slot_name));
+        }
+    }
+
+    struct PcieSlotCountReply reply;
+    reply.subcommand = SysPcieSlotCount;
+    // Fill the pcie slot count as the number of entries in the vector.
+    reply.value = pcie_i2c_map.size();
+
+    std::memcpy(&replyBuf[0], &reply, sizeof(reply));
+
+    // Return the subcommand and the result.
+    (*dataLen) = sizeof(reply);
+
+    return IPMI_CC_OK;
+}
+
+ipmi_ret_t PcieSlotI2cBusMapping(const uint8_t* reqBuf, uint8_t* replyBuf,
+                                 size_t* dataLen)
+{
+    struct PcieSlotI2cBusMappingRequest request;
+
+    if ((*dataLen) < sizeof(request))
+    {
+        std::fprintf(stderr, "Invalid command length: %u\n",
+                     static_cast<uint32_t>(*dataLen));
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    // If there are no entries in the vector return error.
+    if (pcie_i2c_map.empty())
+    {
+        return IPMI_CC_INVALID_RESERVATION_ID;
+    }
+
+    std::memcpy(&request, &reqBuf[0], sizeof(request));
+
+    // The valid entries range from 0 to N - 1, N being the total number of
+    // entries in the vector.
+    if (request.entry >= pcie_i2c_map.size())
+    {
+        return IPMI_CC_PARM_OUT_OF_RANGE;
+    }
+
+    // Get the i2c bus number and the pcie slot name from the vector.
+    uint32_t i2c_bus_number = std::get<0>(pcie_i2c_map[request.entry]);
+    std::string pcie_slot_name = std::get<1>(pcie_i2c_map[request.entry]);
+
+    int length =
+        sizeof(struct PcieSlotI2cBusMappingReply) + pcie_slot_name.length();
+
+    // TODO (jaghu) : Add a way to dynamically receive the MAX_IPMI_BUFFER
+    // value and change error to IPMI_CC_REQUESTED_TOO_MANY_BYTES.
+    if (length > MAX_IPMI_BUFFER)
+    {
+        std::fprintf(stderr, "Response would overflow response buffer\n");
+        return IPMI_CC_INVALID;
+    }
+
+    auto reply =
+        reinterpret_cast<struct PcieSlotI2cBusMappingReply*>(&replyBuf[0]);
+    reply->subcommand = SysPcieSlotI2cBusMapping;
+    // Copy the i2c bus number and the pcie slot name to the reply struct.
+    reply->i2c_bus_number = i2c_bus_number;
+    reply->pcie_slot_name_len = pcie_slot_name.length();
+    std::memcpy(reply->pcie_slot_name, pcie_slot_name.c_str(),
+                pcie_slot_name.length());
+
+    // Return the subcommand and the result.
+    (*dataLen) = length;
+    return IPMI_CC_OK;
+}
+} // namespace ipmi
+} // namespace google