oem-ampere: eventManager: Add CPER Event class handler

Add Ampere OEM CPER event handler to decode CPER data to get TypeID and
SubTypeID. Then logs those info to Ampere IPMI OEM sel logs.

Change-Id: I4ff1cd3c0ba8a2471afb1c8ee61a8caaf30596b8
Signed-off-by: Dung Cao <dung@os.amperecomputing.com>
Signed-off-by: Thu Nguyen <thu@os.amperecomputing.com>
diff --git a/meson.build b/meson.build
index 681dd7a..ff73738 100644
--- a/meson.build
+++ b/meson.build
@@ -138,12 +138,6 @@
 )
 conf_data.set('SENSOR_POLLING_TIME', get_option('sensor-polling-time'))
 
-oem_files = []
-if get_option('oem-ampere').enabled()
-    add_project_arguments('-DOEM_AMPERE', language : 'cpp')
-    subdir('oem/ampere')
-endif
-
 configure_file(output: 'config.h', configuration: conf_data)
 
 add_project_arguments(
@@ -197,6 +191,9 @@
     include_type: 'system',
 )
 
+if get_option('oem-ampere').enabled()
+    add_project_arguments('-DOEM_AMPERE', language : 'cpp')
+endif
 
 libpldmutils_headers = ['.']
 libpldmutils = library(
@@ -231,6 +228,11 @@
     stdplus,
 ]
 
+oem_files = []
+if get_option('oem-ampere').enabled()
+    subdir('oem/ampere')
+endif
+
 if get_option('libpldmresponder').allowed()
     subdir('libpldmresponder')
     deps += [libpldmresponder_dep]
diff --git a/oem/ampere/event/cper.cpp b/oem/ampere/event/cper.cpp
new file mode 100644
index 0000000..3b3436d
--- /dev/null
+++ b/oem/ampere/event/cper.cpp
@@ -0,0 +1,258 @@
+#include "cper.hpp"
+
+#include "libcper/Cper.h"
+
+#include "common/utils.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <cstring>
+#include <ranges>
+#include <vector>
+
+PHOSPHOR_LOG2_USING;
+
+namespace pldm
+{
+namespace oem_ampere
+{
+
+// Returns one if two EFI GUIDs are equal, zero otherwise.
+int guid_equal(EFI_GUID* a, EFI_GUID* b)
+{
+    // Check top base 3 components.
+    if (a->Data1 != b->Data1 || a->Data2 != b->Data2 || a->Data3 != b->Data3)
+    {
+        return 0;
+    }
+
+    // Check Data4 array for equality.
+    for (int i = 0; i < 8; i++)
+    {
+        if (a->Data4[i] != b->Data4[i])
+        {
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+static void decodeSecAmpere(void* section, EFI_AMPERE_ERROR_DATA* ampSpecHdr)
+{
+    std::memcpy(ampSpecHdr, section, sizeof(EFI_AMPERE_ERROR_DATA));
+}
+
+static void decodeSecArm(void* section, EFI_AMPERE_ERROR_DATA* ampSpecHdr)
+{
+    int len;
+    EFI_ARM_ERROR_RECORD* proc;
+    EFI_ARM_ERROR_INFORMATION_ENTRY* errInfo;
+    EFI_ARM_CONTEXT_INFORMATION_HEADER* ctxInfo;
+
+    proc = reinterpret_cast<EFI_ARM_ERROR_RECORD*>(section);
+    errInfo = reinterpret_cast<EFI_ARM_ERROR_INFORMATION_ENTRY*>(proc + 1);
+
+    len = proc->SectionLength -
+          (sizeof(EFI_ARM_ERROR_RECORD) +
+           proc->ErrInfoNum * (sizeof(EFI_ARM_ERROR_INFORMATION_ENTRY)));
+    if (len < 0)
+    {
+        lg2::error("Section length is too small, section_length {LENGTH}",
+                   "LENGTH", static_cast<int>(proc->SectionLength));
+        return;
+    }
+
+    ctxInfo = reinterpret_cast<EFI_ARM_CONTEXT_INFORMATION_HEADER*>(
+        errInfo + proc->ErrInfoNum);
+
+    for ([[maybe_unused]] const auto& i :
+         std::views::iota(0, static_cast<int>(proc->ContextInfoNum)))
+    {
+        int size = sizeof(EFI_ARM_CONTEXT_INFORMATION_HEADER) +
+                   ctxInfo->RegisterArraySize;
+        len -= size;
+        ctxInfo = reinterpret_cast<EFI_ARM_CONTEXT_INFORMATION_HEADER*>(
+            (long)ctxInfo + size);
+    }
+
+    if (len > 0)
+    {
+        /* Get Ampere Specific header data */
+        std::memcpy(ampSpecHdr, (void*)ctxInfo, sizeof(EFI_AMPERE_ERROR_DATA));
+    }
+}
+
+static void decodeSecPlatformMemory(void* section,
+                                    EFI_AMPERE_ERROR_DATA* ampSpecHdr)
+{
+    EFI_PLATFORM_MEMORY_ERROR_DATA* mem =
+        reinterpret_cast<EFI_PLATFORM_MEMORY_ERROR_DATA*>(section);
+    if (mem->ErrorType == MEM_ERROR_TYPE_PARITY)
+    {
+        /* IP Type from bit 0 to 11 of TypeId */
+        ampSpecHdr->TypeId = (ampSpecHdr->TypeId & 0xf800) + ERROR_TYPE_ID_MCU;
+        ampSpecHdr->SubtypeId = SUBTYPE_ID_PARITY;
+    }
+}
+
+static void decodeSecPcie(void* section, EFI_AMPERE_ERROR_DATA* ampSpecHdr)
+{
+    EFI_PCIE_ERROR_DATA* pcieErr =
+        reinterpret_cast<EFI_PCIE_ERROR_DATA*>(section);
+    if (pcieErr->ValidFields & CPER_PCIE_VALID_PORT_TYPE)
+    {
+        if (pcieErr->PortType == CPER_PCIE_PORT_TYPE_ROOT_PORT)
+        {
+            ampSpecHdr->SubtypeId = ERROR_SUBTYPE_PCIE_AER_ROOT_PORT;
+        }
+        else
+        {
+            ampSpecHdr->SubtypeId = ERROR_SUBTYPE_PCIE_AER_DEVICE;
+        }
+    }
+}
+
+static void decodeCperSection(const uint8_t* data, long basePos,
+                              EFI_AMPERE_ERROR_DATA* ampSpecHdr,
+                              EFI_ERROR_SECTION_DESCRIPTOR* secDesc)
+{
+    long pos = basePos + secDesc->SectionOffset;
+    char* section = new char[secDesc->SectionLength];
+    std::memcpy(section, &data[pos], secDesc->SectionLength);
+    pos += secDesc->SectionLength;
+    EFI_GUID* ptr = reinterpret_cast<EFI_GUID*>(&secDesc->SectionType);
+    if (guid_equal(ptr, &gEfiAmpereErrorSectionGuid))
+    {
+        lg2::info("RAS Section Type : Ampere Specific");
+        decodeSecAmpere(section, ampSpecHdr);
+    }
+    else if (guid_equal(ptr, &gEfiArmProcessorErrorSectionGuid))
+    {
+        lg2::info("RAS Section Type : ARM");
+        decodeSecArm(section, ampSpecHdr);
+    }
+    else if (guid_equal(ptr, &gEfiPlatformMemoryErrorSectionGuid))
+    {
+        lg2::info("RAS Section Type : Memory");
+        decodeSecPlatformMemory(section, ampSpecHdr);
+    }
+    else if (guid_equal(ptr, &gEfiPcieErrorSectionGuid))
+    {
+        lg2::info("RAS Section Type : PCIE");
+        decodeSecPcie(section, ampSpecHdr);
+    }
+    else
+    {
+        lg2::error("Section Type is not supported");
+    }
+
+    delete[] section;
+}
+
+void decodeCperRecord(const uint8_t* data, size_t /* eventDataSize */,
+                      EFI_AMPERE_ERROR_DATA* ampSpecHdr)
+{
+    EFI_COMMON_ERROR_RECORD_HEADER cperHeader;
+    long pos = sizeof(CommonEventData);
+    long basePos = sizeof(CommonEventData);
+
+    std::memcpy(&cperHeader, &data[pos],
+                sizeof(EFI_COMMON_ERROR_RECORD_HEADER));
+    pos += sizeof(EFI_COMMON_ERROR_RECORD_HEADER);
+
+    EFI_ERROR_SECTION_DESCRIPTOR* secDesc =
+        new EFI_ERROR_SECTION_DESCRIPTOR[cperHeader.SectionCount];
+    for ([[maybe_unused]] const auto& i :
+         std::views::iota(0, static_cast<int>(cperHeader.SectionCount)))
+    {
+        std::memcpy(&secDesc[i], &data[pos],
+                    sizeof(EFI_ERROR_SECTION_DESCRIPTOR));
+        pos += sizeof(EFI_ERROR_SECTION_DESCRIPTOR);
+    }
+
+    for ([[maybe_unused]] const auto& i :
+         std::views::iota(0, static_cast<int>(cperHeader.SectionCount)))
+    {
+        decodeCperSection(data, basePos, ampSpecHdr, &secDesc[i]);
+    }
+
+    delete[] secDesc;
+}
+
+void addCperSELLog(pldm_tid_t tid, uint16_t eventID, EFI_AMPERE_ERROR_DATA* p)
+{
+    std::vector<uint8_t> evtData;
+    std::string message = "PLDM RAS SEL Event";
+    uint8_t recordType;
+    uint8_t evtData1, evtData2, evtData3, evtData4, evtData5, evtData6;
+    uint8_t socket;
+
+    /*
+     * OEM IPMI SEL Recode Format for RAS event:
+     * evtData1:
+     *    Bit [7:4]: eventClass
+     *        0xF: oemEvent for RAS
+     *    Bit [3:1]: Reserved
+     *    Bit 0: SocketID
+     *        0x0: Socket 0 0x1: Socket 1
+     * evtData2:
+     *    Event ID, indicates RAS PLDM sensor ID.
+     * evtData3:
+     *     Error Type ID high byte - Bit [15:8]
+     * evtData4:
+     *     Error Type ID low byte - Bit [7:0]
+     * evtData5:
+     *     Error Sub Type ID high byte
+     * evtData6:
+     *     Error Sub Type ID low byte
+     */
+    socket = (tid == 1) ? 0 : 1;
+    recordType = 0xD0;
+    evtData1 = SENSOR_TYPE_OEM | socket;
+    evtData2 = eventID;
+    evtData3 = p->TypeId >> 8;
+    evtData4 = p->TypeId;
+    evtData5 = p->SubtypeId >> 8;
+    evtData6 = p->SubtypeId;
+    /*
+     * OEM data bytes
+     *    Ampere IANA: 3 bytes [0x3a 0xcd 0x00]
+     *    event data: 9 bytes [evtData1 evtData2 evtData3
+     *                         evtData4 evtData5 evtData6
+     *                         0x00     0x00     0x00 ]
+     *    sel type: 1 byte [0xC0]
+     */
+    evtData.push_back(0x3a);
+    evtData.push_back(0xcd);
+    evtData.push_back(0);
+    evtData.push_back(evtData1);
+    evtData.push_back(evtData2);
+    evtData.push_back(evtData3);
+    evtData.push_back(evtData4);
+    evtData.push_back(evtData5);
+    evtData.push_back(evtData6);
+    evtData.push_back(0);
+    evtData.push_back(0);
+    evtData.push_back(0);
+    auto& bus = pldm::utils::DBusHandler::getBus();
+    try
+    {
+        auto method =
+            bus.new_method_call(logBusName, logPath, logIntf, "IpmiSelAddOem");
+        method.append(message, evtData, recordType);
+
+        auto selReply = bus.call(method);
+        if (selReply.is_method_error())
+        {
+            lg2::error("addCperSELLog: add SEL log error");
+        }
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error("call addCperSELLog error - {ERROR}", "ERROR", e);
+    }
+}
+
+} // namespace oem_ampere
+} // namespace pldm
diff --git a/oem/ampere/event/cper.hpp b/oem/ampere/event/cper.hpp
new file mode 100644
index 0000000..e4dd5cb
--- /dev/null
+++ b/oem/ampere/event/cper.hpp
@@ -0,0 +1,42 @@
+#include "libcper/Cper.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <vector>
+
+namespace pldm
+{
+namespace oem_ampere
+{
+constexpr auto logBusName = "xyz.openbmc_project.Logging.IPMI";
+constexpr auto logPath = "/xyz/openbmc_project/Logging/IPMI";
+constexpr auto logIntf = "xyz.openbmc_project.Logging.IPMI";
+#define SENSOR_TYPE_OEM 0xF0
+
+/* Memory definitions */
+#define MEM_ERROR_TYPE_PARITY 8
+#define ERROR_TYPE_ID_MCU 1
+#define SUBTYPE_ID_PARITY 9
+
+/* PCIe definitions */
+#define ERROR_SUBTYPE_PCIE_AER_ROOT_PORT 0
+#define ERROR_SUBTYPE_PCIE_AER_DEVICE 1
+#define CPER_PCIE_VALID_PORT_TYPE 0x0001
+#define CPER_PCIE_PORT_TYPE_ROOT_PORT 4
+
+typedef struct
+{
+    uint8_t formatVersion;
+    uint8_t formatType;
+    uint16_t length;
+} __attribute__((packed)) CommonEventData;
+
+void decodeCperRecord(const uint8_t* data, size_t eventDataSize,
+                      EFI_AMPERE_ERROR_DATA* ampSpecHdr);
+void addCperSELLog(uint8_t TID, uint16_t eventID, EFI_AMPERE_ERROR_DATA* p);
+
+} // namespace oem_ampere
+} // namespace pldm
diff --git a/oem/ampere/event/meson.build b/oem/ampere/event/meson.build
index 6dc6a9e..444f7a5 100644
--- a/oem/ampere/event/meson.build
+++ b/oem/ampere/event/meson.build
@@ -1 +1,7 @@
-oem_files += files('oem_event_manager.cpp',)
\ No newline at end of file
+oem_files += files(
+    'oem_event_manager.cpp',
+    'cper.cpp',
+    )
+
+libcper_dep = dependency('libcper', include_type: 'system')
+deps += [libcper_dep]
\ No newline at end of file
diff --git a/oem/ampere/event/oem_event_manager.cpp b/oem/ampere/event/oem_event_manager.cpp
index 79266f5..c3c8fb6 100644
--- a/oem/ampere/event/oem_event_manager.cpp
+++ b/oem/ampere/event/oem_event_manager.cpp
@@ -1,5 +1,8 @@
 #include "oem_event_manager.hpp"
 
+#include "libcper/Cper.h"
+
+#include "cper.hpp"
 #include "requester/handler.hpp"
 #include "requester/request.hpp"
 
@@ -905,5 +908,18 @@
     sendJournalRedfish(description, logLevel);
 }
 
+int OemEventManager::processOemMsgPollEvent(pldm_tid_t tid, uint16_t eventId,
+                                            const uint8_t* eventData,
+                                            size_t eventDataSize)
+{
+    EFI_AMPERE_ERROR_DATA ampHdr;
+
+    decodeCperRecord(eventData, eventDataSize, &ampHdr);
+
+    addCperSELLog(tid, eventId, &ampHdr);
+
+    return PLDM_SUCCESS;
+}
+
 } // namespace oem_ampere
 } // namespace pldm
diff --git a/oem/ampere/event/oem_event_manager.hpp b/oem/ampere/event/oem_event_manager.hpp
index 54f9cdd..d7eb67b 100644
--- a/oem/ampere/event/oem_event_manager.hpp
+++ b/oem/ampere/event/oem_event_manager.hpp
@@ -253,6 +253,18 @@
                           uint8_t /* formatVersion */, pldm_tid_t tid,
                           size_t eventDataOffset);
 
+    /** @brief Handle the polled CPER (0x07, 0xFA) event class.
+     *
+     *  @param[in] tid - terminus ID
+     *  @param[out] eventId - Event ID
+     *  @param[in] eventData - event data
+     *  @param[in] eventDataSize - size of event data
+     *
+     *  @return int - PLDM completion code
+     */
+    int processOemMsgPollEvent(pldm_tid_t tid, uint16_t eventId,
+                               const uint8_t* eventData, size_t eventDataSize);
+
   protected:
     /** @brief Create prefix string for logging message.
      *
diff --git a/oem/ampere/oem_ampere.hpp b/oem/ampere/oem_ampere.hpp
index d054175..ff91652 100644
--- a/oem/ampere/oem_ampere.hpp
+++ b/oem/ampere/oem_ampere.hpp
@@ -63,7 +63,7 @@
     {
         oemEventManager = std::make_shared<oem_ampere::OemEventManager>(
             this->event, this->reqHandler, this->instanceIdDb);
-        createOemEventHandler(oemEventManager, this->platformManager);
+        createOemEventHandler(oemEventManager.get(), this->platformManager);
     }
 
   private:
@@ -72,19 +72,35 @@
      *  This method also assigns the OemEventManager to the below
      *  different handlers.
      */
-    void createOemEventHandler(
-        std::shared_ptr<oem_ampere::OemEventManager> oemEventManager,
-        platform_mc::Manager* platformManager)
+    void createOemEventHandler(oem_ampere::OemEventManager* oemEventManager,
+                               platform_mc::Manager* platformManager)
     {
         platformHandler->registerEventHandlers(
             PLDM_SENSOR_EVENT,
-            {[&oemEventManager](const pldm_msg* request, size_t payloadLength,
-                                uint8_t formatVersion, uint8_t tid,
-                                size_t eventDataOffset) {
+            {[oemEventManager](const pldm_msg* request, size_t payloadLength,
+                               uint8_t formatVersion, uint8_t tid,
+                               size_t eventDataOffset) {
                 return oemEventManager->handleSensorEvent(
                     request, payloadLength, formatVersion, tid,
                     eventDataOffset);
             }});
+
+        /* Register Ampere OEM handler to the PLDM CPER events */
+        platformManager->registerPolledEventHandler(
+            0xFA,
+            {[oemEventManager](pldm_tid_t tid, uint16_t eventId,
+                               const uint8_t* eventData, size_t eventDataSize) {
+                return oemEventManager->processOemMsgPollEvent(
+                    tid, eventId, eventData, eventDataSize);
+            }});
+        platformManager->registerPolledEventHandler(
+            PLDM_CPER_EVENT,
+            {[oemEventManager](pldm_tid_t tid, uint16_t eventId,
+                               const uint8_t* eventData, size_t eventDataSize) {
+                return oemEventManager->processOemMsgPollEvent(
+                    tid, eventId, eventData, eventDataSize);
+            }});
+
         /** CPEREvent class (0x07) is only available in DSP0248 V1.3.0.
          *  Before DSP0248 V1.3.0 spec, Ampere uses OEM event class 0xFA to
          *  report the CPER event
diff --git a/subprojects/libcper.wrap b/subprojects/libcper.wrap
new file mode 100644
index 0000000..9638191
--- /dev/null
+++ b/subprojects/libcper.wrap
@@ -0,0 +1,6 @@
+[wrap-git]
+url = https://github.com/openbmc/libcper.git
+revision = HEAD
+
+[provide]
+libcper = libcper_parse_dep