PEL: Dynamic location code for registry callout

The PEL callout type 'symbolic FRU with trusted location code'
previously had both the symoblic FRU name and the location code
specified in the PEL message registry.  However, there are cases where
the location code is not known until runtime so it cannot be specified
in the registry.

To solve this, create a boolean 'UseInventoryLocCode` key that can be
used in the registry callout entry to specify that the location code to
use for the callout should come from the FRU passed in using the
CALLOUT_INVENTORY_PATH entry in the AdditionalData event log property.
In this case, that FRU will not be added as a normal FRU callout as it
would normally be otherwise.  The registry that uses this must be the
first one in the callout list.

For example:
{
    "Priority": "high",
    "SymbolicFRUTrusted": "air_mover",
    "UseInventoryLocCode": true
},

along with CALLOUT_INVENTORY_PATH=<processor 0 path>

would result in a symbolic FRU callout with the trusted location code
being the processor 0 location code.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: Iaf65c523803662313f2ad5e197262b9f4e722332
diff --git a/extensions/openpower-pels/registry.cpp b/extensions/openpower-pels/registry.cpp
index 22cdc27..c8f10d7 100644
--- a/extensions/openpower-pels/registry.cpp
+++ b/extensions/openpower-pels/registry.cpp
@@ -406,6 +406,7 @@
     RegistryCallout callout;
 
     callout.priority = "high";
+    callout.useInventoryLocCode = false;
 
     if (json.contains("Priority"))
     {
@@ -431,6 +432,11 @@
             json["SymbolicFRUTrusted"].get<std::string>();
     }
 
+    if (json.contains("UseInventoryLocCode"))
+    {
+        callout.useInventoryLocCode = json["UseInventoryLocCode"].get<bool>();
+    }
+
     return callout;
 }
 
diff --git a/extensions/openpower-pels/registry.hpp b/extensions/openpower-pels/registry.hpp
index 673e2c2..7a13b58 100644
--- a/extensions/openpower-pels/registry.hpp
+++ b/extensions/openpower-pels/registry.hpp
@@ -195,6 +195,7 @@
     std::string procedure;
     std::string symbolicFRU;
     std::string symbolicFRUTrusted;
+    bool useInventoryLocCode;
 };
 
 /**
diff --git a/extensions/openpower-pels/registry/README.md b/extensions/openpower-pels/registry/README.md
index f1c90f9..89d4578 100644
--- a/extensions/openpower-pels/registry/README.md
+++ b/extensions/openpower-pels/registry/README.md
@@ -377,6 +377,29 @@
 - Symbolic FRU: symbolic_fru
 - Procedure: maint_procedure
 
+#### Symbolic FRU callouts with dynamic trusted location codes
+
+A special case is when one wants to use a symbolic FRU callout with a trusted
+location code, but the location code to use isn\'t known until runtime. This
+means it can\'t be specified using the 'LocCode' key in the registry.
+
+In this case, one should use the 'SymbolicFRUTrusted' key along with the
+'UseInventoryLocCode' key, and then pass in the inventory item that has the
+desired location code using the 'CALLOUT_INVENTORY_PATH' entry inside of the
+AdditionalData property.  The code will then look up the location code for that
+passed in inventory FRU and place it in the symbolic FRU callout.  The normal
+FRU callout with that inventory item will not be created.  The symbolic FRU
+must be the first callout in the registry for this to work.
+
+```
+{
+
+    "Priority": "high",
+    "SymbolicFRUTrusted": "AIR_MOVR",
+    "UseInventoryLocCode": true
+}
+```
+
 ## Modifying and Testing
 
 The general process for adding new entries to the message registry is:
diff --git a/extensions/openpower-pels/registry/schema/schema.json b/extensions/openpower-pels/registry/schema/schema.json
index 50c2d2d..e122db4 100644
--- a/extensions/openpower-pels/registry/schema/schema.json
+++ b/extensions/openpower-pels/registry/schema/schema.json
@@ -454,6 +454,12 @@
             "enum": ["no_vpd_for_fru", "bmc_code"]
         },
 
+        "useInventoryLocCode":
+        {
+            "description": "Used along with SymbolicFRUTrusted to specify that the location code to use with the symbolic FRU is to be taken from the passed in CALLOUT_INVENTORY_PATH callout rather than being specified with LocCode.",
+            "type": "boolean"
+        },
+
         "calloutList":
         {
             "description": "The list of FRU callouts to add to a PEL.  If just LocCode is specified, it is a normal hardware FRU callout.  If Procedure is specified, it is a procedure callout.  If SymbolicFRU or SymbolicFRUTrusted are specified, it is a Symbolic FRU callout.  SymbolicFRUTrusted also requires LocCode.",
@@ -467,7 +473,8 @@
                     "LocCode": {"$ref": "#/definitions/locationCode" },
                     "SymbolicFRU": {"$ref": "#/definitions/symbolicFRU" },
                     "SymbolicFRUTrusted": {"$ref": "#/definitions/symbolicFRUTrusted" },
-                    "Procedure": {"$ref": "#/definitions/procedure" }
+                    "Procedure": {"$ref": "#/definitions/procedure" },
+                    "UseInventoryLocCode": {"$ref": "#/definitions/useInventoryLocCode" }
                 },
                 "additionalProperties": false,
                 "required": ["Priority"],
@@ -480,7 +487,8 @@
                             { "required": ["LocCode"] },
                             { "not": { "required": ["SymbolicFRU"] }},
                             { "not": { "required": ["SymbolicFRUTrusted"] }},
-                            { "not": { "required": ["Procedure"] }}
+                            { "not": { "required": ["Procedure"] }},
+                            { "not": { "required": ["UseInventoryLocCode"] }}
                         ]
                     },
                     {
@@ -489,16 +497,28 @@
                         [
                             { "required": ["SymbolicFRU"] },
                             { "not": { "required": ["SymbolicFRUTrusted"] }},
-                            { "not": { "required": ["Procedure"] }}
+                            { "not": { "required": ["Procedure"] }},
+                            { "not": { "required": ["UseInventoryLocCode"] }}
                         ]
                     },
 
                     {
                         "allOf":
                         [
-                            { "required": ["SymbolicFRUTrusted", "LocationCode"] },
+                            { "required": ["SymbolicFRUTrusted", "LocCode"] },
                             { "not": { "required": ["SymbolicFRU"] }},
-                            { "not": { "required": ["Procedure"] }}
+                            { "not": { "required": ["Procedure"] }},
+                            { "not": { "required": ["UseInventoryLocCode"] }}
+                        ]
+                    },
+
+                    {
+                        "allOf":
+                        [
+                            { "required": ["SymbolicFRUTrusted", "UseInventoryLocCode"] },
+                            { "not": { "required": ["SymbolicFRU"] }},
+                            { "not": { "required": ["Procedure"] }},
+                            { "not": { "required": ["LocCode"] }}
                         ]
                     },
 
@@ -508,7 +528,8 @@
                             { "required": ["Procedure"] },
                             { "not": { "required": ["SymbolicFRU"] }},
                             { "not": { "required": ["SymbolicFRUTrusted"] }},
-                            { "not": { "required": ["LocCode"] }}
+                            { "not": { "required": ["LocCode"] }},
+                            { "not": { "required": ["UseInventoryLocCode"] }}
                         ]
                     }
                 ]
diff --git a/extensions/openpower-pels/src.cpp b/extensions/openpower-pels/src.cpp
index 0915a24..3fd62e8 100644
--- a/extensions/openpower-pels/src.cpp
+++ b/extensions/openpower-pels/src.cpp
@@ -744,18 +744,28 @@
                       const nlohmann::json& jsonCallouts,
                       const DataInterfaceBase& dataIface)
 {
+    auto registryCallouts =
+        getRegistryCallouts(regEntry, additionalData, dataIface);
+
     auto item = additionalData.getValue("CALLOUT_INVENTORY_PATH");
-    if (item)
+
+    // If the first registry callout says to use the passed in inventory
+    // path to get the location code for a symbolic FRU callout with a
+    // trusted location code, then do not add the inventory path as a
+    // normal FRU callout.
+    bool useInvForSymbolicFRULocCode =
+        !registryCallouts.empty() && registryCallouts[0].useInventoryLocCode &&
+        !registryCallouts[0].symbolicFRUTrusted.empty();
+
+    if (item && !useInvForSymbolicFRULocCode)
     {
         addInventoryCallout(*item, std::nullopt, std::nullopt, dataIface);
     }
 
     addDevicePathCallouts(additionalData, dataIface);
 
-    if (regEntry.callouts)
-    {
-        addRegistryCallouts(regEntry, additionalData, dataIface);
-    }
+    addRegistryCallouts(registryCallouts, dataIface,
+                        (useInvForSymbolicFRULocCode) ? item : std::nullopt);
 
     if (!jsonCallouts.empty())
     {
@@ -822,20 +832,49 @@
     _callouts->addCallout(std::move(callout));
 }
 
-void SRC::addRegistryCallouts(const message::Entry& regEntry,
-                              const AdditionalData& additionalData,
-                              const DataInterfaceBase& dataIface)
+std::vector<message::RegistryCallout>
+    SRC::getRegistryCallouts(const message::Entry& regEntry,
+                             const AdditionalData& additionalData,
+                             const DataInterfaceBase& dataIface)
+{
+    std::vector<message::RegistryCallout> registryCallouts;
+
+    if (regEntry.callouts)
+    {
+        try
+        {
+            auto systemNames = dataIface.getSystemNames();
+
+            registryCallouts = message::Registry::getCallouts(
+                regEntry.callouts.value(), systemNames, additionalData);
+        }
+        catch (const std::exception& e)
+        {
+            addDebugData(fmt::format(
+                "Error parsing PEL message registry callout JSON: {}",
+                e.what()));
+        }
+    }
+
+    return registryCallouts;
+}
+
+void SRC::addRegistryCallouts(
+    const std::vector<message::RegistryCallout>& callouts,
+    const DataInterfaceBase& dataIface,
+    std::optional<std::string> trustedSymbolicFRUInvPath)
 {
     try
     {
-        auto systemNames = dataIface.getSystemNames();
-
-        auto regCallouts = message::Registry::getCallouts(
-            regEntry.callouts.value(), systemNames, additionalData);
-
-        for (const auto& regCallout : regCallouts)
+        for (const auto& callout : callouts)
         {
-            addRegistryCallout(regCallout, dataIface);
+            addRegistryCallout(callout, dataIface, trustedSymbolicFRUInvPath);
+
+            // Only the first callout gets the inventory path
+            if (trustedSymbolicFRUInvPath)
+            {
+                trustedSymbolicFRUInvPath = std::nullopt;
+            }
         }
     }
     catch (std::exception& e)
@@ -846,8 +885,10 @@
     }
 }
 
-void SRC::addRegistryCallout(const message::RegistryCallout& regCallout,
-                             const DataInterfaceBase& dataIface)
+void SRC::addRegistryCallout(
+    const message::RegistryCallout& regCallout,
+    const DataInterfaceBase& dataIface,
+    const std::optional<std::string>& trustedSymbolicFRUInvPath)
 {
     std::unique_ptr<src::Callout> callout;
     auto locCode = regCallout.locCode;
@@ -891,6 +932,22 @@
     {
         // Symbolic FRU with trusted location code callout
 
+        // Use the location code from the inventory path if there is one.
+        if (trustedSymbolicFRUInvPath)
+        {
+            try
+            {
+                locCode = dataIface.getLocationCode(*trustedSymbolicFRUInvPath);
+            }
+            catch (const std::exception& e)
+            {
+                addDebugData(
+                    fmt::format("Could not get location code for {}: {}",
+                                *trustedSymbolicFRUInvPath, e.what()));
+                locCode.clear();
+            }
+        }
+
         // The registry wants it to be trusted, but that requires a valid
         // location code for it to actually be.
         callout = std::make_unique<src::Callout>(
diff --git a/extensions/openpower-pels/src.hpp b/extensions/openpower-pels/src.hpp
index 264fab2..5363864 100644
--- a/extensions/openpower-pels/src.hpp
+++ b/extensions/openpower-pels/src.hpp
@@ -414,24 +414,52 @@
                             const std::vector<src::MRU::MRUCallout>& mrus = {});
 
     /**
-     * @brief Adds FRU callouts based on the registry entry JSON
-     *       for this error.
+     * @brief Returns the callouts to use from the registry entry.
+     *
      * @param[in] regEntry - The message registry entry for the error
-     * @param[in] additionalData - The AdditionalData values
+     * @param[in] additionalData - The AdditionalData property
      * @param[in] dataIface - The DataInterface object
      */
-    void addRegistryCallouts(const message::Entry& regEntry,
-                             const AdditionalData& additionalData,
-                             const DataInterfaceBase& dataIface);
+    std::vector<message::RegistryCallout>
+        getRegistryCallouts(const message::Entry& regEntry,
+                            const AdditionalData& additionalData,
+                            const DataInterfaceBase& dataIface);
+
+    /**
+     * @brief Adds the FRU callouts from the list of registry callouts
+     *        passed in to the SRC.
+     *
+     * The last parameter is used only in a special case when the first
+     * callout is a symbolic FRU with a trusted location code.  See the
+     * addRegistryCallout documentation.
+     *
+     * @param[in] callouts - The message registry callouts to add
+     * @param[in] dataIface - The DataInterface object
+     * @param[in] trustedSymbolicFRUInvPath - The optional inventory path used
+     *                                        in the symbolic FRU case.
+     */
+    void addRegistryCallouts(
+        const std::vector<message::RegistryCallout>& callouts,
+        const DataInterfaceBase& dataIface,
+        std::optional<std::string> trustedSymbolicFRUInvPath);
 
     /**
      * @brief Adds a single FRU callout from the message registry.
      *
+     * If the last parameter is filled in, and the registry callout is a
+     * symbolic FRU callout with a trusted location code, and it has the
+     * 'useInventoryLocCode' member set to true, then the location code of
+     * that inventory item will be what is used for that trusted location code.
+     *
      * @param[in] callout - The registry callout structure
      * @param[in] dataIface - The DataInterface object
+     * @param[in] trustedSymbolicFRUInvPath - The optional inventory path used
+     *                                        in the symbolic FRU case.
      */
-    void addRegistryCallout(const message::RegistryCallout& callout,
-                            const DataInterfaceBase& dataIface);
+    void addRegistryCallout(
+        const message::RegistryCallout& callout,
+        const DataInterfaceBase& dataIface,
+        const std::optional<std::string>& trustedSymbolicFRUInvPath);
 
     /**
      * @brief Creates the Callouts object _callouts
diff --git a/test/openpower-pels/registry_test.cpp b/test/openpower-pels/registry_test.cpp
index fa1aa0c..08eefc4 100644
--- a/test/openpower-pels/registry_test.cpp
+++ b/test/openpower-pels/registry_test.cpp
@@ -384,6 +384,11 @@
                 {
                     "Priority": "low",
                     "SymbolicFRU": "service_docs"
+                },
+                {
+                    "Priority": "low",
+                    "SymbolicFRUTrusted": "air_mover",
+                    "UseInventoryLocCode": true
                 }
             ]
         },
@@ -408,7 +413,7 @@
         names.push_back("system1");
 
         auto callouts = Registry::getCallouts(json, names, ad);
-        EXPECT_EQ(callouts.size(), 3);
+        EXPECT_EQ(callouts.size(), 4);
         EXPECT_EQ(callouts[0].priority, "high");
         EXPECT_EQ(callouts[0].locCode, "P1-C1");
         EXPECT_EQ(callouts[0].procedure, "");
@@ -424,6 +429,12 @@
         EXPECT_EQ(callouts[2].procedure, "");
         EXPECT_EQ(callouts[2].symbolicFRU, "service_docs");
         EXPECT_EQ(callouts[2].symbolicFRUTrusted, "");
+        EXPECT_EQ(callouts[3].priority, "low");
+        EXPECT_EQ(callouts[3].locCode, "");
+        EXPECT_EQ(callouts[3].procedure, "");
+        EXPECT_EQ(callouts[3].symbolicFRU, "");
+        EXPECT_EQ(callouts[3].symbolicFRUTrusted, "air_mover");
+        EXPECT_EQ(callouts[3].useInventoryLocCode, true);
 
         // system2 isn't in the JSON, so it will pick the default one
         names[0] = "system2";
@@ -438,6 +449,7 @@
         EXPECT_EQ(callouts[1].procedure, "");
         EXPECT_EQ(callouts[1].symbolicFRU, "");
         EXPECT_EQ(callouts[1].symbolicFRUTrusted, "service_docs");
+        EXPECT_EQ(callouts[1].useInventoryLocCode, false);
     }
 
     // Empty JSON array (treated as an error)
diff --git a/test/openpower-pels/src_test.cpp b/test/openpower-pels/src_test.cpp
index fc80d1d..f439bfc 100644
--- a/test/openpower-pels/src_test.cpp
+++ b/test/openpower-pels/src_test.cpp
@@ -670,6 +670,111 @@
     }
 }
 
+// Test that a symbolic FRU with a trusted location code callout
+// from the registry can get its location from the
+// CALLOUT_INVENTORY_PATH AdditionalData entry.
+TEST_F(SRCTest, SymbolicFRUWithInvPathTest)
+{
+    message::Entry entry;
+    entry.src.type = 0xBD;
+    entry.src.reasonCode = 0xABCD;
+    entry.subsystem = 0x42;
+    entry.src.powerFault = false;
+
+    entry.callouts = R"(
+        [{
+            "CalloutList":
+            [
+                {
+                    "Priority": "high",
+                    "SymbolicFRUTrusted": "service_docs",
+                    "UseInventoryLocCode": true
+                },
+                {
+                    "Priority": "medium",
+                    "LocCode": "P0-C8",
+                    "SymbolicFRUTrusted": "pwrsply"
+                }
+            ]
+        }])"_json;
+
+    {
+        // The location code for the first symbolic FRU callout will
+        // come from this inventory path since UseInventoryLocCode is set.
+        // In this case there will be no normal FRU callout for the motherboard.
+        std::vector<std::string> adData{"CALLOUT_INVENTORY_PATH=motherboard"};
+        AdditionalData ad{adData};
+        NiceMock<MockDataInterface> dataIface;
+        std::vector<std::string> names{"systemA"};
+
+        EXPECT_CALL(dataIface, getSystemNames).WillOnce(ReturnRef(names));
+
+        EXPECT_CALL(dataIface, getLocationCode("motherboard"))
+            .Times(1)
+            .WillOnce(Return("Ufcs-P10"));
+
+        EXPECT_CALL(dataIface, expandLocationCode("P0-C8", 0))
+            .WillOnce(Return("Ufcs-P0-C8"));
+
+        SRC src{entry, ad, dataIface};
+
+        auto& callouts = src.callouts()->callouts();
+        EXPECT_EQ(callouts.size(), 2);
+
+        // The location code for the first symbolic FRU callout with a
+        // trusted location code comes from the motherboard.
+        EXPECT_EQ(callouts[0]->locationCode(), "Ufcs-P10");
+        EXPECT_EQ(callouts[0]->priority(), 'H');
+        auto& fru1 = callouts[0]->fruIdentity();
+        EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
+        EXPECT_EQ(fru1->failingComponentType(),
+                  src::FRUIdentity::symbolicFRUTrustedLocCode);
+
+        // The second trusted symbolic FRU callouts uses the location
+        // code in the registry as usual.
+        EXPECT_EQ(callouts[1]->locationCode(), "Ufcs-P0-C8");
+        EXPECT_EQ(callouts[1]->priority(), 'M');
+        auto& fru2 = callouts[1]->fruIdentity();
+        EXPECT_EQ(fru2->getPN().value(), "PWRSPLY");
+        EXPECT_EQ(fru2->failingComponentType(),
+                  src::FRUIdentity::symbolicFRUTrustedLocCode);
+    }
+
+    {
+        // This time say we want to use the location code from
+        // the inventory, but don't pass it in and the callout should
+        // end up a regular symbolic FRU
+        entry.callouts = R"(
+        [{
+            "CalloutList":
+            [
+                {
+                    "Priority": "high",
+                    "SymbolicFRUTrusted": "service_docs",
+                    "UseInventoryLocCode": true
+                }
+            ]
+        }])"_json;
+
+        AdditionalData ad;
+        NiceMock<MockDataInterface> dataIface;
+        std::vector<std::string> names{"systemA"};
+
+        EXPECT_CALL(dataIface, getSystemNames).WillOnce(ReturnRef(names));
+
+        SRC src{entry, ad, dataIface};
+
+        auto& callouts = src.callouts()->callouts();
+        EXPECT_EQ(callouts.size(), 1);
+
+        EXPECT_EQ(callouts[0]->locationCode(), "");
+        EXPECT_EQ(callouts[0]->priority(), 'H');
+        auto& fru1 = callouts[0]->fruIdentity();
+        EXPECT_EQ(fru1->getPN().value(), "SVCDOCS");
+        EXPECT_EQ(fru1->failingComponentType(), src::FRUIdentity::symbolicFRU);
+    }
+}
+
 // Test looking up device path fails in the callout jSON.
 TEST_F(SRCTest, DevicePathCalloutTest)
 {