PEL: devcallouts: Extract search keys

In order to find the callouts for a device path, the search keys need to
be pulled out of the device path.

The keys are:
* I2C - bus and address
* FSI - link numbers
  - Multiple link hops separated by dashes like "0-1"
* SPI - bus
* FSI-I2C - link numbers and I2C bus and address
* FSI-SPI - link numbers and SPI bus number

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I1f8bf975fb34a61ea3fa2ac5b397afdc73cd799b
diff --git a/extensions/openpower-pels/device_callouts.cpp b/extensions/openpower-pels/device_callouts.cpp
index c0a4749..b343424 100644
--- a/extensions/openpower-pels/device_callouts.cpp
+++ b/extensions/openpower-pels/device_callouts.cpp
@@ -89,6 +89,111 @@
     return nlohmann::json::parse(file);
 }
 
+std::tuple<size_t, uint8_t> getI2CSearchKeys(const std::string& devPath)
+{
+    std::smatch match;
+
+    // Look for i2c-A/A-00BB
+    // where A = bus number and BB = address
+    std::regex regex{"i2c-[0-9]+/([0-9]+)-00([0-9a-f]{2})"};
+
+    regex_search(devPath, match, regex);
+
+    if (match.size() != 3)
+    {
+        std::string msg = "Could not get I2C bus and address from " + devPath;
+        throw std::invalid_argument{msg.c_str()};
+    }
+
+    size_t bus = std::stoul(match[1].str(), nullptr, 0);
+
+    // An I2C bus on a CFAM has everything greater than the 10s digit
+    // as the CFAM number, so strip it off.  Like:
+    //    112 = cfam1 bus 12
+    //    1001 = cfam10 bus 1
+    bus = bus % 100;
+
+    uint8_t address = std::stoul(match[2].str(), nullptr, 16);
+
+    return {bus, address};
+}
+
+std::string getFSISearchKeys(const std::string& devPath)
+{
+    std::string links;
+    std::smatch match;
+    auto search = devPath;
+
+    // Look for slave@XX:
+    // where XX = link number in hex
+    std::regex regex{"slave@([0-9a-f]{2}):"};
+
+    // Find all links in the path and separate them with hyphens.
+    while (regex_search(search, match, regex))
+    {
+        // Convert to an int first to handle a hex number like "0a"
+        // though in reality there won't be more than links 0 - 9.
+        auto linkNum = std::stoul(match[1].str(), nullptr, 16);
+        links += std::to_string(linkNum) + '-';
+
+        search = match.suffix();
+    }
+
+    if (links.empty())
+    {
+        std::string msg = "Could not get FSI links from " + devPath;
+        throw std::invalid_argument{msg.c_str()};
+    }
+
+    // Remove the trailing '-'
+    links.pop_back();
+
+    return links;
+}
+
+std::tuple<std::string, std::tuple<size_t, uint8_t>>
+    getFSII2CSearchKeys(const std::string& devPath)
+{
+    // This combines the FSI and i2C search keys
+
+    auto links = getFSISearchKeys(devPath);
+    auto busAndAddr = getI2CSearchKeys(devPath);
+
+    return {std::move(links), std::move(busAndAddr)};
+}
+
+size_t getSPISearchKeys(const std::string& devPath)
+{
+    std::smatch match;
+
+    // Look for spi_master/spiX/ where X is the SPI bus/port number
+    // Note: This doesn't distinguish between multiple chips on
+    // the same port as no need for it yet.
+    std::regex regex{"spi_master/spi(\\d+)/"};
+
+    regex_search(devPath, match, regex);
+
+    if (match.size() != 2)
+    {
+        std::string msg = "Could not get SPI bus from " + devPath;
+        throw std::invalid_argument{msg.c_str()};
+    }
+
+    size_t port = std::stoul(match[1].str());
+
+    return port;
+}
+
+std::tuple<std::string, size_t> getFSISPISearchKeys(const std::string& devPath)
+{
+
+    // Combine the FSI and SPI search keys.
+    auto links = getFSISearchKeys(devPath);
+    auto bus = getSPISearchKeys(devPath);
+
+    return {std::move(links), std::move(bus)};
+}
+
 std::vector<device_callouts::Callout>
     calloutI2C(size_t i2cBus, uint8_t i2cAddress,
                const nlohmann::json& calloutJSON)
diff --git a/extensions/openpower-pels/device_callouts.hpp b/extensions/openpower-pels/device_callouts.hpp
index 1f559c2..1df504a 100644
--- a/extensions/openpower-pels/device_callouts.hpp
+++ b/extensions/openpower-pels/device_callouts.hpp
@@ -166,5 +166,69 @@
  */
 CalloutType getCalloutType(const std::string& devPath);
 
+/**
+ * @brief Pulls the fields out of the I2C device path to use as search keys
+ *        in the JSON.
+ *
+ * The keys are the I2C bus and address.
+ *
+ * @param[in] devPath - The device path
+ *
+ * @return std::tuple<size_t, uint8_t> - The I2C bus and address keys
+ */
+std::tuple<size_t, uint8_t> getI2CSearchKeys(const std::string& devPath);
+
+/**
+ * @brief Pulls the fields out of the FSI device path to use as search keys
+ *        in the JSON.
+ *
+ * The key is the FSI link.  For multi-hop paths, the links are
+ * separated by '-'s, like "0-1-2".
+ *
+ * @param[in] devPath - The device path
+ *
+ * @return std::string - The FSI links key
+ */
+std::string getFSISearchKeys(const std::string& devPath);
+
+/**
+ * @brief Pulls the fields out of the FSI-I2C device path to use as
+ *        search keys in the JSON.
+ *
+ * The keys are the FSI link string and the  I2C bus and address.
+ *
+ * @param[in] devPath - The device path
+ *
+ * @return std::tuple<std::string, std::tuple<size_t, uint8_t>>
+ *         - The FSI links key along with the I2C bus/address.
+ */
+std::tuple<std::string, std::tuple<size_t, uint8_t>>
+    getFSII2CSearchKeys(const std::string& devPath);
+
+/**
+ * @brief Pulls the fields out of the SPI device path to use as search keys
+ *        in the JSON.
+ *
+ * The key is the SPI bus number.
+ *
+ * @param[in] devPath - The device path
+ *
+ * @return size_t - The SPI bus key
+ */
+size_t getSPISearchKeys(const std::string& devPath);
+
+/**
+ * @brief Pulls the fields out of the FSI-SPI device path to use as
+ *        search keys in the JSON.
+ *
+ * The keys are the FSI link string and the SPI bus number.
+ *
+ * @param[in] devPath - The device path
+ *
+ * @return std::tuple<std::string, size_t>
+ *         - The FSI links key along with the SPI bus number.
+ */
+std::tuple<std::string, size_t> getFSISPISearchKeys(const std::string& devPath);
+
 } // namespace util
 } // namespace openpower::pels::device_callouts
diff --git a/test/openpower-pels/device_callouts_test.cpp b/test/openpower-pels/device_callouts_test.cpp
index fd5213d..44c2256 100644
--- a/test/openpower-pels/device_callouts_test.cpp
+++ b/test/openpower-pels/device_callouts_test.cpp
@@ -288,3 +288,115 @@
             util::CalloutType::fsispi);
     }
 }
+
+// Test getting I2C search keys
+TEST_F(DeviceCalloutsTest, getI2CSearchKeysTest)
+{
+
+    {
+        EXPECT_EQ(util::getI2CSearchKeys(
+                      "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/"
+                      "1e78a340.i2c-bus/i2c-10/10-0022"),
+                  (std::tuple{10, 0x22}));
+
+        EXPECT_EQ(util::getI2CSearchKeys(
+                      "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
+                      "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
+                      "slave@01:00/01:01:00:03/i2c-211/211-0055"),
+                  (std::tuple{11, 0x55}));
+    }
+
+    {
+        EXPECT_THROW(util::getI2CSearchKeys("/sys/some/bad/path"),
+                     std::invalid_argument);
+    }
+}
+//
+// Test getting SPI search keys
+TEST_F(DeviceCalloutsTest, getSPISearchKeysTest)
+{
+    {
+        EXPECT_EQ(
+            util::getSPISearchKeys(
+                "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/fsi-master/"
+                "fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/slave@08:00/"
+                "01:03:00:04/spi_master/spi9/spi9.0/spi9.00/nvmem"),
+            9);
+    }
+
+    {
+        EXPECT_THROW(util::getSPISearchKeys("/sys/some/bad/path"),
+                     std::invalid_argument);
+    }
+}
+
+// Test getting FSI search keys
+TEST_F(DeviceCalloutsTest, getFSISearchKeysTest)
+{
+    {
+        EXPECT_EQ(util::getFSISearchKeys(
+                      "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
+                      "fsi-master/fsi0/slave@00:00/00:00:00:04/spi_master/spi2/"
+                      "spi2.0/spi2.00/nvmem"),
+                  "0");
+    }
+
+    {
+        EXPECT_EQ(util::getFSISearchKeys(
+                      "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
+                      "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
+                      "slave@01:00/01:01:00:06/sbefifo2-dev0/occ-hwmon.2"),
+                  "0-1");
+    }
+
+    {
+        EXPECT_EQ(
+            util::getFSISearchKeys(
+                "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
+                "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
+                "slave@01:00/01:01:00:0a:/fsi-master/slave@04:00/01:01:00:0a"),
+            "0-1-4");
+    }
+
+    {
+        EXPECT_THROW(util::getFSISearchKeys("/sys/some/bad/path"),
+                     std::invalid_argument);
+    }
+}
+
+// Test getting FSI-I2C search keys
+TEST_F(DeviceCalloutsTest, getFSII2CSearchKeysTest)
+{
+    {
+        // Link 0-1 bus 11 address 0x55
+        EXPECT_EQ(util::getFSII2CSearchKeys(
+                      "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/"
+                      "fsi-master/fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/"
+                      "slave@01:00/01:01:00:03/i2c-211/211-0055"),
+                  (std::tuple{"0-1", std::tuple{11, 0x55}}));
+    }
+
+    {
+        EXPECT_THROW(util::getFSII2CSearchKeys("/sys/some/bad/path"),
+                     std::invalid_argument);
+    }
+}
+
+// Test getting FSI-SPI search keys
+TEST_F(DeviceCalloutsTest, getFSISPISearchKeysTest)
+{
+    {
+        // Link 0-8 SPI 9
+        EXPECT_EQ(
+            util::getFSISPISearchKeys(
+                "/sys/devices/platform/ahb/ahb:apb/1e79b000.fsi/fsi-master/"
+                "fsi0/slave@00:00/00:00:00:0a/fsi-master/fsi1/slave@08:00/"
+                "01:03:00:04/spi_master/spi9/spi9.0/spi9.00/nvmem"),
+            (std::tuple{"0-8", 9}));
+    }
+
+    {
+        EXPECT_THROW(util::getFSISPISearchKeys("/sys/some/bad/path"),
+                     std::invalid_argument);
+    }
+}