Updated Get Firmware Root Certficate data command

Updated IPMI Get Firmware Root Certficate data command(0x25) for PFR

This command works only for PFR enabled platforms.

Tested:
 - Enabled INTEL_PFR_ENABLED flag and verified the below command
   ipmitool -I dbus raw 0x08 0x25
 Returns proper data.
 - Disabled INTEL_PFR_ENABLED flag and verified
   Returns error.

Signed-off-by: Rajashekar Gade Reddy <raja.sekhar.reddy.gade@linux.intel.com>
Change-Id: I8a6d0c939d26a8e8058dc35b26c6f78228ea8e5c
diff --git a/include/spiDev.hpp b/include/spiDev.hpp
new file mode 100644
index 0000000..0ebe6e5
--- /dev/null
+++ b/include/spiDev.hpp
@@ -0,0 +1,70 @@
+/*
+// Copyright (c) 2019 Intel Corporation
+//
+// 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.
+*/
+
+#pragma once
+#include <experimental/filesystem>
+
+class SPIDev
+{
+  private:
+    int fd = -1;
+
+  public:
+    SPIDev() = delete;
+    SPIDev(const SPIDev&) = delete;
+    SPIDev& operator=(const SPIDev&) = delete;
+    SPIDev(SPIDev&&) = delete;
+    SPIDev& operator=(SPIDev&&) = delete;
+
+    SPIDev(const std::string& spiDev) :
+        fd(open(spiDev.c_str(), O_RDWR | O_CLOEXEC))
+    {
+        if (fd < 0)
+        {
+            std::string msg = "Unable to open mtd device. errno=" +
+                              std::string(std::strerror(errno));
+            throw std::runtime_error(msg);
+        }
+    }
+
+    virtual ~SPIDev()
+    {
+        if (!(fd < 0))
+        {
+            close(fd);
+        }
+    }
+
+    void spiReadData(const uint32_t startAddr, const size_t dataLen,
+                     void* dataRes)
+    {
+        if (lseek(fd, startAddr, SEEK_SET) < 0)
+        {
+            std::string msg = "Failed to do lseek on mtd device. errno=" +
+                              std::string(std::strerror(errno));
+            throw std::runtime_error(msg);
+        }
+
+        if (read(fd, dataRes, dataLen) != dataLen)
+        {
+            std::string msg = "Failed to read on mtd device. errno=" +
+                              std::string(std::strerror(errno));
+            throw std::runtime_error(msg);
+        }
+
+        return;
+    }
+};
diff --git a/src/firmware-update.cpp b/src/firmware-update.cpp
index 09f6e90..b4c7608 100644
--- a/src/firmware-update.cpp
+++ b/src/firmware-update.cpp
@@ -26,12 +26,16 @@
 #include <sdbusplus/server/object.hpp>
 #include <sdbusplus/timer.hpp>
 #include <sstream>
+#ifdef INTEL_PFR_ENABLED
+#include <spiDev.hpp>
+#endif
 
 namespace ipmi
 {
 namespace firmware
 {
 constexpr Cmd cmdGetFwVersionInfo = 0x20;
+constexpr ipmi::Cmd cmdFwGetRootCertData = 0x25;
 } // namespace firmware
 } // namespace ipmi
 
@@ -1472,69 +1476,92 @@
     return rc;
 }
 
-struct fw_cert_data_req
+#ifdef INTEL_PFR_ENABLED
+enum class FwGetRootCertDataTag : uint8_t
 {
-    uint8_t cert_id;
-    uint16_t offset;
-    uint16_t count;
-} __attribute__((packed));
+    activeRootKey = 1,
+    recoveryRootKey,
+    activeCSK,
+    recoveryCSK,
+};
 
-static ipmi_ret_t ipmi_firmware_get_root_cert_data(
-    ipmi_netfn_t netfn, ipmi_cmd_t cmd, ipmi_request_t request,
-    ipmi_response_t response, ipmi_data_len_t data_len, ipmi_context_t context)
+static constexpr char *bmcActivePfmMTDDev = "/dev/mtd/pfm";
+static constexpr char *bmcRecoveryImgMTDDev = "/dev/mtd/rc-image";
+static constexpr size_t pfmBaseOffsetInImage = 0x400;
+static constexpr size_t rootkeyOffsetInPfm = 0xA0;
+static constexpr size_t cskKeyOffsetInPfm = 0x124;
+static constexpr size_t cskSignatureOffsetInPfm = 0x19c;
+static constexpr size_t certKeyLen = 96;
+static constexpr size_t cskSignatureLen = 96;
+
+ipmi::RspType<std::array<uint8_t, certKeyLen>,
+              std::optional<std::array<uint8_t, cskSignatureLen>>>
+    ipmiGetFwRootCertData(uint8_t certId)
 {
-    if (DEBUG)
-        std::cerr << "Get FW root cert data\n";
+    size_t certKeyOffset = 0;
+    size_t cskSigOffset = 0;
+    std::string mtdDev;
 
-    // request:
-    // Byte 1 - certificate ID: request which certificate (ignored)
-    // Byte 2-3 - offset within cert to start at
-    // Byte 4-5 - number of bytes to return
+    switch (static_cast<FwGetRootCertDataTag>(certId))
+    {
+        case FwGetRootCertDataTag::activeRootKey:
+        {
+            mtdDev = bmcActivePfmMTDDev;
+            certKeyOffset = rootkeyOffsetInPfm;
+            break;
+        }
+        case FwGetRootCertDataTag::recoveryRootKey:
+        {
+            mtdDev = bmcRecoveryImgMTDDev;
+            certKeyOffset = pfmBaseOffsetInImage + rootkeyOffsetInPfm;
+            break;
+        }
+        case FwGetRootCertDataTag::activeCSK:
+        {
+            mtdDev = bmcActivePfmMTDDev;
+            certKeyOffset = cskKeyOffsetInPfm;
+            cskSigOffset = cskSignatureOffsetInPfm;
+            break;
+        }
+        case FwGetRootCertDataTag::recoveryCSK:
+        {
+            mtdDev = bmcRecoveryImgMTDDev;
+            certKeyOffset = pfmBaseOffsetInImage + cskKeyOffsetInPfm;
+            cskSigOffset = pfmBaseOffsetInImage + cskSignatureOffsetInPfm;
+            break;
+        }
+        default:
+        {
+            return ipmi::responseInvalidFieldRequest();
+        }
+    }
 
-    // response:
-    // Byte 1-N  - certificate data
+    std::array<uint8_t, certKeyLen> certKey = {0};
 
-    if (*data_len != sizeof(fw_cert_data_req))
-        return IPMI_CC_REQ_DATA_LEN_INVALID;
-
-    auto cert_data_req = reinterpret_cast<struct fw_cert_data_req *>(request);
-    std::shared_ptr<sdbusplus::asio::connection> bus = getSdBus();
-    auto method = bus->new_method_call(
-        FW_UPDATE_SERVER_DBUS_NAME, FW_UPDATE_SERVER_INFO_PATH,
-        "org.freedesktop.DBus.Properties", "Get");
-    method.append(FW_UPDATE_SECURITY_INTERFACE, "certificate");
-    ipmi::DbusVariant cert;
     try
     {
-        auto reply = bus->call(method);
-        reply.read(cert);
+        SPIDev spiDev(mtdDev);
+        spiDev.spiReadData(certKeyOffset, certKeyLen, certKey.data());
+
+        if (cskSigOffset)
+        {
+            std::array<uint8_t, cskSignatureLen> cskSignature = {0};
+            spiDev.spiReadData(cskSigOffset, cskSignatureLen,
+                               cskSignature.data());
+            return ipmi::responseSuccess(certKey, cskSignature);
+        }
     }
-    catch (sdbusplus::exception::SdBusError &e)
+    catch (const std::exception &e)
     {
-        std::cerr << "SDBus Error: " << e.what();
-        return IPMI_CC_UNSPECIFIED_ERROR;
+        phosphor::logging::log<phosphor::logging::level::ERR>(
+            "Exception caught in ipmiGetFwRootCertData",
+            phosphor::logging::entry("MSG=%s", e.what()));
+        return ipmi::responseUnspecifiedError();
     }
-    auto cert_data = std::get<std::string>(cert);
 
-    if (cert_data_req->offset >= cert_data.size())
-    {
-        *data_len = 0;
-        return IPMI_CC_INVALID_FIELD_REQUEST;
-    }
-    auto first = cert_data.begin() + cert_data_req->offset;
-    auto last = first + cert_data_req->count;
-    if (last > cert_data.end())
-        last = cert_data.end();
-
-    auto data_out = reinterpret_cast<char *>(response);
-    std::copy(first, last, data_out);
-
-    // Status code.
-    ipmi_ret_t rc = IPMI_CC_OK;
-    *data_len = (last - first);
-
-    return rc;
+    return ipmi::responseSuccess(certKey, std::nullopt);
 }
+#endif
 
 static ipmi_ret_t ipmi_firmware_write_data(ipmi_netfn_t netfn, ipmi_cmd_t cmd,
                                            ipmi_request_t request,
@@ -1640,7 +1667,6 @@
 static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_FW_UPD_CHAN_INFO = 0x22;
 static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_BMC_EXEC_CTX = 0x23;
 static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_ROOT_CERT_INFO = 0x24;
-static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_ROOT_CERT_DATA = 0x25;
 static constexpr ipmi_cmd_t IPMI_CMD_FW_GET_FW_UPDATE_RAND_NUM = 0x26;
 static constexpr ipmi_cmd_t IPMI_CMD_FW_SET_FW_UPDATE_MODE = 0x27;
 static constexpr ipmi_cmd_t cmdFirmwareExitFirmwareUpdateMode = 0x28;
@@ -1695,11 +1721,12 @@
     ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_ROOT_CERT_INFO,
                            NULL, ipmi_firmware_get_root_cert_info,
                            PRIVILEGE_ADMIN);
-
+#ifdef INTEL_PFR_ENABLED
     // get root certificate data
-    ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_ROOT_CERT_DATA,
-                           NULL, ipmi_firmware_get_root_cert_data,
-                           PRIVILEGE_ADMIN);
+    ipmi::registerHandler(ipmi::prioOpenBmcBase, ipmi::netFnFirmware,
+                          ipmi::firmware::cmdFwGetRootCertData,
+                          ipmi::Privilege::Admin, ipmiGetFwRootCertData);
+#endif
 
     // generate bmc fw update random number (for enter fw tranfer mode)
     ipmi_register_callback(NETFUN_FIRMWARE, IPMI_CMD_FW_GET_FW_UPDATE_RAND_NUM,