Add erase sanitize

Sanitize uses the eMMC firmware to erase all blocks.

Tested:
$ cat /dev/urandom > /dev/mmcblk0

$ time busctl call xyz.openbmc_project.eStoraged.mmcblk0 \
/xyz/openbmc_project/inventory/storage/mmcblk0 \
xyz.openbmc_project.Inventory.Item.Volume Erase s \
xyz.openbmc_project.Inventory.Item.Volume.EraseMethod.VendorSanitize \
--timeout=1200

real    0m1.793s
user    0m0.021s
sys     0m0.009s

root@ytbaz20-nfd01:/# cat /dev/mmcblk0 | hexdump

0000000 0000 0000 0000 0000 0000 0000 0000 0000
*

Signed-off-by: John Edward Broadbent <jebr@google.com>
Change-Id: I31bc21c0b6d31cbba0db752d94a93eb004dbbde6
diff --git a/.clang-tidy b/.clang-tidy
index b5f7462..0c9cc0b 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -296,5 +296,5 @@
   - { key: readability-identifier-naming.ParameterCase, value: camelBack }
   - { key: readability-identifier-naming.NamespaceCase, value: lower_case }
   - { key: readability-identifier-naming.StructCase,    value: CamelCase  }
-  - { key: cppcoreguidelines-macro-usage.AllowedRegexp, value: ((ERASE_MIN_GEOMETRY)|(ERASE_MAX_GEOMETRY))  }
+  - { key: cppcoreguidelines-macro-usage.AllowedRegexp, value: ((ERASE_MIN_GEOMETRY)|(ERASE_MAX_GEOMETRY)|(twiddleextCsdEraseGroupDef))  }
 
diff --git a/include/sanitize.hpp b/include/sanitize.hpp
new file mode 100644
index 0000000..4ab73bf
--- /dev/null
+++ b/include/sanitize.hpp
@@ -0,0 +1,125 @@
+#pragma once
+
+#include "erase.hpp"
+
+#include <linux/mmc/ioctl.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+
+#include <stdplus/fd/managed.hpp>
+#include <util.hpp>
+
+#include <array>
+#include <cstddef>
+#include <span>
+#include <string_view>
+
+namespace estoraged
+{
+
+using stdplus::fd::ManagedFd;
+
+class IOCTLWrapperInterface
+{
+  public:
+    /** @brief Wrapper around ioctl
+     *  @details Used for mocking purposes.
+     *
+     * @param[in] devPath - File name of block device
+     * @param[in] request - Device-dependent request code
+     * @param[in] mmc_ioc_cmd - eMMC cmd
+     */
+    virtual int doIoctl(std::string_view devPath, unsigned long request,
+                        struct mmc_ioc_cmd data) = 0;
+
+    /** @brief Wrapper around ioctl
+     *  @details Used for mocking purposes.
+     *
+     * @param[in] devPath - File name of block device
+     * @param[in] request - Device-dependent request code
+     * @param[in] mmc_io_mutli_cmd - many eMMC cmd
+     */
+    virtual int doIoctlMulti(std::string_view devPath, unsigned long request,
+                             struct mmc_io_multi_cmd_erase data) = 0;
+
+    virtual ~IOCTLWrapperInterface() = default;
+    IOCTLWrapperInterface() = default;
+    IOCTLWrapperInterface(const IOCTLWrapperInterface&) = delete;
+    IOCTLWrapperInterface& operator=(const IOCTLWrapperInterface&) = delete;
+
+    IOCTLWrapperInterface(IOCTLWrapperInterface&&) = delete;
+    IOCTLWrapperInterface& operator=(IOCTLWrapperInterface&&) = delete;
+};
+
+// mockIOCTLWapper also inherits from IOCTLWrapperInterface
+class IOCTLWrapperImpl : public IOCTLWrapperInterface
+{
+  public:
+    int doIoctl(std::string_view devPath, unsigned long request,
+                struct mmc_ioc_cmd data) override;
+    int doIoctlMulti(std::string_view devPath, unsigned long request,
+                     struct mmc_io_multi_cmd_erase data) override;
+    ~IOCTLWrapperImpl() override = default;
+    IOCTLWrapperImpl() = default;
+
+    IOCTLWrapperImpl(const IOCTLWrapperImpl&) = delete;
+    IOCTLWrapperImpl& operator=(const IOCTLWrapperImpl&) = delete;
+
+    IOCTLWrapperImpl(IOCTLWrapperImpl&&) = delete;
+    IOCTLWrapperImpl& operator=(IOCTLWrapperImpl&&) = delete;
+};
+
+class Sanitize : public Erase
+{
+  public:
+    /** @breif Creates a sanitize erase object
+     *
+     * @param[in] inDevPath - the linux device path for the block device.
+     * @param[in] IOCTLWrapper - This is a ioctl wrapper, it can be used for
+     * testing
+     */
+    Sanitize(std::string_view inDevPath,
+             std::unique_ptr<IOCTLWrapperInterface> inIOCTL =
+                 std::make_unique<IOCTLWrapperImpl>()) :
+        Erase(inDevPath),
+        ioctlWrapper(std::move(inIOCTL))
+    {}
+
+    /** @brief sanitize the drive, using eMMC specifed erase commands
+     *
+     * param[in] driveSize - size of the drive in bytes
+     */
+    void doSanitize(uint64_t driveSize);
+
+    /** @brief sanitize the drive, using eMMC specifed erase commands
+     *   This function uses the built in utils to call sanitize
+     */
+    void doSanitize()
+    {
+        doSanitize(util::Util::findSizeOfBlockDevice(devPath));
+    }
+
+  private:
+    /* Wrapper for ioctl*/
+    std::unique_ptr<IOCTLWrapperInterface> ioctlWrapper;
+
+    /** @brief uses the eMMC defined sanitize command, it is not the same as
+     * vendor_sanitize  */
+    void emmcSanitize();
+
+    /** @brief uses the eMMC defined erase command
+     *
+     * param[in] driveSize - size of the drive in bytes
+     */
+    void emmcErase(uint64_t driveSize);
+};
+
+// can't use the real mmc_ioc_multi_cmd b/c of zero length array
+// see uapi/linux/mmc/ioctl.h
+struct mmc_io_multi_cmd_erase
+{
+    uint64_t num_of_cmds;
+    struct mmc_ioc_cmd cmds[3]; // NOLINT (c arrays usage)
+};
+
+} // namespace estoraged
diff --git a/src/erase/meson.build b/src/erase/meson.build
index 233fc59..57329c2 100644
--- a/src/erase/meson.build
+++ b/src/erase/meson.build
@@ -3,6 +3,7 @@
   'verifyDriveGeometry.cpp',
   'pattern.cpp',
   'cryptoErase.cpp',
+  'sanitize.cpp',
   'zero.cpp',
   include_directories : eStoraged_headers,
   implicit_include_directories: false,
diff --git a/src/erase/sanitize.cpp b/src/erase/sanitize.cpp
new file mode 100644
index 0000000..26e39fb
--- /dev/null
+++ b/src/erase/sanitize.cpp
@@ -0,0 +1,145 @@
+#include "sanitize.hpp"
+
+#include "estoraged_conf.hpp"
+
+#include <linux/mmc/ioctl.h>
+#include <sys/ioctl.h>
+
+#include <phosphor-logging/lg2.hpp>
+#include <stdplus/fd/create.hpp>
+#include <stdplus/fd/managed.hpp>
+#include <stdplus/handle/managed.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <array>
+#include <cstddef>
+#include <span>
+#include <string>
+#include <string_view>
+
+namespace
+{
+
+constexpr uint32_t mmcSwitch = 6;
+constexpr uint32_t mmcSendExtCsd = 8;
+constexpr uint32_t mmcSwitchModeWriteByte = 0x03;
+constexpr uint32_t extCsdSanitizeStart = 165;
+constexpr uint32_t extCsdCmdSetNormal = (1 << 0);
+
+constexpr uint32_t mmcRspPresent = (1 << 0);
+constexpr uint32_t mmcRspCrc = (1 << 2);
+constexpr uint32_t mmcRspBusy = (1 << 3);
+constexpr uint32_t mmcRspOpcode = (1 << 4);
+
+constexpr uint32_t mmcCmdAc = (0 << 5);
+constexpr uint32_t mmcCmdAdtc = (1 << 5);
+
+constexpr uint32_t mmcRspSpiS1 = (1 << 7);
+constexpr uint32_t mmcRspSpiBusy = (1 << 10);
+
+constexpr uint32_t mmcRspSpiR1 = (mmcRspSpiS1);
+constexpr uint32_t mmcRspSpiR1B = (mmcRspSpiS1 | mmcRspSpiBusy);
+
+constexpr uint32_t mmcRspR1B =
+    (mmcRspPresent | mmcRspCrc | mmcRspOpcode | mmcRspBusy);
+
+constexpr uint32_t mmcRspR1 = (mmcRspPresent | mmcRspCrc | mmcRspOpcode);
+
+constexpr uint32_t mmcEraseGroupStart = 35;
+constexpr uint32_t mmcEraseGroupEnd = 36;
+constexpr uint32_t mmcErase = 38;
+} // namespace
+
+namespace estoraged
+{
+
+using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using stdplus::fd::ManagedFd;
+
+void Sanitize::doSanitize(uint64_t driveSize)
+{
+    try
+    {
+        emmcErase(driveSize);
+        emmcSanitize();
+    }
+    catch (...)
+    {
+        lg2::error("eStorageD erase sanitize failure", "REDFISH_MESSAGE_ID",
+                   std::string("eStorageD.1.0.EraseFailure"));
+        throw InternalFailure();
+    }
+    lg2::info("eStorageD successfully erase sanitize", "REDFISH_MESSAGE_ID",
+              std::string("eStorageD.1.0.EraseSuccessful"));
+}
+
+void Sanitize::emmcErase(uint64_t driveSize)
+{
+
+    uint64_t sectorSize = 0x200; // default value see eMMC spec 6.6.34.
+                                 // NOTE: 0x200 is only valid for eMMC greater
+                                 // then 2 GB
+    struct mmc_io_multi_cmd_erase eraseCmd = {};
+
+    eraseCmd.num_of_cmds = 3;
+    eraseCmd.cmds[0].opcode = mmcEraseGroupStart;
+    eraseCmd.cmds[0].arg = 0;
+    eraseCmd.cmds[0].flags = mmcRspSpiR1 | mmcRspR1 | mmcCmdAc;
+    eraseCmd.cmds[0].write_flag = 1;
+
+    eraseCmd.cmds[1].opcode = mmcEraseGroupEnd;
+    eraseCmd.cmds[1].arg = (driveSize / sectorSize) - 1;
+    eraseCmd.cmds[1].flags = mmcRspSpiR1 | mmcRspR1 | mmcCmdAc;
+    eraseCmd.cmds[1].write_flag = 1;
+
+    /* Send Erase Command */
+    eraseCmd.cmds[2].opcode = mmcErase;
+    eraseCmd.cmds[2].arg = 0x00000000;
+    eraseCmd.cmds[2].cmd_timeout_ms = 0x0FFFFFFF;
+    eraseCmd.cmds[2].flags = mmcRspSpiR1B | mmcRspR1B | mmcCmdAc;
+    eraseCmd.cmds[2].write_flag = 1;
+
+    if (ioctlWrapper->doIoctlMulti(devPath, MMC_IOC_MULTI_CMD, eraseCmd) != 0)
+    {
+        throw InternalFailure();
+    }
+}
+
+void Sanitize::emmcSanitize()
+{
+
+    struct mmc_ioc_cmd idata = {};
+    idata.write_flag = 1;
+    idata.opcode = mmcSwitch;
+    idata.arg = (mmcSwitchModeWriteByte << 24) | (extCsdSanitizeStart << 16) |
+                (1 << 8) | extCsdCmdSetNormal;
+    idata.flags = mmcRspSpiR1B | mmcRspR1B | mmcCmdAc;
+
+    // make the eMMC sanitize ioctl
+    if (ioctlWrapper->doIoctl(devPath, MMC_IOC_CMD, idata) != 0)
+    {
+        throw InternalFailure();
+    }
+}
+
+int IOCTLWrapperImpl::doIoctl(std::string_view devPath, unsigned long request,
+                              struct mmc_ioc_cmd data)
+
+{
+    ManagedFd fd = stdplus::fd::open(std::string(devPath).c_str(),
+                                     stdplus::fd::OpenAccess::ReadOnly);
+
+    return fd.ioctl(request, static_cast<void*>(&data));
+}
+
+int IOCTLWrapperImpl::doIoctlMulti(std::string_view devPath,
+                                   unsigned long request,
+                                   struct mmc_io_multi_cmd_erase data)
+{
+    ManagedFd fd = stdplus::fd::open(std::string(devPath).c_str(),
+                                     stdplus::fd::OpenAccess::ReadOnly);
+
+    return fd.ioctl(request, static_cast<void*>(&data));
+}
+
+} // namespace estoraged
diff --git a/src/estoraged.cpp b/src/estoraged.cpp
index 6285079..f6176e4 100644
--- a/src/estoraged.cpp
+++ b/src/estoraged.cpp
@@ -4,6 +4,7 @@
 #include "cryptErase.hpp"
 #include "cryptsetupInterface.hpp"
 #include "pattern.hpp"
+#include "sanitize.hpp"
 #include "verifyDriveGeometry.hpp"
 #include "zero.hpp"
 
@@ -86,6 +87,8 @@
         }
         case EraseMethod::VendorSanitize:
         {
+            Sanitize mySanitize(devPath);
+            mySanitize.doSanitize();
             break;
         }
         case EraseMethod::ZeroOverWrite:
diff --git a/src/test/erase/sanitize_test.cpp b/src/test/erase/sanitize_test.cpp
new file mode 100644
index 0000000..4e18148
--- /dev/null
+++ b/src/test/erase/sanitize_test.cpp
@@ -0,0 +1,72 @@
+#include "estoraged_conf.hpp"
+#include "sanitize.hpp"
+
+#include <sys/ioctl.h>
+
+#include <stdplus/fd/managed.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+#include <string>
+#include <string_view>
+
+#include <gmock/gmock-matchers.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace estoraged_test
+{
+
+using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using ::testing::_;
+using ::testing::Return;
+
+class IOCTLWrapperMock : public estoraged::IOCTLWrapperInterface
+{
+  public:
+    MOCK_METHOD(int, doIoctl,
+                (std::string_view devPath, unsigned long request,
+                 struct mmc_ioc_cmd idata),
+                (override));
+
+    MOCK_METHOD(int, doIoctlMulti,
+                (std::string_view devPath, unsigned long request,
+                 struct estoraged::mmc_io_multi_cmd_erase),
+                (override));
+};
+
+// mock ioctl returns 0, and everything passes
+TEST(Sanitize, Successful)
+{
+    std::unique_ptr<IOCTLWrapperMock> mockIOCTL =
+        std::make_unique<IOCTLWrapperMock>();
+    IOCTLWrapperMock* mockPtr = mockIOCTL.get();
+    estoraged::Sanitize goodSanitize("/dev/null", std::move(mockIOCTL));
+    EXPECT_CALL(*mockPtr, doIoctl(_, _, _)).WillRepeatedly(Return(0));
+    EXPECT_CALL(*mockPtr, doIoctlMulti(_, _, _)).WillRepeatedly(Return(0));
+    EXPECT_NO_THROW(goodSanitize.doSanitize(52428800));
+}
+
+// doIoctlMulti returns -1, and Sanitize::emmcErase throws InternalFailure
+TEST(Sanitize, Sanitize_emmcErase)
+{
+    std::unique_ptr<IOCTLWrapperMock> mockIOCTL =
+        std::make_unique<IOCTLWrapperMock>();
+    IOCTLWrapperMock* mockPtr = mockIOCTL.get();
+    estoraged::Sanitize emptySanitize("", std::move(mockIOCTL));
+    EXPECT_CALL(*mockPtr, doIoctlMulti(_, _, _)).WillRepeatedly(Return(-1));
+    EXPECT_THROW(emptySanitize.doSanitize(4000000000), InternalFailure);
+}
+
+// mock ioctl returns 1, and emmcSanitize throws
+TEST(Sanitize, Sanitize_emmcSanitize)
+{
+    std::unique_ptr<IOCTLWrapperMock> mockIOCTL =
+        std::make_unique<IOCTLWrapperMock>();
+    IOCTLWrapperMock* mockPtr = mockIOCTL.get();
+    estoraged::Sanitize ioctlSanitize("/dev/null", std::move(mockIOCTL));
+    EXPECT_CALL(*mockPtr, doIoctl(_, _, _)).WillRepeatedly(Return(1));
+    EXPECT_CALL(*mockPtr, doIoctlMulti(_, _, _)).WillRepeatedly(Return(0));
+    EXPECT_THROW(ioctlSanitize.doSanitize(4000000000), InternalFailure);
+}
+
+} // namespace estoraged_test
diff --git a/src/test/meson.build b/src/test/meson.build
index b684d24..29cdda4 100644
--- a/src/test/meson.build
+++ b/src/test/meson.build
@@ -6,6 +6,7 @@
   'erase/pattern_test',
   'erase/zero_test',
   'erase/crypto_test',
+  'erase/sanitize_test',
   'estoraged_test',
 ]