pdr : send up PDRRepositoryChgEvent

Once the BMC has merged entity associations from the host firmware into
its primary PDR repo, send up an event to the host, providing it with
the record handles of the merged entity association PDRs.

Change-Id: I3e6e1d8d1390107f976829ae39f0cc9151447d9c
Signed-off-by: Pavithra Barithaya <pbaritha@in.ibm.com>
Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
diff --git a/host_pdr_handler.cpp b/host_pdr_handler.cpp
index c627b0b..389a492 100644
--- a/host_pdr_handler.cpp
+++ b/host_pdr_handler.cpp
@@ -2,6 +2,8 @@
 
 #include "host_pdr_handler.hpp"
 
+#include <assert.h>
+
 #include <fstream>
 #include <nlohmann/json.hpp>
 
@@ -27,6 +29,8 @@
     fs::path hostFruJson(fs::path(HOST_JSONS_DIR) / fruJson);
     if (fs::exists(hostFruJson))
     {
+        // Note parent entities for entities sent down by the host firmware.
+        // This will enable a merge of entity associations.
         try
         {
             std::ifstream jsonFile(hostFruJson);
@@ -77,6 +81,7 @@
     std::vector<uint8_t> requestMsg(sizeof(pldm_msg_hdr) +
                                     PLDM_GET_PDR_REQ_BYTES);
     auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    bool merged = false;
 
     for (auto recordHandle : pdrRecordHandles)
     {
@@ -108,14 +113,14 @@
             return;
         }
 
-        auto responsePtr =
-            reinterpret_cast<struct pldm_msg*>(responseMsgPtr.get());
         uint8_t completionCode{};
         uint32_t nextRecordHandle{};
         uint32_t nextDataTransferHandle{};
         uint8_t transferFlag{};
         uint16_t respCount{};
         uint8_t transferCRC{};
+        auto responsePtr =
+            reinterpret_cast<struct pldm_msg*>(responseMsgPtr.get());
         rc = decode_get_pdr_resp(
             responsePtr, responseMsgSize - sizeof(pldm_msg_hdr),
             &completionCode, &nextRecordHandle, &nextDataTransferHandle,
@@ -141,10 +146,13 @@
             }
             else
             {
+                // Process the PDR host firmware sent us. The most common action
+                // is to add the PDR to the the BMC's PDR repo.
                 auto pdrHdr = reinterpret_cast<pldm_pdr_hdr*>(pdr.data());
                 if (pdrHdr->type == PLDM_PDR_ENTITY_ASSOCIATION)
                 {
                     mergeEntityAssociations(pdr);
+                    merged = true;
                 }
                 else
                 {
@@ -153,6 +161,15 @@
             }
         }
     }
+
+    if (merged)
+    {
+        // We have merged host's entity association PDRs with our own. Send an
+        // event to the host firmware to indicate the same.
+        sendPDRRepositoryChgEvent(
+            std::move(std::vector<uint8_t>(1, PLDM_PDR_ENTITY_ASSOCIATION)),
+            FORMAT_IS_PDR_HANDLES);
+    }
 }
 
 bool HostPDRHandler::getParent(EntityType type, pldm_entity& parent)
@@ -196,8 +213,106 @@
 
     if (merged)
     {
+        // Update our PDR repo with the merged entity association PDRs
         pldm_entity_association_pdr_add(entityTree, repo, true);
     }
 }
 
+void HostPDRHandler::sendPDRRepositoryChgEvent(std::vector<uint8_t>&& pdrTypes,
+                                               uint8_t eventDataFormat)
+{
+    assert(eventDataFormat == FORMAT_IS_PDR_HANDLES);
+
+    // Extract from the PDR repo record handles of PDRs we want the host
+    // to pull up.
+    std::vector<uint8_t> eventDataOps{PLDM_RECORDS_ADDED};
+    std::vector<uint8_t> numsOfChangeEntries(1);
+    std::vector<std::vector<ChangeEntry>> changeEntries(
+        numsOfChangeEntries.size());
+    for (auto pdrType : pdrTypes)
+    {
+        const pldm_pdr_record* record{};
+        do
+        {
+            record = pldm_pdr_find_record_by_type(repo, pdrType, record,
+                                                  nullptr, nullptr);
+            if (record && pldm_pdr_record_is_remote(record))
+            {
+                changeEntries[0].push_back(
+                    pldm_pdr_get_record_handle(repo, record));
+            }
+        } while (record);
+    }
+    if (changeEntries.empty())
+    {
+        return;
+    }
+    numsOfChangeEntries[0] = changeEntries[0].size();
+
+    // Encode PLDM platform event msg to indicate a PDR repo change.
+    size_t maxSize = PLDM_PDR_REPOSITORY_CHG_EVENT_MIN_LENGTH +
+                     PLDM_PDR_REPOSITORY_CHANGE_RECORD_MIN_LENGTH +
+                     changeEntries[0].size() * sizeof(uint32_t);
+    std::vector<uint8_t> eventDataVec{};
+    eventDataVec.resize(maxSize);
+    auto eventData =
+        reinterpret_cast<struct pldm_pdr_repository_chg_event_data*>(
+            eventDataVec.data());
+    size_t actualSize{};
+    auto firstEntry = changeEntries[0].data();
+    auto rc = encode_pldm_pdr_repository_chg_event_data(
+        eventDataFormat, 1, eventDataOps.data(), numsOfChangeEntries.data(),
+        &firstEntry, eventData, &actualSize, maxSize);
+    if (rc != PLDM_SUCCESS)
+    {
+        std::cerr
+            << "Failed to encode_pldm_pdr_repository_chg_event_data, rc = "
+            << rc << std::endl;
+        return;
+    }
+    auto instanceId = requester.getInstanceId(mctp_eid);
+    std::vector<uint8_t> requestMsg(sizeof(pldm_msg_hdr) +
+                                    PLDM_PLATFORM_EVENT_MESSAGE_MIN_REQ_BYTES +
+                                    actualSize);
+    auto request = reinterpret_cast<pldm_msg*>(requestMsg.data());
+    rc = encode_platform_event_message_req(
+        instanceId, 1, 0, PLDM_PDR_REPOSITORY_CHG_EVENT, eventDataVec.data(),
+        actualSize, request);
+    if (rc != PLDM_SUCCESS)
+    {
+        requester.markFree(mctp_eid, instanceId);
+        std::cerr << "Failed to encode_platform_event_message_req, rc = " << rc
+                  << std::endl;
+        return;
+    }
+
+    // Send up the event to host.
+    uint8_t* responseMsg = nullptr;
+    size_t responseMsgSize{};
+    auto requesterRc =
+        pldm_send_recv(mctp_eid, mctp_fd, requestMsg.data(), requestMsg.size(),
+                       &responseMsg, &responseMsgSize);
+    requester.markFree(mctp_eid, instanceId);
+    if (requesterRc != PLDM_REQUESTER_SUCCESS)
+    {
+        std::cerr << "Failed to send msg to report pdrs, rc = " << requesterRc
+                  << std::endl;
+        return;
+    }
+    uint8_t completionCode{};
+    uint8_t status{};
+    auto responsePtr = reinterpret_cast<struct pldm_msg*>(responseMsg);
+    rc = decode_platform_event_message_resp(
+        responsePtr, responseMsgSize - sizeof(pldm_msg_hdr), &completionCode,
+        &status);
+    free(responseMsg);
+    if (rc != PLDM_SUCCESS || completionCode != PLDM_SUCCESS)
+    {
+        std::cerr << "Failed to decode_platform_event_message_resp: "
+                  << "rc=" << rc
+                  << ", cc=" << static_cast<unsigned>(completionCode)
+                  << std::endl;
+    }
+}
+
 } // namespace pldm
diff --git a/host_pdr_handler.hpp b/host_pdr_handler.hpp
index af1fbab..fec6da6 100644
--- a/host_pdr_handler.hpp
+++ b/host_pdr_handler.hpp
@@ -59,8 +59,18 @@
      *  @param[in] recordHandles - list of record handles pointing to host's
      *             PDRs that need to be fetched.
      */
+
     void fetchPDR(std::vector<uint32_t>&& recordHandles);
 
+    /** @brief Send a PLDM event to host firmware containing a list of record
+     *  handles of PDRs that the host firmware has to fetch.
+     *  @param[in] pdrTypes - list of PDR types that need to be looked up in the
+     *                        BMC repo
+     *  @param[in] eventDataFormat - format for PDRRepositoryChgEvent in DSP0248
+     */
+    void sendPDRRepositoryChgEvent(std::vector<uint8_t>&& pdrTypes,
+                                   uint8_t eventDataFormat);
+
   private:
     /** @brief fetchPDR schedules work on the event loop, this method does the
      *  actual work. This is so that the PDR exchg with the host is async.
diff --git a/libpldm/pdr.c b/libpldm/pdr.c
index 64c314b..e0cd527 100644
--- a/libpldm/pdr.c
+++ b/libpldm/pdr.c
@@ -195,8 +195,6 @@
 			     uint32_t *size)
 {
 	assert(repo != NULL);
-	assert(data != NULL);
-	assert(size != NULL);
 
 	pldm_pdr_record *record = repo->first;
 	if (curr_record != NULL) {
@@ -205,14 +203,18 @@
 	while (record != NULL) {
 		struct pldm_pdr_hdr *hdr = (struct pldm_pdr_hdr *)record->data;
 		if (hdr->type == pdr_type) {
-			*size = record->size;
-			*data = record->data;
+			if (data && size) {
+				*size = record->size;
+				*data = record->data;
+			}
 			return record;
 		}
 		record = record->next;
 	}
 
-	*size = 0;
+	if (size) {
+		*size = 0;
+	}
 	return NULL;
 }
 
diff --git a/libpldm/pdr.h b/libpldm/pdr.h
index 16ceb55..b85bed5 100644
--- a/libpldm/pdr.h
+++ b/libpldm/pdr.h
@@ -125,8 +125,8 @@
  *  @param[in] curr_record - opaque pointer acting as a PDR record handle; if
  *  not NULL, then search will begin from this record's next record
  *  @param[in/out] data - will point to PDR record data (as per DSP0248) on
- *                        return
- *  @param[out] size - *size will be size of PDR record
+ *                        return, if input is not NULL
+ *  @param[out] size - *size will be size of PDR record, if input is not NULL
  *
  *  @return opaque pointer acting as PDR record handle, will be NULL if record
  *  was not found
diff --git a/libpldmresponder/fru_parser.cpp b/libpldmresponder/fru_parser.cpp
index e2c2317..a2e2e92 100644
--- a/libpldmresponder/fru_parser.cpp
+++ b/libpldmresponder/fru_parser.cpp
@@ -94,45 +94,54 @@
             throw InternalFailure();
         }
 
-        auto record = data.value("record_details", emptyJson);
-        auto recordType =
-            static_cast<uint8_t>(record.value("fru_record_type", 0));
-        auto encType =
-            static_cast<uint8_t>(record.value("fru_encoding_type", 0));
-        auto dbusIntfName = record.value("dbus_interface_name", "");
-        auto entries = data.value("fru_fields", emptyJsonList);
-        std::vector<FieldInfo> fieldInfo;
-
-        for (const auto& entry : entries)
+        try
         {
-            auto fieldType =
-                static_cast<uint8_t>(entry.value("fru_field_type", 0));
-            auto dbus = entry.value("dbus", emptyJson);
-            auto interface = dbus.value("interface", "");
-            auto property = dbus.value("property_name", "");
-            auto propType = dbus.value("property_type", "");
-            fieldInfo.emplace_back(
-                std::make_tuple(std::move(interface), std::move(property),
-                                std::move(propType), std::move(fieldType)));
+            auto record = data.value("record_details", emptyJson);
+            auto recordType =
+                static_cast<uint8_t>(record.value("fru_record_type", 0));
+            auto encType =
+                static_cast<uint8_t>(record.value("fru_encoding_type", 0));
+            auto dbusIntfName = record.value("dbus_interface_name", "");
+            auto entries = data.value("fru_fields", emptyJsonList);
+            std::vector<FieldInfo> fieldInfo;
+
+            for (const auto& entry : entries)
+            {
+                auto fieldType =
+                    static_cast<uint8_t>(entry.value("fru_field_type", 0));
+                auto dbus = entry.value("dbus", emptyJson);
+                auto interface = dbus.value("interface", "");
+                auto property = dbus.value("property_name", "");
+                auto propType = dbus.value("property_type", "");
+                fieldInfo.emplace_back(
+                    std::make_tuple(std::move(interface), std::move(property),
+                                    std::move(propType), std::move(fieldType)));
+            }
+
+            FruRecordInfo fruInfo;
+            fruInfo =
+                std::make_tuple(recordType, encType, std::move(fieldInfo));
+
+            auto search = recordMap.find(dbusIntfName);
+
+            // PLDM FRU can have multiple records for the same FRU like General
+            // FRU record and multiple OEM FRU records. If the FRU item
+            // interface name is already in the map, that indicates a record
+            // info is already added for the FRU, so append the new record info
+            // to the same data.
+            if (search != recordMap.end())
+            {
+                search->second.emplace_back(std::move(fruInfo));
+            }
+            else
+            {
+                FruRecordInfos recordInfos{fruInfo};
+                recordMap.emplace(dbusIntfName, recordInfos);
+            }
         }
-
-        FruRecordInfo fruInfo;
-        fruInfo = std::make_tuple(recordType, encType, std::move(fieldInfo));
-
-        auto search = recordMap.find(dbusIntfName);
-
-        // PLDM FRU can have multiple records for the same FRU like General FRU
-        // record and multiple OEM FRU records. If the FRU item interface name
-        // is already in the map, that indicates a record info is already added
-        // for the FRU, so append the new record info to the same data.
-        if (search != recordMap.end())
+        catch (const std::exception& e)
         {
-            search->second.emplace_back(std::move(fruInfo));
-        }
-        else
-        {
-            FruRecordInfos recordInfos{fruInfo};
-            recordMap.emplace(dbusIntfName, recordInfos);
+            continue;
         }
     }
 }
diff --git a/tool/pldm_platform_cmd.cpp b/tool/pldm_platform_cmd.cpp
index 387f03f..db5cbed 100644
--- a/tool/pldm_platform_cmd.cpp
+++ b/tool/pldm_platform_cmd.cpp
@@ -82,6 +82,8 @@
   private:
     const std::map<uint16_t, std::string> entityType = {
         {64, "System Board"},
+        {66, "Memory Module"},
+        {67, "Processor Module"},
         {137, "Management Controller"},
         {69, "Chassis front panel board (control panel)"},
         {123, "Power converter"},