ConnectedCalloutResolution support

Signed-off-by: Zane Shelley <zshelle@us.ibm.com>
Change-Id: Id1b215d70ca98ae18528f91162ff4d99de9e9ce8
diff --git a/analyzer/callout.hpp b/analyzer/callout.hpp
index 51cf4ed..b49f97b 100644
--- a/analyzer/callout.hpp
+++ b/analyzer/callout.hpp
@@ -110,6 +110,44 @@
 
 inline const Procedure Procedure::NEXTLVL{"NEXTLVL"};
 
+/** @brief Container class for bus callout service actions. */
+class BusType
+{
+  public:
+    /** SMP bus (fabric A or X bus). */
+    static const BusType SMP_BUS;
+
+    /** OMI bus (memory bus). */
+    static const BusType OMI_BUS;
+
+  private:
+    /**
+     * @brief Constructor from components.
+     * @param i_string The string representation of the procedure used for
+     *                 callouts.
+     */
+    explicit BusType(const std::string& i_string) : iv_string(i_string) {}
+
+  private:
+    /** The string representation of the procedure used for callouts. */
+    const std::string iv_string;
+
+  public:
+    bool operator==(const BusType& r) const
+    {
+        return this->iv_string == r.iv_string;
+    }
+
+    /** iv_string accessor */
+    std::string getString() const
+    {
+        return iv_string;
+    }
+};
+
+inline const BusType BusType::SMP_BUS{"SMP_BUS"};
+inline const BusType BusType::OMI_BUS{"OMI_BUS"};
+
 /** @brief Container class for clock callout service actions. */
 class ClockType
 {
diff --git a/analyzer/ras-data/data/ras-data-explorer-11.json b/analyzer/ras-data/data/ras-data-explorer-11.json
index 8c835b4..37a216e 100644
--- a/analyzer/ras-data/data/ras-data-explorer-11.json
+++ b/analyzer/ras-data/data/ras-data-explorer-11.json
@@ -7,7 +7,7 @@
    },
    "buses" : {
        "omi_bus": {
-           "type": "parent"
+           "type": "OMI_BUS"
        }
    },
    "actions" : {
diff --git a/analyzer/ras-data/data/ras-data-explorer-20.json b/analyzer/ras-data/data/ras-data-explorer-20.json
index 2bee510..1d99c6a 100644
--- a/analyzer/ras-data/data/ras-data-explorer-20.json
+++ b/analyzer/ras-data/data/ras-data-explorer-20.json
@@ -7,7 +7,7 @@
    },
    "buses" : {
        "omi_bus": {
-           "type": "parent"
+           "type": "OMI_BUS"
        }
    },
    "actions" : {
diff --git a/analyzer/ras-data/ras-data-definition.md b/analyzer/ras-data/ras-data-definition.md
index 2538230..b7846d7 100644
--- a/analyzer/ras-data/ras-data-definition.md
+++ b/analyzer/ras-data/ras-data-definition.md
@@ -58,7 +58,7 @@
 
 | Keyword | Description                                                        |
 |---------|--------------------------------------------------------------------|
-| type    | The bus connection type. Values (string): `peer`, `parent`, `child`|
+| type    | The bus connection type. Values (string): `SMP_BUS` and `OMI_BUS`  |
 | unit    | Optional. The `<unit_name>` of the bus endpoint on this chip.      |
 
 ## 5) `actions` keyword (required)
diff --git a/analyzer/ras-data/ras-data-parser.cpp b/analyzer/ras-data/ras-data-parser.cpp
index d59c289..ea59c07 100644
--- a/analyzer/ras-data/ras-data-parser.cpp
+++ b/analyzer/ras-data/ras-data-parser.cpp
@@ -118,6 +118,33 @@
 
 //------------------------------------------------------------------------------
 
+std::tuple<callout::BusType, std::string>
+    RasDataParser::parseBus(const nlohmann::json& i_data,
+                            const std::string& i_name)
+{
+    auto bus = i_data.at("buses").at(i_name);
+
+    // clang-format off
+    static const std::map<std::string, callout::BusType> m =
+    {
+        {"SMP_BUS", callout::BusType::SMP_BUS},
+        {"OMI_BUS", callout::BusType::OMI_BUS},
+    };
+    // clang-format on
+
+    auto busType = m.at(bus.at("type").get<std::string>());
+
+    std::string unitPath{}; // default empty if unit does not exist
+    if (bus.contains("unit"))
+    {
+        unitPath = bus.at("unit").get<std::string>();
+    }
+
+    return std::make_tuple(busType, unitPath);
+}
+
+//------------------------------------------------------------------------------
+
 std::shared_ptr<Resolution>
     RasDataParser::parseAction(const nlohmann::json& i_data,
                                const std::string& i_action)
@@ -168,9 +195,11 @@
             auto priority = a.at("priority").get<std::string>();
             auto guard    = a.at("guard").get<bool>();
 
-            // TODO
-            trace::inf("callout_connected: name=%s priority=%s guard=%c",
-                       name.c_str(), priority.c_str(), guard ? 'T' : 'F');
+            auto busData = parseBus(i_data, name);
+
+            o_list->push(std::make_shared<ConnectedCalloutResolution>(
+                std::get<0>(busData), std::get<1>(busData),
+                getPriority(priority), guard));
         }
         else if ("callout_bus" == type)
         {
diff --git a/analyzer/ras-data/ras-data-parser.hpp b/analyzer/ras-data/ras-data-parser.hpp
index fe5e4a1..60d9ab7 100644
--- a/analyzer/ras-data/ras-data-parser.hpp
+++ b/analyzer/ras-data/ras-data-parser.hpp
@@ -54,6 +54,17 @@
                                const libhei::Signature& i_signature);
 
     /**
+     * @brief  Parses a bus object in the given data file and returns the bus
+     *         type and unit path.
+     * @param  i_data The parsed RAS data file associated with the signature's
+     *                chip type.
+     * @param  i_name The name of the target bus.
+     * @return A tuple containing the bus type and unit path.
+     */
+    std::tuple<callout::BusType, std::string>
+        parseBus(const nlohmann::json& i_data, const std::string& i_name);
+
+    /**
      * @brief  Parses an action in the given data file and returns the
      *         corresponding resolution.
      * @param  i_data   The parsed RAS data file associated with the signature's
diff --git a/analyzer/ras-data/schema/ras-data-schema-v01.json b/analyzer/ras-data/schema/ras-data-schema-v01.json
index 9ec7150..d675631 100644
--- a/analyzer/ras-data/schema/ras-data-schema-v01.json
+++ b/analyzer/ras-data/schema/ras-data-schema-v01.json
@@ -48,7 +48,7 @@
                     "properties": {
                         "type": {
                             "type": "string",
-                            "enum": [ "peer", "parent", "child" ]
+                            "enum": [ "SMP_BUS", "OMI_BUS" ]
                         },
                         "unit": {
                             "type": "string",
diff --git a/analyzer/resolution.cpp b/analyzer/resolution.cpp
index fb451aa..d6cd174 100644
--- a/analyzer/resolution.cpp
+++ b/analyzer/resolution.cpp
@@ -44,6 +44,76 @@
 
 //------------------------------------------------------------------------------
 
+// Helper function to get the connected target on the other side of the
+// given bus.
+pdbg_target* __getConnectedTarget(pdbg_target* i_rxTarget,
+                                  const callout::BusType& i_busType)
+{
+    assert(nullptr != i_rxTarget);
+
+    pdbg_target* txTarget = nullptr;
+
+    auto rxType        = util::pdbg::getTrgtType(i_rxTarget);
+    std::string rxPath = util::pdbg::getPath(i_rxTarget);
+
+    if (callout::BusType::SMP_BUS == i_busType &&
+        util::pdbg::TYPE_IOLINK == rxType)
+    {
+        // TODO: Will need to reference some sort of data that can tell us how
+        //       the processors are connected in the system. For now, return the
+        //       RX target to avoid returning a nullptr.
+        trace::inf("No support to get peer target on SMP bus");
+        txTarget = i_rxTarget;
+    }
+    else if (callout::BusType::OMI_BUS == i_busType &&
+             util::pdbg::TYPE_OMI == rxType)
+    {
+        // This is a bit clunky. The pdbg APIs only give us the ability to
+        // iterate over the children instead of just returning a list. So we'll
+        // push all the children to a list and go from there.
+        std::vector<pdbg_target*> childList;
+
+        pdbg_target* childTarget = nullptr;
+        pdbg_for_each_target("ocmb", i_rxTarget, childTarget)
+        {
+            if (nullptr != childTarget)
+            {
+                childList.push_back(childTarget);
+            }
+        }
+
+        // We know there should only be one OCMB per OMI.
+        if (1 != childList.size())
+        {
+            throw std::logic_error("Invalid child list size for " + rxPath);
+        }
+
+        // Get the connected target.
+        txTarget = childList.front();
+    }
+    else if (callout::BusType::OMI_BUS == i_busType &&
+             util::pdbg::TYPE_OCMB == rxType)
+    {
+        txTarget = pdbg_target_parent("omi", i_rxTarget);
+        if (nullptr == txTarget)
+        {
+            throw std::logic_error("No parent OMI found for " + rxPath);
+        }
+    }
+    else
+    {
+        // This would be a code bug.
+        throw std::logic_error("Unsupported config: i_rxTarget=" + rxPath +
+                               " i_busType=" + i_busType.getString());
+    }
+
+    assert(nullptr != txTarget); // just in case we missed something above
+
+    return txTarget;
+}
+
+//------------------------------------------------------------------------------
+
 void HardwareCalloutResolution::resolve(ServiceData& io_sd) const
 {
     // Get the target for the hardware callout.
@@ -73,6 +143,39 @@
 
 //------------------------------------------------------------------------------
 
+void ConnectedCalloutResolution::resolve(ServiceData& io_sd) const
+{
+    // Get the chip target from the root cause signature.
+    auto chipTarget = __getRootCauseChipTarget(io_sd);
+
+    // Get the endpoint target for the receiving side of the bus.
+    auto rxTarget = __getUnitTarget(chipTarget, iv_unitPath);
+
+    // Get the endpoint target for the transfer side of the bus.
+    auto txTarget = __getConnectedTarget(rxTarget, iv_busType);
+
+    // Callout the TX endpoint.
+    nlohmann::json txCallout;
+    txCallout["LocationCode"] = util::pdbg::getLocationCode(txTarget);
+    txCallout["Priority"]     = iv_priority.getUserDataString();
+    io_sd.addCallout(txCallout);
+
+    // Guard the TX endpoint.
+    Guard txGuard =
+        io_sd.addGuard(util::pdbg::getPhysDevPath(txTarget), iv_guard);
+
+    // Add the callout FFDC to the service data.
+    nlohmann::json ffdc;
+    ffdc["Callout Type"] = "Connected Callout";
+    ffdc["Bus Type"]     = iv_busType.getString();
+    ffdc["Target"]       = util::pdbg::getPhysDevPath(txTarget);
+    ffdc["Priority"]     = iv_priority.getRegistryString();
+    ffdc["Guard Type"]   = txGuard.getString();
+    io_sd.addCalloutFFDC(ffdc);
+}
+
+//------------------------------------------------------------------------------
+
 void ClockCalloutResolution::resolve(ServiceData& io_sd) const
 {
     // Add the callout to the service data.
diff --git a/analyzer/resolution.hpp b/analyzer/resolution.hpp
index 7c20647..aee3851 100644
--- a/analyzer/resolution.hpp
+++ b/analyzer/resolution.hpp
@@ -57,6 +57,46 @@
     void resolve(ServiceData& io_sd) const override;
 };
 
+/** @brief Resolution to callout a connected chip/target. */
+class ConnectedCalloutResolution : public Resolution
+{
+  public:
+    /**
+     * @brief Constructor from components.
+     * @param i_busType  The bus type.
+     * @param i_unitPath The path of the chip unit that is connected to the
+     *                   other chip. An empty string refers to the chip itself,
+     *                   which generally means this chip is a child of another.
+     * @param i_priority The callout priority.
+     * @param i_guard    The guard type for this callout.
+     */
+    ConnectedCalloutResolution(const callout::BusType& i_busType,
+                               const std::string& i_unitPath,
+                               const callout::Priority& i_priority,
+                               bool i_guard) :
+        iv_busType(i_busType),
+        iv_unitPath(i_unitPath), iv_priority(i_priority), iv_guard(i_guard)
+    {}
+
+  private:
+    /** The bus type. */
+    const callout::BusType iv_busType;
+
+    /** The devtree path the chip unit that is connected to the other chip. An
+     *  empty string refers to the chip itself, which generally means this chip
+     *  is a child of the other chip. */
+    const std::string iv_unitPath;
+
+    /** The callout priority. */
+    const callout::Priority iv_priority;
+
+    /** True, if guard is required. False, otherwise. */
+    const bool iv_guard;
+
+  public:
+    void resolve(ServiceData& io_sd) const override;
+};
+
 /** @brief Resolves a clock callout service event. */
 class ClockCalloutResolution : public Resolution
 {
diff --git a/test/resolution_test.cpp b/test/resolution_test.cpp
index 52242f1..ddca989 100644
--- a/test/resolution_test.cpp
+++ b/test/resolution_test.cpp
@@ -1,6 +1,9 @@
 #include <stdio.h>
 
 #include <analyzer/resolution.hpp>
+#include <util/trace.hpp>
+
+#include <regex>
 
 #include "gtest/gtest.h"
 
@@ -8,9 +11,11 @@
 constexpr auto chip_str = "/proc0";
 
 // Unit paths
-constexpr auto proc_str = "";
-constexpr auto omi_str  = "pib/perv12/mc0/mi0/mcc0/omi0";
-constexpr auto core_str = "pib/perv39/eq7/fc1/core1";
+constexpr auto proc_str   = "";
+constexpr auto iolink_str = "pib/perv24/pauc0/iohs0/smpgroup0";
+constexpr auto omi_str    = "pib/perv12/mc0/mi0/mcc0/omi0";
+constexpr auto ocmb_str   = "pib/perv12/mc0/mi0/mcc0/omi0/ocmb0";
+constexpr auto core_str   = "pib/perv39/eq7/fc1/core1";
 
 // Local implementation of this function.
 namespace analyzer
@@ -44,6 +49,54 @@
 
 //------------------------------------------------------------------------------
 
+// Helper function to get the connected target Path on the other side of the
+// given bus.
+std::tuple<std::string, std::string>
+    __getConnectedPath(const std::string& i_rxPath,
+                       const callout::BusType& i_busType)
+{
+    std::string txUnitPath{};
+    std::string txChipPath{};
+
+    // Need to get the target type from the RX path.
+    const std::regex re{"(/proc0)(.*)/([a-z]+)([0-9]+)"};
+    std::smatch match;
+    std::regex_match(i_rxPath, match, re);
+    assert(5 == match.size());
+    std::string rxType = match[3].str();
+
+    if (callout::BusType::SMP_BUS == i_busType && "smpgroup" == rxType)
+    {
+        // Use the RX unit path on a different processor.
+        txUnitPath = "/proc1" + match[2].str() + "/" + rxType + match[4].str();
+        txChipPath = "/proc1";
+    }
+    else if (callout::BusType::OMI_BUS == i_busType && "omi" == rxType)
+    {
+        // Append the OCMB to the RX path.
+        txUnitPath = i_rxPath + "/ocmb0";
+        txChipPath = txUnitPath;
+    }
+    else if (callout::BusType::OMI_BUS == i_busType && "ocmb" == rxType)
+    {
+        // Strip the OCMB off of the RX path.
+        txUnitPath = match[1].str() + match[2].str();
+        txChipPath = "/proc0";
+    }
+    else
+    {
+        // This would be a code bug.
+        throw std::logic_error("Unsupported config: i_rxTarget=" + i_rxPath +
+                               " i_busType=" + i_busType.getString());
+    }
+
+    assert(!txUnitPath.empty()); // just in case we missed something above
+
+    return std::make_tuple(txUnitPath, txChipPath);
+}
+
+//------------------------------------------------------------------------------
+
 void HardwareCalloutResolution::resolve(ServiceData& io_sd) const
 {
     // Get the location code and entity path for this target.
@@ -70,6 +123,38 @@
 
 //------------------------------------------------------------------------------
 
+void ConnectedCalloutResolution::resolve(ServiceData& io_sd) const
+{
+    // Get the chip target path from the root cause signature.
+    auto chipPath = __getRootCauseChipPath(io_sd);
+
+    // Get the endpoint target path for the receiving side of the bus.
+    auto rxPath = __getUnitPath(chipPath, iv_unitPath);
+
+    // Get the endpoint target path for the transfer side of the bus.
+    auto txPath = __getConnectedPath(rxPath, iv_busType);
+
+    // Callout the TX endpoint.
+    nlohmann::json txCallout;
+    txCallout["LocationCode"] = std::get<1>(txPath);
+    txCallout["Priority"]     = iv_priority.getUserDataString();
+    io_sd.addCallout(txCallout);
+
+    // Guard the TX endpoint.
+    Guard txGuard = io_sd.addGuard(std::get<0>(txPath), iv_guard);
+
+    // Add the callout FFDC to the service data.
+    nlohmann::json ffdc;
+    ffdc["Callout Type"] = "Connected Callout";
+    ffdc["Bus Type"]     = iv_busType.getString();
+    ffdc["Target"]       = std::get<0>(txPath);
+    ffdc["Priority"]     = iv_priority.getRegistryString();
+    ffdc["Guard Type"]   = txGuard.getString();
+    io_sd.addCalloutFFDC(ffdc);
+}
+
+//------------------------------------------------------------------------------
+
 void ProcedureCalloutResolution::resolve(ServiceData& io_sd) const
 {
     // Add the actual callout to the service data.
@@ -252,6 +337,74 @@
     EXPECT_EQ(s, j.dump(4));
 }
 
+TEST(Resolution, ConnectedCallout)
+{
+    auto c1 = std::make_shared<ConnectedCalloutResolution>(
+        callout::BusType::SMP_BUS, iolink_str, callout::Priority::MED_A, true);
+
+    auto c2 = std::make_shared<ConnectedCalloutResolution>(
+        callout::BusType::OMI_BUS, ocmb_str, callout::Priority::MED_B, true);
+
+    auto c3 = std::make_shared<ConnectedCalloutResolution>(
+        callout::BusType::OMI_BUS, omi_str, callout::Priority::MED_C, true);
+
+    libhei::Chip chip{chip_str, 0xdeadbeef};
+    libhei::Signature sig{chip, 0xabcd, 0, 0, libhei::ATTN_TYPE_CHECKSTOP};
+    ServiceData sd{sig, true};
+
+    nlohmann::json j{};
+    std::string s{};
+
+    c1->resolve(sd);
+    c2->resolve(sd);
+    c3->resolve(sd);
+
+    // Callout list
+    j = sd.getCalloutList();
+    s = R"([
+    {
+        "LocationCode": "/proc1",
+        "Priority": "A"
+    },
+    {
+        "LocationCode": "/proc0",
+        "Priority": "B"
+    },
+    {
+        "LocationCode": "/proc0/pib/perv12/mc0/mi0/mcc0/omi0/ocmb0",
+        "Priority": "C"
+    }
+])";
+    EXPECT_EQ(s, j.dump(4));
+
+    // Callout FFDC
+    j = sd.getCalloutFFDC();
+    s = R"([
+    {
+        "Bus Type": "SMP_BUS",
+        "Callout Type": "Connected Callout",
+        "Guard Type": "FATAL",
+        "Priority": "medium_group_A",
+        "Target": "/proc1/pib/perv24/pauc0/iohs0/smpgroup0"
+    },
+    {
+        "Bus Type": "OMI_BUS",
+        "Callout Type": "Connected Callout",
+        "Guard Type": "FATAL",
+        "Priority": "medium_group_B",
+        "Target": "/proc0/pib/perv12/mc0/mi0/mcc0/omi0"
+    },
+    {
+        "Bus Type": "OMI_BUS",
+        "Callout Type": "Connected Callout",
+        "Guard Type": "FATAL",
+        "Priority": "medium_group_C",
+        "Target": "/proc0/pib/perv12/mc0/mi0/mcc0/omi0/ocmb0"
+    }
+])";
+    EXPECT_EQ(s, j.dump(4));
+}
+
 TEST(Resolution, ClockCallout)
 {
     auto c1 = std::make_shared<ClockCalloutResolution>(
diff --git a/util/pdbg.hpp b/util/pdbg.hpp
index 8de19cf..7288ebc 100644
--- a/util/pdbg.hpp
+++ b/util/pdbg.hpp
@@ -21,8 +21,10 @@
 /** Chip target types. */
 enum TargetType_t : uint8_t
 {
-    TYPE_PROC = 0x05,
-    TYPE_OCMB = 0x4b,
+    TYPE_PROC   = 0x05,
+    TYPE_IOLINK = 0x47,
+    TYPE_OMI    = 0x48,
+    TYPE_OCMB   = 0x4b,
 };
 
 /** @return The target associated with the given chip. */