| // 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 <fcntl.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <set> |
| |
| #include <lib/ftl-mtd/nand-volume-driver.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "fake-nand-interface.h" |
| |
| using namespace ftl_mtd; |
| |
| namespace { |
| |
| constexpr uint32_t kOobSizeDefault = 128; // Produces page_multiplier of 1. |
| constexpr uint32_t kOobSizeNeedsMultiplier2 = 8; // Produces page_multiplier of 2. |
| constexpr uint32_t kPageSize = 4 * 1024; // 4 KiB |
| constexpr uint32_t kBlockSize = 256 * 1024; // 256 KiB |
| constexpr uint32_t kSize = 64 * 1024 * 1024; // 64 MiB |
| constexpr uint32_t kMaxBadBlocks = 10; |
| |
| class NandVolumeDriverTest : public zxtest::Test { |
| protected: |
| void SetUpDriver(uint32_t block_offset, uint32_t group_size, uint32_t oob_size) { |
| page_multiplier_ = std::max(1u, kMinimumOobSize / oob_size); |
| oob_size_ = oob_size; |
| group_size_ = group_size; |
| |
| read_page_buffer_ = std::make_unique<uint8_t[]>( |
| group_size_ * kPageSize * page_multiplier_); |
| read_oob_buffer_ = std::make_unique<uint8_t[]>(group_size_ * oob_size_ * page_multiplier_); |
| |
| write_page_buffer_ = std::make_unique<uint8_t[]>( |
| group_size_ * kPageSize * page_multiplier_); |
| write_oob_buffer_ = std::make_unique<uint8_t[]>(group_size_ * oob_size_ * page_multiplier_); |
| |
| auto intf = std::make_unique<FakeNandInterface>(kPageSize, oob_size_, kBlockSize, kSize); |
| interface_ = intf.get(); |
| |
| ASSERT_OK(NandVolumeDriver::Create(block_offset, kMaxBadBlocks, std::move(intf), |
| &nand_volume_driver_)); |
| ASSERT_NULL(nand_volume_driver_->Init()); |
| } |
| |
| uint32_t PageBufferSize() { |
| return group_size_ * kPageSize * page_multiplier_; |
| } |
| |
| uint32_t OobBufferSize() { |
| return group_size_ * oob_size_ * page_multiplier_; |
| } |
| |
| void SetWritePageBufferData(uint8_t value) { |
| memset(write_page_buffer_.get(), value, PageBufferSize()); |
| } |
| |
| void SetWriteOobBufferData(uint8_t value) { |
| memset(write_oob_buffer_.get(), value, OobBufferSize()); |
| } |
| |
| void SetReadPageBufferData(uint8_t value) { |
| memset(read_page_buffer_.get(), value, PageBufferSize()); |
| } |
| |
| void SetReadOobBufferData(uint8_t value) { |
| memset(read_oob_buffer_.get(), value, OobBufferSize()); |
| } |
| |
| std::unique_ptr<uint8_t[]> read_page_buffer_; |
| std::unique_ptr<uint8_t[]> read_oob_buffer_; |
| std::unique_ptr<uint8_t[]> write_page_buffer_; |
| std::unique_ptr<uint8_t[]> write_oob_buffer_; |
| |
| uint32_t page_multiplier_; |
| uint32_t oob_size_; |
| uint32_t group_size_; |
| |
| FakeNandInterface* interface_; |
| std::unique_ptr<NandVolumeDriver> nand_volume_driver_; |
| }; |
| |
| TEST_F(NandVolumeDriverTest, WriteAllSucceeds) { |
| // Start on block 2 (0-indexed). |
| // Try to write all pages, 4 at a time. |
| uint32_t block_offset = 2; |
| uint32_t group_size = 4; |
| |
| SetUpDriver(block_offset, group_size, kOobSizeDefault); |
| SetWritePageBufferData(0x12); |
| SetWriteOobBufferData(0x89); |
| |
| uint32_t byte_offset = block_offset * kBlockSize; |
| uint32_t num_pages = (kSize - byte_offset) / (page_multiplier_ * kPageSize); |
| |
| for (uint32_t page = 0; page < num_pages; page += group_size) { |
| ASSERT_EQ(ftl::kNdmOk, nand_volume_driver_->NandWrite(page, group_size, |
| write_page_buffer_.get(), |
| write_oob_buffer_.get())); |
| } |
| |
| for (uint32_t offset = byte_offset; offset < kSize; offset += kPageSize) { |
| SetReadPageBufferData(0xFF); |
| SetReadOobBufferData(0xFF); |
| |
| uint32_t actual; |
| ASSERT_OK(interface_->ReadPage(offset, read_page_buffer_.get(), &actual)); |
| ASSERT_OK(interface_->ReadOob(offset, read_oob_buffer_.get())); |
| |
| ASSERT_BYTES_EQ(read_page_buffer_.get(), write_page_buffer_.get(), kPageSize); |
| ASSERT_BYTES_EQ(read_oob_buffer_.get(), write_oob_buffer_.get(), oob_size_); |
| } |
| } |
| |
| TEST_F(NandVolumeDriverTest, WriteAllWithPageMultiplierSucceeds) { |
| // Start on block 4 (0-indexed). |
| // Try to write all pages, 2 at a time with page multiplier. |
| uint32_t block_offset = 4; |
| uint32_t group_size = 2; |
| |
| SetUpDriver(block_offset, group_size, kOobSizeNeedsMultiplier2); |
| SetWritePageBufferData(0x01); |
| SetWriteOobBufferData(0x78); |
| |
| uint32_t byte_offset = block_offset * kBlockSize; |
| uint32_t num_pages = (kSize - byte_offset) / (page_multiplier_ * kPageSize); |
| |
| for (uint32_t page = 0; page < num_pages; page += group_size) { |
| ASSERT_EQ(ftl::kNdmOk, nand_volume_driver_->NandWrite(page, group_size, |
| write_page_buffer_.get(), |
| write_oob_buffer_.get())); |
| } |
| |
| for (uint32_t offset = byte_offset; offset < kSize; offset += kPageSize) { |
| SetReadPageBufferData(0xFF); |
| SetReadOobBufferData(0xFF); |
| |
| uint32_t actual; |
| ASSERT_OK(interface_->ReadPage(offset, read_page_buffer_.get(), &actual)); |
| ASSERT_OK(interface_->ReadOob(offset, read_oob_buffer_.get())); |
| |
| ASSERT_BYTES_EQ(read_page_buffer_.get(), write_page_buffer_.get(), kPageSize); |
| ASSERT_BYTES_EQ(read_oob_buffer_.get(), write_oob_buffer_.get(), oob_size_); |
| } |
| } |
| |
| TEST_F(NandVolumeDriverTest, BadWriteReportsError) { |
| SetUpDriver(0, 1, kOobSizeDefault); |
| |
| // Attempt to write to non-existent page. |
| ASSERT_EQ(ftl::kNdmFatalError, nand_volume_driver_->NandWrite(kSize, 1, |
| write_page_buffer_.get(), |
| write_oob_buffer_.get())); |
| |
| // Bad read from interface should result in an error. |
| interface_->set_fail_write(true); |
| ASSERT_EQ(ftl::kNdmError, nand_volume_driver_->NandWrite(0, 1, write_page_buffer_.get(), |
| write_oob_buffer_.get())); |
| } |
| |
| TEST_F(NandVolumeDriverTest, ReadAllSucceeds) { |
| // Start on block 16 (0-indexed). |
| // Try to read all pages, 2 at a time. |
| uint32_t block_offset = 16; |
| uint32_t group_size = 2; |
| |
| SetUpDriver(block_offset, group_size, kOobSizeDefault); |
| SetWritePageBufferData(0x23); |
| SetWriteOobBufferData(0xA1); |
| |
| uint32_t byte_offset = block_offset * kBlockSize; |
| for (uint32_t offset = byte_offset; offset < kSize; offset += kPageSize) { |
| ASSERT_OK(interface_->WritePage(offset, write_page_buffer_.get(), write_oob_buffer_.get())); |
| } |
| |
| uint32_t num_pages = (kSize - byte_offset) / (page_multiplier_ * kPageSize); |
| for (uint32_t page = 0; page < num_pages; page += group_size) { |
| SetReadPageBufferData(0xFF); |
| SetReadOobBufferData(0xFF); |
| |
| ASSERT_EQ(ftl::kNdmOk, nand_volume_driver_->NandRead(page, group_size, |
| read_page_buffer_.get(), |
| read_oob_buffer_.get())); |
| |
| ASSERT_BYTES_EQ(write_page_buffer_.get(), read_page_buffer_.get(), PageBufferSize()); |
| ASSERT_BYTES_EQ(write_oob_buffer_.get(), read_oob_buffer_.get(), OobBufferSize()); |
| } |
| } |
| |
| TEST_F(NandVolumeDriverTest, ReadAllWithPageMultiplierSucceeds) { |
| // Start on block 1 (0-indexed). |
| // Try to read all pages, 1 at a time with page multiplier. |
| uint32_t block_offset = 1; |
| uint32_t group_size = 1; |
| |
| SetUpDriver(block_offset, group_size, kOobSizeNeedsMultiplier2); |
| SetWritePageBufferData(0xF0); |
| SetWriteOobBufferData(0x6E); |
| |
| uint32_t byte_offset = block_offset * kBlockSize; |
| for (uint32_t offset = byte_offset; offset < kSize; offset += kPageSize) { |
| ASSERT_OK(interface_->WritePage(offset, write_page_buffer_.get(), write_oob_buffer_.get())); |
| } |
| |
| uint32_t num_pages = (kSize - byte_offset) / (page_multiplier_ * kPageSize); |
| for (uint32_t page = 0; page < num_pages; page += group_size) { |
| SetReadPageBufferData(0xFF); |
| SetReadOobBufferData(0xFF); |
| |
| ASSERT_EQ(ftl::kNdmOk, nand_volume_driver_->NandRead(page, group_size, |
| read_page_buffer_.get(), |
| read_oob_buffer_.get())); |
| |
| ASSERT_BYTES_EQ(write_page_buffer_.get(), read_page_buffer_.get(), PageBufferSize()); |
| ASSERT_BYTES_EQ(write_oob_buffer_.get(), read_oob_buffer_.get(), OobBufferSize()); |
| } |
| } |
| |
| TEST_F(NandVolumeDriverTest, BadReadReportsFatalError) { |
| SetUpDriver(0, 1, kOobSizeNeedsMultiplier2); |
| |
| // Attempt to read from non-existent page should fail fatally. |
| ASSERT_EQ(ftl::kNdmFatalError, |
| nand_volume_driver_->NandRead(kSize, 1, read_page_buffer_.get(), |
| read_oob_buffer_.get())); |
| |
| // Bad read from interface should result in an error. |
| interface_->set_fail_read(true); |
| ASSERT_EQ(ftl::kNdmFatalError, |
| nand_volume_driver_->NandRead(0, 1, read_page_buffer_.get(), nullptr)); |
| ASSERT_EQ(ftl::kNdmFatalError, |
| nand_volume_driver_->NandRead(0, 1, nullptr, read_oob_buffer_.get())); |
| } |
| |
| TEST_F(NandVolumeDriverTest, ShortReadReportsError) { |
| SetUpDriver(0, 1, kOobSizeDefault); |
| |
| // Say no data was actually read. |
| interface_->set_read_actual(0); |
| ASSERT_EQ(ftl::kNdmFatalError, |
| nand_volume_driver_->NandRead(0, 1, read_page_buffer_.get(), read_oob_buffer_.get())); |
| } |
| |
| TEST_F(NandVolumeDriverTest, EraseAllSucceeds) { |
| // Start on block 9 (0-indexed). |
| // Try to read all pages, 2 at a time. |
| uint32_t block_offset = 9; |
| |
| SetUpDriver(block_offset, 1, kOobSizeDefault); |
| SetWritePageBufferData(0x2A); |
| SetWriteOobBufferData(0xBD); |
| |
| uint32_t byte_offset = block_offset * kBlockSize; |
| for (uint32_t offset = byte_offset; offset < kSize; offset += kPageSize) { |
| ASSERT_OK(interface_->WritePage(offset, write_page_buffer_.get(), write_oob_buffer_.get())); |
| } |
| |
| uint32_t num_pages = (kSize - byte_offset) / (page_multiplier_ * kPageSize); |
| for (uint32_t page = 0; page < num_pages; page++) { |
| ASSERT_EQ(ftl::kNdmOk, nand_volume_driver_->NandErase(page)); |
| } |
| |
| // After erasure, expect 0xFF to be returned after a read. |
| SetWritePageBufferData(0xFF); |
| SetWriteOobBufferData(0xFF); |
| |
| for (uint32_t offset = byte_offset; offset < kSize; offset += kPageSize) { |
| SetReadPageBufferData(0); |
| SetReadOobBufferData(0); |
| |
| uint32_t actual; |
| ASSERT_OK(interface_->ReadPage(offset, read_page_buffer_.get(), &actual)); |
| ASSERT_OK(interface_->ReadOob(offset, read_oob_buffer_.get())); |
| |
| ASSERT_BYTES_EQ(write_page_buffer_.get(), read_page_buffer_.get(), kPageSize); |
| ASSERT_BYTES_EQ(write_oob_buffer_.get(), read_oob_buffer_.get(), oob_size_); |
| } |
| } |
| |
| TEST_F(NandVolumeDriverTest, EraseAllWithPageMultiplierSucceeds) { |
| // Start on block 9 (0-indexed). |
| // Try to read all pages, 2 at a time. |
| uint32_t block_offset = 9; |
| |
| SetUpDriver(block_offset, 1, kOobSizeNeedsMultiplier2); |
| SetWritePageBufferData(0x2A); |
| SetWriteOobBufferData(0xBD); |
| |
| uint32_t byte_offset = block_offset * kBlockSize; |
| for (uint32_t offset = byte_offset; offset < kSize; offset += kPageSize) { |
| ASSERT_OK(interface_->WritePage(offset, write_page_buffer_.get(), write_oob_buffer_.get())); |
| } |
| |
| uint32_t num_pages = (kSize - byte_offset) / (page_multiplier_ * kPageSize); |
| for (uint32_t page = 0; page < num_pages; page++) { |
| ASSERT_EQ(ftl::kNdmOk, nand_volume_driver_->NandErase(page)); |
| } |
| |
| // After erasure, expect 0xFF to be returned after a read. |
| SetWritePageBufferData(0xFF); |
| SetWriteOobBufferData(0xFF); |
| |
| for (uint32_t offset = byte_offset; offset < kSize; offset += kPageSize) { |
| SetReadPageBufferData(0); |
| SetReadOobBufferData(0); |
| |
| uint32_t actual; |
| ASSERT_OK(interface_->ReadPage(offset, read_page_buffer_.get(), &actual)); |
| ASSERT_OK(interface_->ReadOob(offset, read_oob_buffer_.get())); |
| |
| ASSERT_BYTES_EQ(write_page_buffer_.get(), read_page_buffer_.get(), kPageSize); |
| ASSERT_BYTES_EQ(write_oob_buffer_.get(), read_oob_buffer_.get(), oob_size_); |
| } |
| } |
| |
| TEST_F(NandVolumeDriverTest, BadEraseReportsError) { |
| SetUpDriver(0, 1, kOobSizeNeedsMultiplier2); |
| |
| // Erase of non-existent page returns an error. |
| ASSERT_EQ(ftl::kNdmError, nand_volume_driver_->NandErase(kSize)); |
| |
| // Failure to erase returns an error. |
| interface_->set_fail_erase(true); |
| ASSERT_EQ(ftl::kNdmError, nand_volume_driver_->NandErase(0)); |
| } |
| |
| TEST_F(NandVolumeDriverTest, IsBadBlockSucceeds) { |
| uint32_t block_offset = 1; |
| SetUpDriver(block_offset, 1, kOobSizeDefault); |
| |
| std::set<uint32_t> bad_blocks{2, 4, 9}; |
| for (uint32_t block : bad_blocks) { |
| interface_->SetBadBlock(block, true); |
| } |
| |
| uint32_t pages_per_block = kBlockSize / kPageSize; |
| uint32_t byte_offset = block_offset * kBlockSize; |
| uint32_t num_pages = (kSize - byte_offset) / (page_multiplier_ * kPageSize); |
| |
| for (uint32_t page = 0; page < num_pages; page++) { |
| uint32_t block = block_offset + page / pages_per_block; |
| int expected_value = bad_blocks.find(block) != bad_blocks.end() ? ftl::kTrue : ftl::kFalse; |
| ASSERT_EQ(expected_value, nand_volume_driver_->IsBadBlock(page)); |
| } |
| } |
| |
| TEST_F(NandVolumeDriverTest, BadIsBadBlockReportsError) { |
| SetUpDriver(0, 1, kOobSizeNeedsMultiplier2); |
| |
| // Check for non-existent page returns an error. |
| ASSERT_EQ(ftl::kNdmError, nand_volume_driver_->IsBadBlock(kSize)); |
| |
| // Failure to check bad block returns an error. |
| interface_->set_fail_is_bad_block(true); |
| ASSERT_EQ(ftl::kNdmError, nand_volume_driver_->IsBadBlock(0)); |
| } |
| |
| } // namespace |