Add OEM command to request flash size

The BMC flash is `/dev/mtd0` and the size can found in
`/sys/class/mtd/mtd0/size`. This OEM command will request that
information with MTDINFO.

Tested:
Successfully requested the flash size information with ipmitool.

```
cat /sys/class/mtd/mtd0/size
67108864
```
67108864 / 1024 / 1024 = 64MB flash

```
ipmitool raw 0x2e 0x32 0x79 0x2b 0x00 0x09
 79 2b 00 09 00 00 00 04
```
Output in little endian.
0x04000000 -> 67108864

Change-Id: Iec1b33503d1166a42ceef4b8491e5c19c3a077fe
Signed-off-by: Willy Tu <wltu@google.com>
diff --git a/Makefile.am b/Makefile.am
index 34e60bd..5016449 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -44,6 +44,7 @@
 	cable.cpp \
 	cpld.cpp \
 	eth.cpp \
+	flash_size.cpp \
 	psu.cpp \
 	pcie_i2c.cpp \
 	entity_name.cpp \
diff --git a/README.md b/README.md
index 4e79158..25ae8e0 100644
--- a/README.md
+++ b/README.md
@@ -198,3 +198,20 @@
 |--------|------|----
 |0x00|0x08|Subcommand
 
+
+### GetFlashSize - SubCommand 0x09
+
+Request the physical size of the BMC flash.
+
+Request
+
+|Byte(s) |Value |Data
+|--------|------|----
+|0x00|0x09|Subcommand
+
+Response
+
+|Byte(s) |Value |Data
+|--------|------|----
+|0x00|0x09|Subcommand
+|0x01...0x04|Flash size|Flash size
diff --git a/commands.hpp b/commands.hpp
index 19632ef..d9efb4f 100644
--- a/commands.hpp
+++ b/commands.hpp
@@ -25,6 +25,8 @@
     SysMachineName = 7,
     // Arm for psu reset on host shutdown
     SysPsuHardResetOnShutdown = 8,
+    // The Sys get flash size command
+    SysGetFlashSize = 9,
 };
 
 } // namespace ipmi
diff --git a/flash_size.cpp b/flash_size.cpp
new file mode 100644
index 0000000..069b1b1
--- /dev/null
+++ b/flash_size.cpp
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2021 Google Inc.
+ *
+ * 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 "flash_size.hpp"
+
+#include "commands.hpp"
+#include "errors.hpp"
+#include "handler.hpp"
+
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <vector>
+
+namespace google
+{
+namespace ipmi
+{
+
+struct GetFlashSizeRequest
+{
+    uint8_t subcommand;
+} __attribute__((packed));
+
+struct GetFlashSizeReply
+{
+    uint8_t subcommand;
+    uint32_t flashSize;
+} __attribute__((packed));
+
+ipmi_ret_t getFlashSize(const uint8_t* reqBuf, uint8_t* replyBuf,
+                        size_t* dataLen, HandlerInterface* handler)
+{
+    struct GetFlashSizeRequest request;
+
+    if ((*dataLen) < sizeof(request))
+    {
+        std::fprintf(stderr, "Invalid command length: %u\n",
+                     static_cast<uint32_t>(*dataLen));
+        return IPMI_CC_REQ_DATA_LEN_INVALID;
+    }
+
+    std::memcpy(&request, &reqBuf[0], sizeof(request));
+    uint32_t flashSize;
+    try
+    {
+        flashSize = handler->getFlashSize();
+    }
+    catch (const IpmiException& e)
+    {
+        return e.getIpmiError();
+    }
+
+    auto reply = reinterpret_cast<struct GetFlashSizeReply*>(&replyBuf[0]);
+    reply->subcommand = SysGetFlashSize;
+    reply->flashSize = htole32(flashSize);
+
+    (*dataLen) = sizeof(struct GetFlashSizeReply);
+    return IPMI_CC_OK;
+}
+} // namespace ipmi
+} // namespace google
diff --git a/flash_size.hpp b/flash_size.hpp
new file mode 100644
index 0000000..9cec240
--- /dev/null
+++ b/flash_size.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "handler.hpp"
+
+#include <ipmid/api.h>
+
+namespace google
+{
+namespace ipmi
+{
+
+ipmi_ret_t getFlashSize(const uint8_t* reqBuf, uint8_t* replyBuf,
+                        size_t* dataLen, HandlerInterface* handler);
+
+} // namespace ipmi
+} // namespace google
diff --git a/handler.cpp b/handler.cpp
index 4f1bc56..ef48842 100644
--- a/handler.cpp
+++ b/handler.cpp
@@ -20,7 +20,12 @@
 #include "handler_impl.hpp"
 #include "util.hpp"
 
+#include <fcntl.h>
 #include <ipmid/api.h>
+#include <mtd/mtd-abi.h>
+#include <mtd/mtd-user.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
 
 #include <cinttypes>
 #include <cstdio>
@@ -213,6 +218,20 @@
     ofs.close();
 }
 
+uint32_t Handler::getFlashSize()
+{
+    mtd_info_t info;
+    int fd = open("/dev/mtd0", O_RDONLY);
+    int err = ioctl(fd, MEMGETINFO, &info);
+    close(fd);
+
+    if (err)
+    {
+        throw IpmiException(IPMI_CC_UNSPECIFIED_ERROR);
+    }
+    return info.size;
+}
+
 std::string Handler::getEntityName(std::uint8_t id, std::uint8_t instance)
 {
     // Check if we support this Entity ID.
diff --git a/handler.hpp b/handler.hpp
index 2db83df..1a2cbae 100644
--- a/handler.hpp
+++ b/handler.hpp
@@ -74,6 +74,14 @@
                                       std::uint8_t instance) = 0;
 
     /**
+     * Return the flash size of bmc chip.
+     *
+     * @return the flash size of bmc chip
+     * @throw IpmiException on failure.
+     */
+    virtual uint32_t getFlashSize() = 0;
+
+    /**
      * Return the name of the machine, parsed from release information.
      *
      * @return the machine name
diff --git a/handler_impl.hpp b/handler_impl.hpp
index f1ea491..677efa2 100644
--- a/handler_impl.hpp
+++ b/handler_impl.hpp
@@ -30,6 +30,7 @@
     void psuResetDelay(std::uint32_t delay) const override;
     void psuResetOnShutdown() const override;
     std::string getEntityName(std::uint8_t id, std::uint8_t instance) override;
+    uint32_t getFlashSize() override;
     std::string getMachineName() override;
     void buildI2cPcieMapping() override;
     size_t getI2cPcieMappingSize() const override;
diff --git a/ipmi.cpp b/ipmi.cpp
index cdf3b64..2b71315 100644
--- a/ipmi.cpp
+++ b/ipmi.cpp
@@ -21,6 +21,7 @@
 #include "cpld.hpp"
 #include "entity_name.hpp"
 #include "eth.hpp"
+#include "flash_size.hpp"
 #include "handler.hpp"
 #include "machine_name.hpp"
 #include "pcie_i2c.hpp"
@@ -69,6 +70,8 @@
         case SysPsuHardResetOnShutdown:
             return psuHardResetOnShutdown(reqBuf, replyCmdBuf, dataLen,
                                           handler);
+        case SysGetFlashSize:
+            return getFlashSize(reqBuf, replyCmdBuf, dataLen, handler);
         default:
             std::fprintf(stderr, "Invalid subcommand: 0x%x\n", reqBuf[0]);
             return IPMI_CC_INVALID;
diff --git a/test/Makefile.am b/test/Makefile.am
index d10f4ba..c97f480 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -48,3 +48,7 @@
 check_PROGRAMS += pcie_unittest
 pcie_unittest_SOURCES = pcie_unittest.cpp
 pcie_unittest_LDADD = $(top_builddir)/libsyscmds_common.la
+
+check_PROGRAMS += flash_unittest
+flash_unittest_SOURCES = flash_unittest.cpp
+flash_unittest_LDADD = $(top_builddir)/libsyscmds_common.la
diff --git a/test/flash_unittest.cpp b/test/flash_unittest.cpp
new file mode 100644
index 0000000..36f5d4a
--- /dev/null
+++ b/test/flash_unittest.cpp
@@ -0,0 +1,52 @@
+#include "commands.hpp"
+#include "flash_size.hpp"
+#include "handler_mock.hpp"
+
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#define MAX_IPMI_BUFFER 64
+
+using ::testing::Return;
+
+namespace google
+{
+namespace ipmi
+{
+
+TEST(FlashSizeCommandTest, InvalidCommandLength)
+{
+    // GetFlashSizeRequest is one byte, let's send 0.
+    std::vector<std::uint8_t> request = {};
+    size_t dataLen = request.size();
+    std::uint8_t reply[MAX_IPMI_BUFFER];
+
+    HandlerMock hMock;
+    EXPECT_EQ(IPMI_CC_REQ_DATA_LEN_INVALID,
+              getFlashSize(request.data(), reply, &dataLen, &hMock));
+}
+
+TEST(FlashSizeCommandTest, ValidRequest)
+{
+    std::vector<std::uint8_t> request = {SysOEMCommands::SysGetFlashSize};
+    size_t dataLen = request.size();
+    std::uint8_t reply[MAX_IPMI_BUFFER];
+    uint32_t flashSize = 5422312; // 0x52BCE8
+
+    HandlerMock hMock;
+    EXPECT_CALL(hMock, getFlashSize()).WillOnce(Return(flashSize));
+    EXPECT_EQ(IPMI_CC_OK,
+              getFlashSize(request.data(), reply, &dataLen, &hMock));
+    EXPECT_EQ(dataLen, 5);
+    EXPECT_EQ(reply[4], 0);
+    EXPECT_EQ(reply[3], 0x52);
+    EXPECT_EQ(reply[2], 0xBC);
+    EXPECT_EQ(reply[1], 0xE8);
+}
+
+} // namespace ipmi
+} // namespace google
diff --git a/test/handler_mock.hpp b/test/handler_mock.hpp
index e20886a..d539f7b 100644
--- a/test/handler_mock.hpp
+++ b/test/handler_mock.hpp
@@ -27,6 +27,7 @@
                                   std::uint8_t>(unsigned int));
     MOCK_CONST_METHOD1(psuResetDelay, void(std::uint32_t));
     MOCK_CONST_METHOD0(psuResetOnShutdown, void());
+    MOCK_METHOD0(getFlashSize, uint32_t());
     MOCK_METHOD2(getEntityName, std::string(std::uint8_t, std::uint8_t));
     MOCK_METHOD0(getMachineName, std::string());
     MOCK_METHOD0(buildI2cPcieMapping, void());