blob: 7fe2a87c0081153f7bf2dcc7d4755cfdfb14c599 [file] [log] [blame]
// 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 <string.h>
#include <memory>
#include <vector>
#include <zxtest/zxtest.h>
#include <lib/mtd/mtd-interface.h>
using namespace mtd;
// This MTD interface test relies on a device file located at /dev/mtd0/
// 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
// linux/arm64-based tests are run on astro.
namespace {
#ifdef ASTRO
constexpr const char* kTestDevicePath = "/dev/mtd/mtd9";
constexpr uint32_t kOobSize = 8; // 8 bytes
constexpr uint32_t kPageSize = 4 * 1024; // 4KiB
constexpr uint32_t kBlockSize = 256 * 1024; // 256KiB
constexpr uint32_t kSize = 3 * 1024 * 1024; // 3MiB
#else
constexpr const char* kTestDevicePath = "/dev/mtd0";
constexpr uint32_t kOobSize = 128; // 128 bytes
constexpr uint32_t kPageSize = 4 * 1024; // 4KiB
constexpr uint32_t kBlockSize = 256 * 1024; // 256KiB
constexpr uint32_t kSize = 512 * 1024 * 1024; // 512MiB
#endif
class MtdInterfaceTest : public zxtest::Test {
protected:
void SetUp() override { mtd_ = MtdInterface::Create(kTestDevicePath); }
void SetData(int8_t data, uint32_t size) {
data_ = std::vector<uint8_t>(size);
memset(data_.data(), data, size);
}
void SetOob(int8_t data, uint32_t size) {
oob_ = std::vector<uint8_t>(size);
memset(oob_.data(), data, size);
}
std::unique_ptr<MtdInterface> mtd_;
std::vector<uint8_t> data_;
std::vector<uint8_t> oob_;
};
TEST_F(MtdInterfaceTest, ValidMtd) {
ASSERT_NE(nullptr, mtd_, "Failed to initialize mtd_ with device %s", kTestDevicePath);
// The following specifications are set by the chip ID and never change.
EXPECT_EQ(kPageSize, mtd_->PageSize());
EXPECT_EQ(kBlockSize, mtd_->BlockSize());
EXPECT_EQ(kOobSize, mtd_->OobSize());
EXPECT_EQ(kSize, mtd_->Size());
}
TEST_F(MtdInterfaceTest, InvalidMtd) {
// File does not exist.
std::unique_ptr<MtdInterface> nonexistant_mtd =
MtdInterface::Create("/dev/bad/mtd");
EXPECT_EQ(nullptr, nonexistant_mtd);
// File is not an MTD device.
std::unique_ptr<MtdInterface> invalid_mtd = MtdInterface::Create("/dev/zero");
EXPECT_EQ(nullptr, invalid_mtd);
}
TEST_F(MtdInterfaceTest, ReadWriteEraseTest) {
uint32_t bytes_read;
std::vector<uint8_t> out_data(mtd_->PageSize());
std::vector<uint8_t> out_oob(mtd_->OobSize());
const uint32_t page20 = 20 * mtd_->PageSize();
const uint32_t page5 = 5 * mtd_->PageSize();
uint32_t block = page5 & ~(mtd_->BlockSize() - 1);
// Erase the block containing page 5 then verify page 5 is empty.
ASSERT_EQ(ZX_OK, mtd_->EraseBlock(block));
ASSERT_EQ(ZX_OK, mtd_->ReadPage(page5, out_data.data(), &bytes_read));
ASSERT_EQ(ZX_OK, mtd_->ReadOob(page5, out_oob.data()));
ASSERT_EQ(mtd_->PageSize(), bytes_read);
for (uint32_t i = 0; i < mtd_->PageSize(); i++) {
ASSERT_EQ(0xFF, out_data[i], "Failed at index %u", i);
}
for (uint32_t i = 0; i < mtd_->OobSize(); i++) {
ASSERT_EQ(0xFF, out_oob[i], "Failed at index %u", i);
}
// Set data/oob to be written.
SetData(0x12, mtd_->PageSize());
SetOob(0x23, mtd_->OobSize());
// Write one page 20 then read.
ASSERT_EQ(ZX_OK, mtd_->WritePage(page20, data_.data(), oob_.data()));
ASSERT_EQ(ZX_OK, mtd_->ReadPage(page20, out_data.data(), &bytes_read));
ASSERT_EQ(ZX_OK, mtd_->ReadOob(page20, out_oob.data()));
ASSERT_EQ(mtd_->PageSize(), bytes_read);
for (uint32_t i = 0; i < mtd_->PageSize(); i++) {
ASSERT_EQ(data_[i], out_data[i], "Failed at index %u", i);
}
for (uint32_t i = 0; i < mtd_->OobSize(); i++) {
ASSERT_EQ(oob_[i], out_oob[i], "Failed at index %u", i);
}
// Set data/oob with different bytes to be written.
SetData(0x45, mtd_->PageSize());
SetOob(0x67, mtd_->OobSize());
// Write different data to page 5 then read and verify.
ASSERT_EQ(ZX_OK, mtd_->WritePage(page5, data_.data(), oob_.data()));
ASSERT_EQ(ZX_OK, mtd_->ReadPage(page5, out_data.data(), &bytes_read));
ASSERT_EQ(ZX_OK, mtd_->ReadOob(page5, out_oob.data()));
ASSERT_EQ(mtd_->PageSize(), bytes_read);
for (uint32_t i = 0; i < mtd_->PageSize(); i++) {
ASSERT_EQ(data_[i], out_data[i], "Failed at index %u", i);
}
for (uint32_t i = 0; i < mtd_->OobSize(); i++) {
ASSERT_EQ(oob_[i], out_oob[i], "Failed at index %u", i);
}
// Read the page 20 again. Verify it hasn't changed.
ASSERT_EQ(ZX_OK, mtd_->ReadPage(page20, out_data.data(), &bytes_read));
ASSERT_EQ(ZX_OK, mtd_->ReadOob(page20, out_oob.data()));
ASSERT_EQ(mtd_->PageSize(), bytes_read);
// Set data/oob with same bytes expected for page 20.
SetData(0x12, mtd_->PageSize());
SetOob(0x23, mtd_->OobSize());
for (uint32_t i = 0; i < mtd_->PageSize(); i++) {
ASSERT_EQ(data_[i], out_data[i], "Failed at index %u", i);
}
for (uint32_t i = 0; i < mtd_->OobSize(); i++) {
ASSERT_EQ(oob_[i], out_oob[i], "Failed at index %u", i);
}
// Erase the block containing page 5 then verify page 5 is empty.
ASSERT_EQ(ZX_OK, mtd_->EraseBlock(block));
ASSERT_EQ(ZX_OK, mtd_->ReadPage(page5, out_data.data(), &bytes_read));
ASSERT_EQ(ZX_OK, mtd_->ReadOob(page5, out_oob.data()));
ASSERT_EQ(mtd_->PageSize(), bytes_read);
for (uint32_t i = 0; i < mtd_->PageSize(); i++) {
ASSERT_EQ(0xFF, out_data[i], "Failed at index %u", i);
}
for (uint32_t i = 0; i < mtd_->OobSize(); i++) {
ASSERT_EQ(0xFF, out_oob[i], "Failed at index %u", i);
}
}
TEST_F(MtdInterfaceTest, InvalidOffset) {
const uint32_t nonPageOffset = kPageSize - 1;
EXPECT_EQ(ZX_ERR_INVALID_ARGS,
mtd_->WritePage(nonPageOffset, nullptr, nullptr));
EXPECT_EQ(ZX_ERR_INVALID_ARGS,
mtd_->ReadPage(nonPageOffset, nullptr, nullptr));
EXPECT_EQ(ZX_ERR_INVALID_ARGS, mtd_->EraseBlock(nonPageOffset));
EXPECT_EQ(ZX_ERR_INVALID_ARGS, mtd_->IsBadBlock(nonPageOffset, nullptr));
}
// TODO(mbrunson): Figure out a way to run this test consistently on real
// hardware.
TEST_F(MtdInterfaceTest, BadBlockTest) {
bool is_bad_block;
#ifndef ASTRO
// nandsim with badblocks=5 should only mark pages in block 5 as bad.
ASSERT_EQ(ZX_OK, mtd_->IsBadBlock(5 * mtd_->BlockSize(), &is_bad_block));
EXPECT_TRUE(is_bad_block);
#endif
ASSERT_EQ(ZX_OK, mtd_->IsBadBlock(0, &is_bad_block));
EXPECT_FALSE(is_bad_block);
}
} // namespace