| // 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 <stdint.h> |
| #include <vector> |
| |
| #include <lib/mtd/mtd-interface.h> |
| #include <lib/nand-redundant-storage/nand-redundant-storage.h> |
| #include <zxtest/zxtest.h> |
| |
| // This NAND interface test relies on an MTD device file located at /dev/mtd0/ |
| // for non-astro tests, and /dev/mtd/mtd9 for astro-based tests. |
| // |
| // On the host machine, nandsim is used to create a virtual MTD device. |
| // The following command was used to create the device for this test. |
| // |
| // $ sudo modprobe nandsim id_bytes=0x2c,0xdc,0x90,0xa6,0x54,0x0 badblocks=5 |
| |
| using namespace nand_rs; |
| |
| namespace { |
| |
| #ifdef ASTRO |
| constexpr const char* kTestDevicePath = "/dev/mtd/mtd9"; |
| #else |
| constexpr const char* kTestDevicePath = "/dev/mtd0"; |
| #endif |
| |
| class MtdRsTest : public zxtest::Test { |
| protected: |
| void Wipe() { |
| for (uint32_t offset = 0; offset < mtd_ptr_->Size(); offset += mtd_ptr_->BlockSize()) { |
| bool is_bad_block; |
| ASSERT_OK(mtd_ptr_->IsBadBlock(offset, &is_bad_block)); |
| if (is_bad_block) { |
| continue; |
| } |
| ASSERT_OK(mtd_ptr_->EraseBlock(offset)); |
| } |
| } |
| |
| // Zero-index-based block erase. |
| void EraseBlockAtIndex(uint32_t index) { |
| ASSERT_OK(mtd_ptr_->EraseBlock(mtd_ptr_->BlockSize() * index)); |
| } |
| |
| void SetUp() override { |
| auto mtd = mtd::MtdInterface::Create(kTestDevicePath); |
| mtd_ptr_ = mtd.get(); |
| // Sanity "test" to make sure the interface is valid. |
| ASSERT_NE(nullptr, mtd_ptr_, "Failed to initialize nand_ with device %s", kTestDevicePath); |
| nand_ = NandRedundantStorage::Create(std::move(mtd)); |
| |
| max_blocks_ = mtd_ptr_->Size() / mtd_ptr_->BlockSize(); |
| num_copies_written_ = 0; |
| Wipe(); |
| } |
| |
| std::vector<uint8_t> MakeFakePage(uint8_t value, uint32_t checksum, uint32_t file_size) { |
| std::vector<uint8_t> res(mtd_ptr_->PageSize(), value); |
| res[0] = 'Z'; |
| res[1] = 'N'; |
| res[2] = 'N'; |
| res[3] = 'D'; |
| |
| // Avoid byte-masking, as the struct is just copied into memory. |
| auto checksum_ptr = reinterpret_cast<uint32_t*>(&res[4]); |
| *checksum_ptr = checksum; |
| auto file_size_ptr = reinterpret_cast<uint32_t*>(&res[8]); |
| *file_size_ptr = file_size; |
| return res; |
| } |
| |
| mtd::MtdInterface* mtd_ptr_; |
| std::unique_ptr<NandRedundantStorage> nand_; |
| std::vector<uint8_t> out_buffer_; |
| uint32_t num_copies_written_; |
| uint32_t max_blocks_; |
| }; |
| |
| TEST_F(MtdRsTest, ReadWriteTest) { |
| std::vector<uint8_t> nonsense_buffer = {12, 14, 22, 0, 12, 8, 0, 0, 0, 3, 45, 0xFF}; |
| |
| ASSERT_OK(nand_->WriteBuffer(nonsense_buffer, 10, &num_copies_written_)); |
| ASSERT_EQ(10, num_copies_written_); |
| ASSERT_OK(nand_->ReadToBuffer(&out_buffer_)); |
| ASSERT_EQ(out_buffer_, nonsense_buffer); |
| |
| std::vector<uint8_t> page_crossing_buffer(mtd_ptr_->PageSize() * 2 + 13, 0xF5); |
| ASSERT_OK(nand_->WriteBuffer(page_crossing_buffer, 10, &num_copies_written_)); |
| ASSERT_EQ(10, num_copies_written_); |
| ASSERT_OK(nand_->ReadToBuffer(&out_buffer_)); |
| ASSERT_EQ(out_buffer_, page_crossing_buffer); |
| } |
| |
| TEST_F(MtdRsTest, ReadWriteTestWithErasedBlock) { |
| std::vector<uint8_t> page_crossing_buffer(mtd_ptr_->PageSize() * 2 + 13, 0xF5); |
| ASSERT_OK(nand_->WriteBuffer(page_crossing_buffer, 20, &num_copies_written_)); |
| ASSERT_EQ(20, num_copies_written_); |
| |
| EraseBlockAtIndex(0); |
| EraseBlockAtIndex(1); |
| EraseBlockAtIndex(2); |
| EraseBlockAtIndex(3); |
| ASSERT_OK(nand_->ReadToBuffer(&out_buffer_)); |
| ASSERT_EQ(out_buffer_, page_crossing_buffer); |
| } |
| |
| TEST_F(MtdRsTest, ReadWriteTestWithCorruptedBlockValidHeader) { |
| std::vector<uint8_t> page_crossing_buffer(mtd_ptr_->PageSize() * 2 + 13, 0xF5); |
| ASSERT_OK(nand_->WriteBuffer(page_crossing_buffer, 10, &num_copies_written_)); |
| ASSERT_EQ(10, num_copies_written_); |
| |
| EraseBlockAtIndex(0); |
| EraseBlockAtIndex(1); |
| EraseBlockAtIndex(2); |
| EraseBlockAtIndex(3); |
| uint32_t block_three_start = mtd_ptr_->BlockSize() * 2; |
| std::vector<uint8_t> page_of_nonsense = MakeFakePage(0x40, 0x40404040, 0x40404040); |
| ASSERT_EQ(ZX_OK, mtd_ptr_->WritePage(block_three_start, page_of_nonsense.data(), nullptr)); |
| ASSERT_OK(nand_->ReadToBuffer(&out_buffer_)); |
| ASSERT_EQ(out_buffer_, page_crossing_buffer); |
| } |
| |
| TEST_F(MtdRsTest, ReadWiteTestWithCorruptedBlockWrongCrc) { |
| std::vector<uint8_t> page_crossing_buffer(mtd_ptr_->PageSize() * 2 + 13, 0xF5); |
| ASSERT_OK(nand_->WriteBuffer(page_crossing_buffer, 10, &num_copies_written_)); |
| ASSERT_EQ(10, num_copies_written_); |
| |
| EraseBlockAtIndex(0); |
| EraseBlockAtIndex(1); |
| EraseBlockAtIndex(2); |
| EraseBlockAtIndex(3); |
| // Nonsense block, but with valid looking CRC and file size. |
| uint32_t block_three_start = mtd_ptr_->BlockSize() * 2; |
| std::vector<uint8_t> page_of_nonsense = MakeFakePage(0x40, 1, 34); |
| ASSERT_EQ(ZX_OK, mtd_ptr_->WritePage(block_three_start, page_of_nonsense.data(), nullptr)); |
| ASSERT_OK(nand_->ReadToBuffer(&out_buffer_)); |
| ASSERT_EQ(out_buffer_, page_crossing_buffer); |
| } |
| |
| TEST_F(MtdRsTest, ReadWriteTestWithCorruptedBlockWrongHeader) { |
| std::vector<uint8_t> page_crossing_buffer(mtd_ptr_->PageSize() * 2 + 13, 0xF5); |
| ASSERT_OK(nand_->WriteBuffer(page_crossing_buffer, 10, &num_copies_written_)); |
| ASSERT_EQ(10, num_copies_written_); |
| |
| EraseBlockAtIndex(0); |
| EraseBlockAtIndex(1); |
| EraseBlockAtIndex(2); |
| EraseBlockAtIndex(3); |
| // Nonsense block, but with invalid header. |
| uint32_t block_three_start = mtd_ptr_->BlockSize() * 2; |
| std::vector<uint8_t> page_of_nonsense = MakeFakePage(0x40, 1, 34); |
| page_of_nonsense[0] = 'z'; |
| ASSERT_EQ(ZX_OK, mtd_ptr_->WritePage(block_three_start, page_of_nonsense.data(), nullptr)); |
| ASSERT_OK(nand_->ReadToBuffer(&out_buffer_)); |
| ASSERT_EQ(out_buffer_, page_crossing_buffer); |
| } |
| |
| TEST_F(MtdRsTest, ReadEmptyMtd) { ASSERT_EQ(ZX_ERR_IO, nand_->ReadToBuffer(&out_buffer_)); } |
| |
| TEST_F(MtdRsTest, TestBlockWriteLimits) { |
| std::vector<uint8_t> some_bits = {1, 2, 3, 5, 10, 9, 25, 83}; |
| ASSERT_OK(nand_->WriteBuffer(some_bits, max_blocks_, &num_copies_written_)); |
| ASSERT_EQ(max_blocks_ - 1, num_copies_written_); |
| } |
| |
| } // namespace |