Add SEL support for FB Tiogapass

Added SEL commands for FB Tiogapass platform.
Get Sel Info
Get Sel Entry
Add Sel Entry
Also included libipmid to use new ipmi implementations.

Tested: Verified data in journal logs by sending command from host.

Change-Id: I0300acdf53e8d6162153e27a90cb59ce3a7a3c6e
Signed-off-by: Vijay Khemka <vijaykhemka@fb.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1711e6e..1c42385 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -63,7 +63,7 @@
 add_library (zfboemcmds
              SHARED src/oemcommands.cpp src/appcommands.cpp
              src/storagecommands.cpp src/usb-dbg.cpp
-             src/transportcommands.cpp)
+             src/selcommands.cpp src/transportcommands.cpp)
 set_target_properties (zfboemcmds PROPERTIES VERSION "0.1.0")
 set_target_properties (zfboemcmds PROPERTIES SOVERSION "0")
 target_link_libraries (zfboemcmds sdbusplus)
diff --git a/include/storagecommands.hpp b/include/storagecommands.hpp
index 9e2d79b..4980433 100644
--- a/include/storagecommands.hpp
+++ b/include/storagecommands.hpp
@@ -17,6 +17,15 @@
 #pragma once
 #include "sdrutils.hpp"
 
+#define SEL_JSON_DATA_FILE "/var/log/fbSelRaw.json"
+#define KEY_SEL_COUNT "SelCount"
+#define KEY_SEL_ENTRY_RAW "SelEntry"
+#define KEY_ADD_TIME "AddTimeStamp"
+#define KEY_ERASE_TIME "EraseTimeStamp"
+#define KEY_FREE_SPACE "FreeSpace"
+#define KEY_OPER_SUPP "OperationalSupport"
+#define KEY_SEL_VER "SELVersion"
+
 static constexpr uint8_t ipmiSdrVersion = 0x51;
 
 #pragma pack(push, 1)
@@ -83,3 +92,55 @@
     uint8_t checksum;
 };
 #pragma pack(pop)
+
+namespace fb_oem::ipmi::sel
+{
+
+static constexpr auto selVersion = 0x51;
+static constexpr auto invalidTimeStamp = 0xFFFFFFFF;
+/* Spec indicates that more than 64kB is free */
+static constexpr auto freeSpace = 0xFFFF;
+static constexpr uint8_t selOperationSupport = 0x02;
+
+static constexpr auto firstEntry = 0x0000;
+static constexpr auto lastEntry = 0xFFFF;
+static constexpr auto entireRecord = 0xFF;
+static constexpr auto selRecordSize = 16;
+
+/** @struct GetSELInfoData
+ *
+ * IPMI response payload data for Get SEL Info request
+ */
+struct GetSELInfoData
+{
+    uint8_t selVersion;
+    uint16_t entries;
+    uint16_t freeSpace;
+    uint32_t addTimeStamp;
+    uint32_t eraseTimeStamp;
+    uint8_t operationSupport;
+};
+
+/** @struct GetSELEntryRequest
+ *
+ *  IPMI payload for Get SEL Entry command request.
+ */
+struct GetSELEntryRequest
+{
+    uint16_t reservID; //!< Reservation ID.
+    uint16_t recordID; //!< SEL Record ID.
+    uint8_t offset;    //!< Offset into record.
+    uint8_t readLen;   //!< Bytes to read.
+} __attribute__((packed));
+
+/** @struct GetSELEntryResponse
+ *
+ *  IPMI payload for Get SEL Entry command response.
+ */
+struct GetSELEntryResponse
+{
+    uint16_t nextRecordID;  //!< Next RecordID.
+    uint8_t recordData[16]; //!< Record Data.
+} __attribute__((packed));
+
+} // namespace fb_oem::ipmi::sel
diff --git a/src/selcommands.cpp b/src/selcommands.cpp
new file mode 100644
index 0000000..0fc33b4
--- /dev/null
+++ b/src/selcommands.cpp
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c)  2018 Intel Corporation.
+ * Copyright (c)  2018-present Facebook.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ipmid/api.hpp>
+
+#include <boost/algorithm/string/join.hpp>
+#include <nlohmann/json.hpp>
+#include <iostream>
+#include <sstream>
+#include <fstream>
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/message/types.hpp>
+#include <sdbusplus/timer.hpp>
+#include <storagecommands.hpp>
+
+//----------------------------------------------------------------------
+// Platform specific functions for storing app data
+//----------------------------------------------------------------------
+
+static void toHexStr(std::vector<uint8_t> &bytes, std::string &hexStr)
+{
+    std::stringstream stream;
+    stream << std::hex << std::uppercase << std::setfill('0');
+    for (const uint8_t byte : bytes)
+    {
+        stream << std::setw(2) << static_cast<int>(byte);
+    }
+    hexStr = stream.str();
+}
+
+static int fromHexStr(const std::string hexStr, std::vector<uint8_t> &data)
+{
+    for (unsigned int i = 0; i < hexStr.size(); i += 2)
+    {
+        try
+        {
+            data.push_back(static_cast<uint8_t>(
+                std::stoul(hexStr.substr(i, 2), nullptr, 16)));
+        }
+        catch (std::invalid_argument &e)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+            return -1;
+        }
+        catch (std::out_of_range &e)
+        {
+            phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
+            return -1;
+        }
+    }
+    return 0;
+}
+
+namespace fb_oem::ipmi::sel
+{
+
+class SELData
+{
+  private:
+    nlohmann::json selDataObj;
+
+    void flush()
+    {
+        std::ofstream file(SEL_JSON_DATA_FILE);
+        file << selDataObj;
+        file.close();
+    }
+
+    void init()
+    {
+        selDataObj[KEY_SEL_VER] = 0x51;
+        selDataObj[KEY_SEL_COUNT] = 0;
+        selDataObj[KEY_ADD_TIME] = 0xFFFFFFFF;
+        selDataObj[KEY_ERASE_TIME] = 0xFFFFFFFF;
+        selDataObj[KEY_OPER_SUPP] = 0x02;
+        /* Spec indicates that more than 64kB is free */
+        selDataObj[KEY_FREE_SPACE] = 0xFFFF;
+    }
+
+  public:
+    SELData()
+    {
+        /* Get App data stored in json file */
+        std::ifstream file(SEL_JSON_DATA_FILE);
+        if (file)
+        {
+            file >> selDataObj;
+            file.close();
+        }
+
+        /* Initialize SelData object if no entries. */
+        if (selDataObj.find(KEY_SEL_COUNT) == selDataObj.end())
+        {
+            init();
+        }
+    }
+
+    int clear()
+    {
+        /* Clear the complete Sel Json object */
+        selDataObj.clear();
+        /* Reinitialize it with basic data */
+        init();
+        /* Save the erase time */
+        struct timespec selTime = {};
+        if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
+        {
+            return -1;
+        }
+        selDataObj[KEY_ERASE_TIME] = selTime.tv_sec;
+        flush();
+        return 0;
+    }
+
+    uint32_t getCount()
+    {
+        return selDataObj[KEY_SEL_COUNT];
+    }
+
+    void getInfo(GetSELInfoData &info)
+    {
+        info.selVersion = selDataObj[KEY_SEL_VER];
+        info.entries = selDataObj[KEY_SEL_COUNT];
+        info.freeSpace = selDataObj[KEY_FREE_SPACE];
+        info.addTimeStamp = selDataObj[KEY_ADD_TIME];
+        info.eraseTimeStamp = selDataObj[KEY_ERASE_TIME];
+        info.operationSupport = selDataObj[KEY_OPER_SUPP];
+    }
+
+    int getEntry(uint32_t index, std::string &rawStr)
+    {
+        std::stringstream ss;
+        ss << std::hex;
+        ss << std::setw(2) << std::setfill('0') << index;
+
+        /* Check or the requested SEL Entry, if record is available */
+        if (selDataObj.find(ss.str()) == selDataObj.end())
+        {
+            return -1;
+        }
+
+        rawStr = selDataObj[ss.str()][KEY_SEL_ENTRY_RAW];
+        return 0;
+    }
+
+    int addEntry(std::string keyStr)
+    {
+        struct timespec selTime = {};
+
+        if (clock_gettime(CLOCK_REALTIME, &selTime) < 0)
+        {
+            return -1;
+        }
+
+        selDataObj[KEY_ADD_TIME] = selTime.tv_sec;
+
+        int selCount = selDataObj[KEY_SEL_COUNT];
+        selDataObj[KEY_SEL_COUNT] = ++selCount;
+
+        std::stringstream ss;
+        ss << std::hex;
+        ss << std::setw(2) << std::setfill('0') << selCount;
+
+        selDataObj[ss.str()][KEY_SEL_ENTRY_RAW] = keyStr;
+        flush();
+        return selCount;
+    }
+};
+
+} // namespace fb_oem::ipmi::sel
+
+namespace ipmi
+{
+
+namespace storage
+{
+
+static void registerSELFunctions() __attribute__((constructor));
+static fb_oem::ipmi::sel::SELData selObj __attribute__((init_priority(101)));
+
+ipmi::RspType<uint8_t,  // SEL version
+              uint16_t, // SEL entry count
+              uint16_t, // free space
+              uint32_t, // last add timestamp
+              uint32_t, // last erase timestamp
+              uint8_t>  // operation support
+    ipmiStorageGetSELInfo()
+{
+
+    fb_oem::ipmi::sel::GetSELInfoData info;
+
+    selObj.getInfo(info);
+    return ipmi::responseSuccess(info.selVersion, info.entries, info.freeSpace,
+                                 info.addTimeStamp, info.eraseTimeStamp,
+                                 info.operationSupport);
+}
+
+ipmi::RspType<uint16_t, std::vector<uint8_t>>
+    ipmiStorageGetSELEntry(std::vector<uint8_t> data)
+{
+
+    if (data.size() != sizeof(fb_oem::ipmi::sel::GetSELEntryRequest))
+    {
+        return ipmi::responseReqDataLenInvalid();
+    }
+
+    fb_oem::ipmi::sel::GetSELEntryRequest *reqData =
+        reinterpret_cast<fb_oem::ipmi::sel::GetSELEntryRequest *>(&data[0]);
+
+    if (reqData->reservID != 0)
+    {
+        if (!checkSELReservation(reqData->reservID))
+        {
+            return ipmi::responseInvalidReservationId();
+        }
+    }
+
+    uint16_t selCnt = selObj.getCount();
+    if (selCnt == 0)
+    {
+        return ipmi::responseSensorInvalid();
+    }
+
+    /* If it is asked for first entry */
+    if (reqData->recordID == fb_oem::ipmi::sel::firstEntry)
+    {
+        /* First Entry (0x0000) as per Spec */
+        reqData->recordID = 1;
+    }
+    else if (reqData->recordID == fb_oem::ipmi::sel::lastEntry)
+    {
+        /* Last entry (0xFFFF) as per Spec */
+        reqData->recordID = selCnt;
+    }
+
+    std::string ipmiRaw;
+
+    if (selObj.getEntry(reqData->recordID, ipmiRaw) < 0)
+    {
+        return ipmi::responseSensorInvalid();
+    }
+
+    std::vector<uint8_t> recDataBytes;
+    if (fromHexStr(ipmiRaw, recDataBytes) < 0)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+
+    /* Identify the next SEL record ID. If recordID is same as
+     * total SeL count then next id should be last entry else
+     * it should be incremented by 1 to current RecordID
+     */
+    uint16_t nextRecord;
+    if (reqData->recordID == selCnt)
+    {
+        nextRecord = fb_oem::ipmi::sel::lastEntry;
+    }
+    else
+    {
+        nextRecord = reqData->recordID + 1;
+    }
+
+    if (reqData->readLen == fb_oem::ipmi::sel::entireRecord)
+    {
+        return ipmi::responseSuccess(nextRecord, recDataBytes);
+    }
+    else
+    {
+        if (reqData->offset >= fb_oem::ipmi::sel::selRecordSize ||
+            reqData->readLen > fb_oem::ipmi::sel::selRecordSize)
+        {
+            return ipmi::responseUnspecifiedError();
+        }
+        std::vector<uint8_t> recPartData;
+
+        auto diff = fb_oem::ipmi::sel::selRecordSize - reqData->offset;
+        auto readLength = std::min(diff, static_cast<int>(reqData->readLen));
+
+        for (int i = 0; i < readLength; i++)
+        {
+            recPartData.push_back(recDataBytes[i + reqData->offset]);
+        }
+        return ipmi::responseSuccess(nextRecord, recPartData);
+    }
+}
+
+ipmi::RspType<uint16_t> ipmiStorageAddSELEntry(std::vector<uint8_t> data)
+{
+    /* Per the IPMI spec, need to cancel any reservation when a
+     * SEL entry is added
+     */
+    cancelSELReservation();
+
+    if (data.size() != fb_oem::ipmi::sel::selRecordSize)
+    {
+        return ipmi::responseReqDataLenInvalid();
+    }
+
+    std::string ipmiRaw, logErr;
+    toHexStr(data, ipmiRaw);
+
+    /* Log the Raw SEL message to the journal */
+    std::string journalMsg = "SEL Entry Added: " + ipmiRaw;
+    phosphor::logging::log<phosphor::logging::level::INFO>(journalMsg.c_str());
+
+    int responseID = selObj.addEntry(ipmiRaw.c_str());
+    if (responseID < 0)
+    {
+        return ipmi::responseUnspecifiedError();
+    }
+    return ipmi::responseSuccess((uint16_t)responseID);
+}
+
+void registerSELFunctions()
+{
+    // <Get SEL Info>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdGetSelInfo, ipmi::Privilege::User,
+                          ipmiStorageGetSELInfo);
+
+    // <Get SEL Entry>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdGetSelEntry, ipmi::Privilege::User,
+                          ipmiStorageGetSELEntry);
+
+    // <Add SEL Entry>
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnStorage,
+                          ipmi::storage::cmdAddSelEntry,
+                          ipmi::Privilege::Operator, ipmiStorageAddSELEntry);
+
+    return;
+}
+
+} // namespace storage
+} // namespace ipmi