text: // Copyright 2019 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "aml-rawnand.h"
  
  #include <lib/fake-bti/bti.h>
  #include <lib/fake_ddk/fake_ddk.h>
  #include <lib/zx/bti.h>
  #include <lib/zx/interrupt.h>
  
  #include <map>
  #include <memory>
  #include <queue>
  
  #include <ddk/io-buffer.h>
  #include <mock-mmio-reg/mock-mmio-reg.h>
  #include <soc/aml-common/aml-rawnand.h>
  #include <zxtest/zxtest.h>
  
  #include "onfi.h"
  
  namespace amlrawnand {
  
  namespace {
  
  // Amlogic NAND register info.
  constexpr size_t kNandRegSize = sizeof(uint32_t);
  constexpr size_t kNandRegCount = 14;
  constexpr size_t kClockRegSize = sizeof(uint32_t);
  constexpr size_t kClockRegCount = 1;
  
  // Toshiba TC58NVG2S0F NAND settings (taken from Astro).
  constexpr uint8_t kTestNandManufacturerId = 0x98;
  constexpr uint8_t kTestNandDeviceId = 0xDC;
  constexpr uint8_t kTestNandExtendedId = 0x26;
  constexpr uint32_t kTestNandWriteSize = 4 * 1024;  // Derived from extended ID.
  
  // Other configuration constants (Astro).
  constexpr uint32_t kNumBl2Pages = 1024;                // Based on BL2 partition size.
  constexpr uint32_t kFirstNonBl2Page = kNumBl2Pages;    // Redefined for test readability.
  constexpr int kDefaultNumEccPages = 4;                 // 4KiB NAND page / 1 KiB ECC page.
  constexpr int kDefaultNumUserBytes = 8;                // 4 ECC pages * 2 user bytes per page.
  constexpr uint32_t kDefaultWriteCommand = 0x00210004;  // Match what the bootloader uses.
  constexpr uint32_t kDefaultReadCommand = 0x00230004;   // Match what the bootloader uses.
  constexpr uint32_t kRandomSeedOffset = 0xC2;           // Match what the bootloader uses.
  
  constexpr uint16_t kPage0OobValue = 0xAA55;
  constexpr int kPage0NumEccPages = 8;                 // 8 ECC shortpages.
  constexpr uint32_t kPage0WriteCommand = 0x0029EC08;  // Match what the bootloader uses.
  constexpr uint32_t kPage0ReadCommand = 0x002BEC08;   // Match what the bootloader uses.
  // clang-format off
  constexpr uint8_t kPage0Data[] = {
    0x04, 0x00, 0xE3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
    0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
    0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  };
  // clang-format on
  
  // A test NAND page we can feed into AmlRawNand reads.
  struct NandPage {
    std::vector<uint8_t> data;
    std::vector<AmlInfoFormat> info;
  
    // Initializes in a valid state to allow successful reads.
    NandPage(size_t ecc_pages) : data(kTestNandWriteSize), info(ecc_pages) {
      for (auto& info_block : info) {
        info_block.ecc.completed = 1;
      }
    }
  
    // Default constructor creates a default (non-Page0) page.
    NandPage() : NandPage(kDefaultNumEccPages) {}
  };
  
  // Returns a NandPage that looks like a 0-page.
  NandPage NandPage0() {
    NandPage page0(kPage0NumEccPages);
    memcpy(&page0.data[0], kPage0Data, sizeof(kPage0Data));
    for (AmlInfoFormat& info_block : page0.info) {
      info_block.info_bytes = kPage0OobValue;
    }
    return page0;
  }
  
  // A stub Onfi implementation that just tracks the most recent command.
  class StubOnfi : public Onfi {
   public:
    void OnfiCommand(uint32_t command, int32_t column, int32_t page_addr, uint32_t capacity_mb,
                     uint32_t chip_delay_us, int buswidth_16) override {
      last_command_ = command;
      last_page_address_ = page_addr;
    }
  
    zx_status_t OnfiWait(zx::duration timeout, zx::duration first_interval,
                         zx::duration polling_interval) override {
      return ZX_OK;
    }
  
    uint32_t last_command() const { return last_command_; }
    int32_t last_page_address() const { return last_page_address_; }
  
   private:
    uint32_t last_command_ = 0;
    int32_t last_page_address_ = 0;
  };
  
  // Provides the necessary support to make AmlRawNand testable.
  //
  // Contains a fake page map that holds read/write information. Reading requires
  // first staging a fake page with SetFakePage(), but writing will just
  // overwrite whatever data is there or create a new fake page.
  class FakeAmlRawNand : public AmlRawNand {
   public:
    // Factory method so we can indicate failure by returning nullptr.
    static std::unique_ptr<FakeAmlRawNand> Create() {
      // Zircon objects required by AmlRawNand.
      zx::bti bti;
      EXPECT_OK(fake_bti_create(bti.reset_and_get_address()));
      if (!bti.is_valid()) {
        return nullptr;
      }
      zx::interrupt interrupt;
      EXPECT_OK(zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &interrupt));
      if (!interrupt.is_valid()) {
        return nullptr;
      }
  
      // We need to create these before the AmlRawNand object but also ensure that
      // the buffers don't move around so put them on the heap.
      auto mock_nand_regs = std::make_unique<ddk_mock::MockMmioReg[]>(kNandRegCount);
      auto mock_nand_reg_region = std::make_unique<ddk_mock::MockMmioRegRegion>(
          mock_nand_regs.get(), kNandRegSize, kNandRegCount);
      auto mock_clock_regs = std::make_unique<ddk_mock::MockMmioReg[]>(kClockRegCount);
      auto mock_clock_reg_region = std::make_unique<ddk_mock::MockMmioRegRegion>(
          mock_clock_regs.get(), kClockRegSize, kClockRegCount);
  
      // The AmlRawNand object owns the Onfi, but we retain a raw pointer to it
      // so we can interact with it during tests.
      auto stub_onfi = std::make_unique<StubOnfi>();
      auto stub_onfi_raw = stub_onfi.get();
  
      auto nand = std::unique_ptr<FakeAmlRawNand>(
          new FakeAmlRawNand(std::move(bti), std::move(interrupt), std::move(mock_nand_regs),
                             std::move(mock_nand_reg_region), std::move(mock_clock_regs),
                             std::move(mock_clock_reg_region), std::move(stub_onfi)));
      nand->stub_onfi_ = stub_onfi_raw;
  
      // Initialize the AmlRawNand with some parameters taken from a real device.
      nand->PrepareForInit();
      zx_status_t status = nand->Init();
      EXPECT_OK(status);
      if (status != ZX_OK) {
        return nullptr;
      }
  
      // Clear any pages we needed to set for proper initialization so we start
      // with a blank slate for tests.
      nand->fake_page_map_.clear();
  
      return nand;
    }
  
    // On test exit, make sure we met all the expectations we had.
    ~FakeAmlRawNand() override {
      CleanUpIrq();
      EXPECT_TRUE(fake_read_bytes_.empty());
      mock_nand_reg_region_->VerifyAll();
      mock_clock_reg_region_->VerifyAll();
    }
  
    // Returns the current fake data at the given page.
    //
    // If the page hasn't been staged or written yet, returns an empty page
    // but marks a test failure.
    const NandPage& GetFakePage(uint32_t index) {
      auto iter = fake_page_map_.find(index);
      if (iter == fake_page_map_.end()) {
        ADD_FAILURE("NandPage %u hasn't been staged or written yet", index);
        return fake_page_map_[index] = NandPage();
      }
      return iter->second;
    }
  
    // Sets a fake NAND page for RawNandReadPageHwecc(), overwriting any page
    // data current at this index.
    void SetFakePage(uint32_t index, NandPage page) { fake_page_map_[index] = std::move(page); }
  
    // Returns true if the fake page has been set or written.
    bool FakePageExists(uint32_t index) const { return fake_page_map_.count(index) > 0; }
  
    // Queues a fake NAND byte for AmlReadByte().
    void QueueFakeNandByteRead(uint8_t byte) { fake_read_bytes_.push(byte); }
  
    // Sets up an expectation for the given read/write command and its
    // corresponding setup commands, including the "set seed" command if
    // |random_seed| is present.
    static constexpr auto kNoRandomSeed = std::nullopt;  // Make call sites more readable.
    void ExpectReadWriteCommand(uint32_t command, std::optional<uint32_t> random_seed) {
      ddk_mock::MockMmioReg& command_register = (*mock_nand_reg_region_)[P_NAND_CMD];
  
      // First we expect to configure the data and info buffer physical addresses.
      const auto data_addr = data_buffer().phys();
      const auto info_addr = info_buffer().phys();
      EXPECT_NE(0, data_addr);
      EXPECT_NE(0, info_addr);
      command_register.ExpectWrite(AML_CMD_ADL | (data_addr & 0xFFFF));
      command_register.ExpectWrite(AML_CMD_ADH | ((data_addr >> 16) & 0xFFFF));
      command_register.ExpectWrite(AML_CMD_AIL | (info_addr & 0xFFFF));
      command_register.ExpectWrite(AML_CMD_AIH | ((info_addr >> 16) & 0xFFFF));
  
      // Then we may set the random seed if randomization is used.
      if (random_seed) {
        // Note: we are intentionally masking *before* adding the offset. This
        // seems incorrect according to the docs, but is what the bootloader does,
        // and we have to match its behavior in order to make sure we're always
        // using matching seed values.
        command_register.ExpectWrite(AML_CMD_SEED |
                                     (kRandomSeedOffset + (random_seed.value() & 0x7FFF)));
      }
  
      // Finally we expect the actual read/write command.
      command_register.ExpectWrite(command);
    }
  
   protected:
    zx_status_t AmlQueueRB() override { return ZX_OK; }
  
    zx_status_t AmlWaitDmaFinish() override {
      switch (stub_onfi_->last_command()) {
        case NAND_CMD_READ0:
          return PerformFakeRead(stub_onfi_->last_page_address());
        case NAND_CMD_SEQIN:
          return PerformFakeWrite(stub_onfi_->last_page_address());
      }
  
      ADD_FAILURE("AmlWaitDmaFinish() called with unknown Onfi command 0x%02X",
                  stub_onfi_->last_command());
      return ZX_ERR_INTERNAL;
    }
  
    uint8_t AmlReadByte() override {
      if (fake_read_bytes_.empty()) {
        ADD_FAILURE("AmlReadByte() called with no fake bytes ready");
        return 0x00;
      }
      uint8_t byte = fake_read_bytes_.front();
      fake_read_bytes_.pop();
      return byte;
    }
  
   private:
    FakeAmlRawNand(zx::bti bti, zx::interrupt interrupt,
                   std::unique_ptr<ddk_mock::MockMmioReg[]> mock_nand_regs,
                   std::unique_ptr<ddk_mock::MockMmioRegRegion> mock_nand_reg_region,
                   std::unique_ptr<ddk_mock::MockMmioReg[]> mock_clock_regs,
                   std::unique_ptr<ddk_mock::MockMmioRegRegion> mock_clock_reg_region,
                   std::unique_ptr<Onfi> onfi)
        : AmlRawNand(fake_ddk::kFakeParent, ddk::MmioBuffer(mock_nand_reg_region->GetMmioBuffer()),
                     ddk::MmioBuffer(mock_clock_reg_region->GetMmioBuffer()), std::move(bti),
                     std::move(interrupt), std::move(onfi)),
          mock_nand_regs_(std::move(mock_nand_regs)),
          mock_nand_reg_region_(std::move(mock_nand_reg_region)),
          mock_clock_regs_(std::move(mock_clock_regs)),
          mock_clock_reg_region_(std::move(mock_clock_reg_region)) {}
  
    // Sets up the necessary fake page and byte reads to successfully initialize
    // the AmlRawNand object.
    void PrepareForInit() {
      // First we read the first 2 ID bytes.
      QueueFakeNandByteRead(kTestNandManufacturerId);
      QueueFakeNandByteRead(kTestNandDeviceId);
  
      // Next we read the full 8-byte ID string, of which we only care about
      // a few bytes.
      QueueFakeNandByteRead(kTestNandManufacturerId);
      QueueFakeNandByteRead(kTestNandDeviceId);
      QueueFakeNandByteRead(0x00);
      QueueFakeNandByteRead(kTestNandExtendedId);
      QueueFakeNandByteRead(0x00);
      QueueFakeNandByteRead(0x00);
      QueueFakeNandByteRead(0x00);
      QueueFakeNandByteRead(0x00);
  
      // Next we read the page0 metadata.
      SetFakePage(0, NandPage0());
    }
  
    zx_status_t PerformFakeRead(uint32_t page_index) {
      auto iter = fake_page_map_.find(page_index);
      if (iter == fake_page_map_.end()) {
        ADD_FAILURE("PerformFakeRead(): page %u hasn't been set", page_index);
        return ZX_ERR_INTERNAL;
      }
  
      const NandPage& page = iter->second;
  
      const size_t data_bytes = page.data.size() * sizeof(page.data[0]);
      const size_t info_bytes = page.info.size() * sizeof(page.info[0]);
  
      // Make sure the buffers are big enough to hold the page.
      if (data_buffer().size() < data_bytes) {
        ADD_FAILURE("Fake page data size is larger than the buffer");
        return ZX_ERR_BUFFER_TOO_SMALL;
      }
      if (info_buffer().size() < info_bytes) {
        ADD_FAILURE("Fake page info size is larger than the buffer");
        return ZX_ERR_BUFFER_TOO_SMALL;
      }
  
      memcpy(data_buffer().virt(), &page.data[0], data_bytes);
      memcpy(info_buffer().virt(), &page.info[0], info_bytes);
  
      return ZX_OK;
    }
  
    zx_status_t PerformFakeWrite(uint32_t page_index) {
      // We could calculate whether |page_index| is a page0 metadata page or not,
      // but it doesn't matter since we're just allocating buffers, so we always
      // make it a page0 which has the larger OOB buffer.
      NandPage& page = fake_page_map_[page_index] = NandPage0();
  
      const size_t data_bytes = page.data.size() * sizeof(page.data[0]);
      const size_t info_bytes = page.info.size() * sizeof(page.info[0]);
  
      // Make sure the buffers are big enough to fill up the page.
      if (data_buffer().size() < data_bytes) {
        ADD_FAILURE("Fake page data size is larger than the buffer");
        return ZX_ERR_BUFFER_TOO_SMALL;
      }
      if (info_buffer().size() < info_bytes) {
        ADD_FAILURE("Fake page info size is larger than the buffer");
        return ZX_ERR_BUFFER_TOO_SMALL;
      }
  
      memcpy(&page.data[0], data_buffer().virt(), data_bytes);
      // Since AmlInfoFormat isn't trivially copyable, we can't memcpy() directly
      // into it but have to copy each field individually instead.
      const uint8_t* buffer = reinterpret_cast<const uint8_t*>(info_buffer().virt());
      for (AmlInfoFormat& info : page.info) {
        static_assert(sizeof(info.info_bytes) == 2);
        memcpy(&info.info_bytes, buffer, 2);
        buffer += 2;
  
        static_assert(sizeof(info.zero_bits) == 1);
        info.zero_bits = *(buffer++);
  
        static_assert(sizeof(info.ecc.raw_value) == 1);
        info.ecc.raw_value = *(buffer++);
  
        static_assert(sizeof(info.reserved) == 4);
        memcpy(&info.reserved, buffer, 4);
        buffer += 4;
      }
  
      return ZX_OK;
    }
  
    std::unique_ptr<ddk_mock::MockMmioReg[]> mock_nand_regs_;
    std::unique_ptr<ddk_mock::MockMmioRegRegion> mock_nand_reg_region_;
  
    std::unique_ptr<ddk_mock::MockMmioReg[]> mock_clock_regs_;
    std::unique_ptr<ddk_mock::MockMmioRegRegion> mock_clock_reg_region_;
  
    StubOnfi* stub_onfi_;
  
    std::map<uint32_t, NandPage> fake_page_map_;
    std::queue<uint8_t> fake_read_bytes_;
  };
  
  TEST(AmlRawnand, FakeNandCreate) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  }
  
  TEST(AmlRawnand, ReadPage) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    NandPage page;
    page.data.front() = 0x55;
    page.data.back() = 0xAA;
    page.info.front().info_bytes = 0x1234;
    page.info.back().info_bytes = 0xABCD;
    ASSERT_NO_FATAL_FAILURES(nand->SetFakePage(5, page));
  
    std::vector<uint8_t> data(kTestNandWriteSize);
    std::vector<uint16_t> oob(kDefaultNumUserBytes / 2);  // /2 for 16-bit values.
    size_t data_bytes_read = 0;
    size_t oob_bytes_read = 0;
    uint32_t ecc_correct = -1;
    ASSERT_OK(nand->RawNandReadPageHwecc(5, &data[0], kTestNandWriteSize, &data_bytes_read, &oob[0],
                                         kDefaultNumUserBytes, &oob_bytes_read, &ecc_correct));
  
    EXPECT_EQ(kTestNandWriteSize, data_bytes_read);
    EXPECT_EQ(kDefaultNumUserBytes, oob_bytes_read);
    EXPECT_EQ(0, ecc_correct);
    EXPECT_EQ(0x55, data.front());
    EXPECT_EQ(0xAA, data.back());
    EXPECT_EQ(0x1234, oob.front());
    EXPECT_EQ(0xABCD, oob.back());
  }
  
  TEST(AmlRawnand, ReadPageDataOnly) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    NandPage page;
    page.data.front() = 0x55;
    page.data.back() = 0xAA;
    ASSERT_NO_FATAL_FAILURES(nand->SetFakePage(5, page));
  
    std::vector<uint8_t> data(kTestNandWriteSize);
    size_t data_bytes_read = 0;
    uint32_t ecc_correct = -1;
    ASSERT_OK(nand->RawNandReadPageHwecc(5, &data[0], kTestNandWriteSize, &data_bytes_read, nullptr,
                                         0, nullptr, &ecc_correct));
  
    EXPECT_EQ(kTestNandWriteSize, data_bytes_read);
    EXPECT_EQ(0, ecc_correct);
    EXPECT_EQ(0x55, data.front());
    EXPECT_EQ(0xAA, data.back());
  }
  
  TEST(AmlRawnand, ReadPageOobOnly) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    NandPage page;
    page.info.front().info_bytes = 0x1234;
    page.info.back().info_bytes = 0xABCD;
    ASSERT_NO_FATAL_FAILURES(nand->SetFakePage(5, page));
  
    std::vector<uint16_t> oob(kDefaultNumUserBytes / 2);
    size_t oob_bytes_read = 0;
    uint32_t ecc_correct = -1;
    ASSERT_OK(nand->RawNandReadPageHwecc(5, nullptr, 0, nullptr, &oob[0], kDefaultNumUserBytes,
                                         &oob_bytes_read, &ecc_correct));
  
    EXPECT_EQ(kDefaultNumUserBytes, oob_bytes_read);
    EXPECT_EQ(0, ecc_correct);
    EXPECT_EQ(0x1234, oob.front());
    EXPECT_EQ(0xABCD, oob.back());
  }
  
  TEST(AmlRawnand, WritePage) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    std::vector<uint8_t> data(kTestNandWriteSize);
    std::vector<uint16_t> oob(kDefaultNumEccPages);
    data[0] = 0x11;
    data[kTestNandWriteSize - 1] = 0x22;
    oob[0] = 0x5566;
    oob[kDefaultNumEccPages - 1] = 0xAABB;
    // We have to write to a page index outside of BL2 because all BL2 pages
    // require special OOB values.
    ASSERT_OK(nand->RawNandWritePageHwecc(&data[0], kTestNandWriteSize, &oob[0], kDefaultNumUserBytes,
                                          kFirstNonBl2Page));
  
    const NandPage& page = nand->GetFakePage(kFirstNonBl2Page);
    EXPECT_EQ(0x11, page.data[0]);
    EXPECT_EQ(0x22, page.data[kTestNandWriteSize - 1]);
    EXPECT_EQ(0x5566, page.info[0].info_bytes);
    EXPECT_EQ(0xAABB, page.info[kDefaultNumEccPages - 1].info_bytes);
  }
  
  TEST(AmlRawnand, WritePageDataOnly) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    std::vector<uint8_t> data(kTestNandWriteSize);
    data[0] = 0x11;
    data[kTestNandWriteSize - 1] = 0x22;
    ASSERT_OK(
        nand->RawNandWritePageHwecc(&data[0], kTestNandWriteSize, nullptr, 0, kFirstNonBl2Page));
  
    const NandPage& page = nand->GetFakePage(kFirstNonBl2Page);
    EXPECT_EQ(0x11, page.data[0]);
    EXPECT_EQ(0x22, page.data[kTestNandWriteSize - 1]);
  }
  
  TEST(AmlRawnand, WritePageOobOnly) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    std::vector<uint16_t> oob(kDefaultNumEccPages);
    oob[0] = 0x5566;
    oob[kDefaultNumEccPages - 1] = 0xAABB;
    ASSERT_OK(
        nand->RawNandWritePageHwecc(nullptr, 0, &oob[0], kDefaultNumUserBytes, kFirstNonBl2Page));
  
    const NandPage& page = nand->GetFakePage(kFirstNonBl2Page);
    EXPECT_EQ(0x5566, page.info[0].info_bytes);
    EXPECT_EQ(0xAABB, page.info[kDefaultNumEccPages - 1].info_bytes);
  }
  
  TEST(AmlRawnand, WritePageShortOob) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    std::vector<uint16_t> oob(kDefaultNumEccPages);
    oob[0] = 0x1234;
    oob[1] = 0x5678;
    ASSERT_OK(nand->RawNandWritePageHwecc(nullptr, 0, &oob[0], 2, kFirstNonBl2Page));
  
    // The driver should pad with zeros since we only told it to use 2 OOB bytes.
    const NandPage& page = nand->GetFakePage(kFirstNonBl2Page);
    EXPECT_EQ(0x1234, page.info[0].info_bytes);
    EXPECT_EQ(0x0000, page.info[1].info_bytes);
    EXPECT_EQ(0x0000, page.info[kDefaultNumEccPages - 1].info_bytes);
  }
  
  TEST(AmlRawnand, WritePageShortOobOddBytes) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    std::vector<uint16_t> oob(kDefaultNumEccPages);
    oob[0] = 0x1234;
    oob[1] = 0x5678;
    ASSERT_OK(nand->RawNandWritePageHwecc(nullptr, 0, &oob[0], 3, kFirstNonBl2Page));
  
    // The driver should pad with zeros since we only told it to use 3 OOB bytes.
    const NandPage& page = nand->GetFakePage(kFirstNonBl2Page);
    EXPECT_EQ(0x1234, page.info[0].info_bytes);
    EXPECT_EQ(0x0078, page.info[1].info_bytes);  // Little-endian: LSB comes first.
    EXPECT_EQ(0x0000, page.info[kDefaultNumEccPages - 1].info_bytes);
  }
  
  TEST(AmlRawnand, WritePageShortOobZeroBytes) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    std::vector<uint16_t> oob(kDefaultNumEccPages);
    oob[0] = 0x1234;
    ASSERT_OK(nand->RawNandWritePageHwecc(nullptr, 0, &oob[0], 0, kFirstNonBl2Page));
  
    // The driver should pad with zeros since we told it to use 0 OOB bytes.
    const NandPage& page = nand->GetFakePage(kFirstNonBl2Page);
    EXPECT_EQ(0x0000, page.info[0].info_bytes);
    EXPECT_EQ(0x0000, page.info[kDefaultNumEccPages - 1].info_bytes);
  }
  
  TEST(AmlRawnand, WriteBl2Page) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    const uint32_t page_index = kNumBl2Pages - 1;
    std::vector<uint8_t> data(kTestNandWriteSize);
    data[0] = 0x11;
    data[kTestNandWriteSize - 1] = 0x22;
    ASSERT_OK(nand->RawNandWritePageHwecc(&data[0], kTestNandWriteSize, nullptr, 0, page_index));
  
    const NandPage& page = nand->GetFakePage(page_index);
    EXPECT_EQ(0x11, page.data[0]);
    EXPECT_EQ(0x22, page.data[kTestNandWriteSize - 1]);
  
    // We didn't supply any OOB bytes, but the driver should have automatically
    // used the magic BL2 values for each page.
    ASSERT_GE(page.info.size(), kPage0NumEccPages);
    for (int i = 0; i < kPage0NumEccPages; ++i) {
      EXPECT_EQ(kPage0OobValue, page.info[i].info_bytes);
    }
  }
  
  TEST(AmlRawnand, WriteBl2PageInvalidOobError) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    std::vector<uint8_t> data(kTestNandWriteSize);
    std::vector<uint16_t> oob(kDefaultNumEccPages);
  
    // The driver should refuse to write custom OOB bytes to BL2 pages.
    for (uint32_t page_index : {0u, kNumBl2Pages / 2, kNumBl2Pages - 1}) {
      ASSERT_EQ(nand->RawNandWritePageHwecc(&data[0], kTestNandWriteSize, &oob[0],
                                            kDefaultNumUserBytes, page_index),
                ZX_ERR_INVALID_ARGS);
  
      // Make sure we never attempted to write.
      EXPECT_FALSE(nand->FakePageExists(page_index));
    }
  }
  
  // Read/write command tests - ensure we're sending the right commands to the
  // NAND control registers.
  
  TEST(AmlRawnand, WritePage0Command) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    // We expect randomization to always be on for page0 metadata pages.
    nand->ExpectReadWriteCommand(kPage0WriteCommand, 0);
  
    std::vector<uint8_t> data(kTestNandWriteSize);
    ASSERT_OK(nand->RawNandWritePageHwecc(&data[0], kTestNandWriteSize, nullptr, 0, 0));
  }
  
  TEST(AmlRawnand, ReadPage0Command) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    // We expect randomization to always be on for page0 metadata pages.
    nand->ExpectReadWriteCommand(kPage0ReadCommand, 0);
    nand->SetFakePage(0, NandPage0());
  
    std::vector<uint8_t> data(kTestNandWriteSize);
    size_t data_bytes_read = 0;
    uint32_t ecc_correct = -1;
    ASSERT_OK(nand->RawNandReadPageHwecc(0, &data[0], kTestNandWriteSize, &data_bytes_read, nullptr,
                                         0, nullptr, &ecc_correct));
  }
  
  TEST(AmlRawnand, WriteBl2Command) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    // We don't use randomization for other BL2 pages.
    nand->ExpectReadWriteCommand(kDefaultWriteCommand, FakeAmlRawNand::kNoRandomSeed);
  
    std::vector<uint8_t> data(kTestNandWriteSize);
    ASSERT_OK(nand->RawNandWritePageHwecc(&data[0], kTestNandWriteSize, nullptr, 0, 1));
  }
  
  TEST(AmlRawnand, ReadBl2Command) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    // We don't use randomization for other BL2 pages.
    nand->ExpectReadWriteCommand(kDefaultReadCommand, FakeAmlRawNand::kNoRandomSeed);
    nand->SetFakePage(1, NandPage());
  
    std::vector<uint8_t> data(kTestNandWriteSize);
    size_t data_bytes_read = 0;
    uint32_t ecc_correct = -1;
    ASSERT_OK(nand->RawNandReadPageHwecc(1, &data[0], kTestNandWriteSize, &data_bytes_read, nullptr,
                                         0, nullptr, &ecc_correct));
  }
  
  TEST(AmlRawnand, WriteCommand) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    // We don't use randomization for normal pages.
    nand->ExpectReadWriteCommand(kDefaultWriteCommand, FakeAmlRawNand::kNoRandomSeed);
  
    std::vector<uint8_t> data(kTestNandWriteSize);
    ASSERT_OK(
        nand->RawNandWritePageHwecc(&data[0], kTestNandWriteSize, nullptr, 0, kFirstNonBl2Page));
  }
  
  TEST(AmlRawnand, ReadCommand) {
    auto nand = FakeAmlRawNand::Create();
    ASSERT_NOT_NULL(nand);
  
    // We don't use randomization for normal pages.
    nand->ExpectReadWriteCommand(kDefaultReadCommand, FakeAmlRawNand::kNoRandomSeed);
    nand->SetFakePage(kFirstNonBl2Page, NandPage());
  
    std::vector<uint8_t> data(kTestNandWriteSize);
    size_t data_bytes_read = 0;
    uint32_t ecc_correct = -1;
    ASSERT_OK(nand->RawNandReadPageHwecc(kFirstNonBl2Page, &data[0], kTestNandWriteSize,
                                         &data_bytes_read, nullptr, 0, nullptr, &ecc_correct));
  }
  
  }  // namespace
  
  }  // namespace amlrawnand
  