blob: 6d45a3b9a6549920df7e76442cd643dfaba53332 [file] [log] [blame]
// Copyright 2018 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 <new>
#include <stdio.h>
#include <stdlib.h>
#include <fbl/algorithm.h>
#include <fbl/unique_fd.h>
#include <fuchsia/nand/c/fidl.h>
#include <lib/fdio/watcher.h>
#include <lib/fzl/fdio.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/zx/vmo.h>
#include <unittest/unittest.h>
#include <zircon/device/device.h>
#include <zircon/syscalls.h>
#include "parent.h"
namespace {
constexpr uint32_t kMinOobSize = 4;
constexpr uint32_t kMinBlockSize = 4;
constexpr uint32_t kMinNumBlocks = 5;
constexpr uint32_t kInMemoryPages = 20;
fbl::unique_fd OpenBroker(const char* path) {
fbl::unique_fd broker;
auto callback = [](int dir_fd, int event, const char* filename, void* cookie) {
if (event != WATCH_EVENT_ADD_FILE || strcmp(filename, "broker") != 0) {
return ZX_OK;
}
fbl::unique_fd* broker = reinterpret_cast<fbl::unique_fd*>(cookie);
broker->reset(openat(dir_fd, filename, O_RDWR));
return ZX_ERR_STOP;
};
fbl::unique_fd dir(open(path, O_DIRECTORY));
if (dir) {
zx_time_t deadline = zx_deadline_after(ZX_SEC(5));
fdio_watch_directory(dir.get(), callback, deadline, &broker);
}
return broker;
}
// The device under test.
class NandDevice {
public:
NandDevice();
~NandDevice() {
if (linked_) {
fbl::unique_fd broker = caller_.release();
ioctl_device_unbind(broker.get());
}
}
bool IsValid() const { return is_valid_; }
// Provides a channel to issue fidl calls.
zx_handle_t channel() { return caller_.borrow_channel(); }
// Wrappers for "queue" operations that take care of preserving the vmo's handle
// and translating the request to the desired block range on the actual device.
bool Read(const zx::vmo& vmo, const fuchsia_nand_BrokerRequest& request,
zx_status_t* response = nullptr);
bool Write(const zx::vmo& vmo, const fuchsia_nand_BrokerRequest& request,
zx_status_t* response = nullptr);
bool Erase(const fuchsia_nand_BrokerRequest& request, zx_status_t* response = nullptr);
// Erases a given block number.
bool EraseBlock(uint32_t block_num);
// Verifies that the buffer pointed to by the operation's vmo contains the given
// pattern for the desired number of pages, skipping the pages before start.
bool CheckPattern(uint8_t expected, int start, int num_pages, const void* memory);
const fuchsia_hardware_nand_Info& Info() const { return parent_->Info(); }
uint32_t PageSize() const { return parent_->Info().page_size; }
uint32_t OobSize() const { return parent_->Info().oob_size; }
uint32_t BlockSize() const { return parent_->Info().pages_per_block; }
uint32_t NumBlocks() const { return num_blocks_; }
uint32_t NumPages() const { return num_blocks_ * BlockSize(); }
uint32_t MaxBufferSize() const { return kInMemoryPages * (PageSize() + OobSize()); }
// True when the whole device under test can be modified.
bool IsFullDevice() const { return full_device_; }
private:
bool ValidateNandDevice();
ParentDevice* parent_ = g_parent_device_;
fzl::FdioCaller caller_;
uint32_t num_blocks_ = 0;
uint32_t first_block_ = 0;
bool full_device_ = true;
bool linked_ = false;
bool is_valid_ = false;
};
NandDevice::NandDevice() {
ZX_ASSERT(parent_->IsValid());
if (parent_->IsBroker()) {
caller_.reset(fbl::unique_fd(open(parent_->Path(), O_RDWR)));
} else {
const char kBroker[] = "/boot/driver/nand-broker.so";
if (ioctl_device_bind(parent_->get(), kBroker, sizeof(kBroker) - 1) < 0) {
unittest_printf_critical("Failed to bind broker\n");
return;
}
linked_ = true;
caller_.reset(OpenBroker(parent_->Path()));
}
is_valid_ = ValidateNandDevice();
}
bool NandDevice::Read(const zx::vmo& vmo, const fuchsia_nand_BrokerRequest& request,
zx_status_t* response) {
BEGIN_TEST;
fuchsia_nand_BrokerRequest request_copy = request;
if (!full_device_) {
request_copy.offset_nand = request.offset_nand + first_block_ * BlockSize();
ZX_DEBUG_ASSERT(request.offset_nand < NumPages());
ZX_DEBUG_ASSERT(request.offset_nand + request.length <= NumPages());
}
uint32_t bit_flips;
zx_status_t status;
zx::vmo dup;
ASSERT_EQ(ZX_OK, vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
request_copy.vmo = dup.release();
ASSERT_EQ(ZX_OK, fuchsia_nand_BrokerRead(channel(), &request_copy, &status, &bit_flips));
if (response) {
*response = status;
} else {
ASSERT_EQ(ZX_OK, status);
}
ASSERT_EQ(0, bit_flips);
END_TEST;
}
bool NandDevice::Write(const zx::vmo& vmo, const fuchsia_nand_BrokerRequest& request,
zx_status_t* response) {
BEGIN_TEST;
fuchsia_nand_BrokerRequest request_copy = request;
if (!full_device_) {
request_copy.offset_nand = request.offset_nand + first_block_ * BlockSize();
ZX_DEBUG_ASSERT(request.offset_nand < NumPages());
ZX_DEBUG_ASSERT(request.offset_nand + request.length <= NumPages());
}
zx_status_t status;
zx::vmo dup;
ASSERT_EQ(ZX_OK, vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup));
request_copy.vmo = dup.release();
ASSERT_EQ(ZX_OK, fuchsia_nand_BrokerWrite(channel(), &request_copy, &status));
if (response) {
*response = status;
} else {
ASSERT_EQ(ZX_OK, status);
}
END_TEST;
}
bool NandDevice::Erase(const fuchsia_nand_BrokerRequest& request, zx_status_t* response) {
BEGIN_TEST;
fuchsia_nand_BrokerRequest request_copy = request;
if (!full_device_) {
request_copy.offset_nand = request.offset_nand + first_block_;
ZX_DEBUG_ASSERT(request.offset_nand < NumBlocks());
ZX_DEBUG_ASSERT(request.offset_nand + request.length <= NumBlocks());
}
zx_status_t status;
ASSERT_EQ(ZX_OK, fuchsia_nand_BrokerErase(channel(), &request_copy, &status));
if (response) {
*response = status;
} else {
ASSERT_EQ(ZX_OK, status);
}
END_TEST;
}
bool NandDevice::EraseBlock(uint32_t block_num) {
BEGIN_TEST;
fuchsia_nand_BrokerRequest request = {};
request.length = 1;
request.offset_nand = block_num;
ASSERT_TRUE(Erase(request));
END_TEST;
}
bool NandDevice::CheckPattern(uint8_t expected, int start, int num_pages, const void* memory) {
const uint8_t* buffer = reinterpret_cast<const uint8_t*>(memory) + PageSize() * start;
for (uint32_t i = 0; i < PageSize() * num_pages; i++) {
if (buffer[i] != expected) {
return false;
}
}
return true;
}
bool NandDevice::ValidateNandDevice() {
if (parent_->IsExternal()) {
// This looks like using code under test to setup the test, but this
// path is for external devices, not really the broker. The issue is that
// ParentDevice cannot query a nand device for the actual parameters.
fuchsia_hardware_nand_Info info;
zx_status_t status;
if (fuchsia_nand_BrokerGetInfo(channel(), &status, &info) != ZX_OK || status != ZX_OK) {
printf("Failed to query nand device\n");
return false;
}
parent_->SetInfo(info);
}
num_blocks_ = parent_->NumBlocks();
first_block_ = parent_->FirstBlock();
if (OobSize() < kMinOobSize || BlockSize() < kMinBlockSize || num_blocks_ < kMinNumBlocks ||
num_blocks_ + first_block_ > parent_->Info().num_blocks) {
printf("Invalid nand device parameters\n");
return false;
}
if (num_blocks_ != parent_->Info().num_blocks) {
// Not using the whole device, don't need to test all limits.
num_blocks_ = fbl::min(num_blocks_, kMinNumBlocks);
full_device_ = false;
}
return true;
}
bool TrivialLifetimeTest() {
BEGIN_TEST;
NandDevice device;
ASSERT_TRUE(device.IsValid());
END_TEST;
}
bool QueryTest() {
BEGIN_TEST;
NandDevice device;
ASSERT_TRUE(device.IsValid());
fuchsia_hardware_nand_Info info;
zx_status_t status;
ASSERT_EQ(ZX_OK, fuchsia_nand_BrokerGetInfo(device.channel(), &status, &info));
ASSERT_EQ(ZX_OK, status);
EXPECT_EQ(device.Info().page_size, info.page_size);
EXPECT_EQ(device.Info().oob_size, info.oob_size);
EXPECT_EQ(device.Info().pages_per_block, info.pages_per_block);
EXPECT_EQ(device.Info().num_blocks, info.num_blocks);
EXPECT_EQ(device.Info().ecc_bits, info.ecc_bits);
EXPECT_EQ(device.Info().nand_class, info.nand_class);
END_TEST;
}
bool ReadWriteLimitsTest() {
BEGIN_TEST;
NandDevice device;
ASSERT_TRUE(device.IsValid());
fzl::VmoMapper mapper;
zx::vmo vmo;
ASSERT_EQ(ZX_OK, mapper.CreateAndMap(device.MaxBufferSize(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
nullptr, &vmo));
fuchsia_nand_BrokerRequest request = {};
zx_status_t status;
ASSERT_TRUE(device.Read(vmo, request, &status));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
ASSERT_TRUE(device.Write(vmo, request, &status));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
if (device.IsFullDevice()) {
request.length = 1;
request.offset_nand = device.NumPages();
ASSERT_TRUE(device.Read(vmo, request, &status));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
ASSERT_TRUE(device.Write(vmo, request, &status));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
request.length = 2;
request.offset_nand = device.NumPages() - 1;
ASSERT_TRUE(device.Read(vmo, request, &status));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
ASSERT_TRUE(device.Write(vmo, request, &status));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
}
request.length = 1;
request.offset_nand = device.NumPages() - 1;
ASSERT_TRUE(device.Read(vmo, request, &status));
EXPECT_EQ(ZX_ERR_BAD_HANDLE, status);
ASSERT_TRUE(device.Write(vmo, request, &status));
EXPECT_EQ(ZX_ERR_BAD_HANDLE, status);
request.data_vmo = true;
ASSERT_TRUE(device.Read(vmo, request, &status));
EXPECT_EQ(ZX_OK, status);
ASSERT_TRUE(device.Write(vmo, request, &status));
EXPECT_EQ(ZX_OK, status);
END_TEST;
}
bool EraseLimitsTest() {
return true;
BEGIN_TEST;
NandDevice device;
ASSERT_TRUE(device.IsValid());
fuchsia_nand_BrokerRequest request = {};
zx_status_t status;
ASSERT_TRUE(device.Erase(request, &status));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
request.offset_nand = device.NumBlocks();
if (device.IsFullDevice()) {
request.length = 1;
ASSERT_TRUE(device.Erase(request, &status));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
request.length = 2;
request.offset_nand = device.NumBlocks() - 1;
ASSERT_TRUE(device.Erase(request, &status));
EXPECT_EQ(ZX_ERR_OUT_OF_RANGE, status);
}
request.length = 1;
request.offset_nand = device.NumBlocks() - 1;
ASSERT_TRUE(device.Erase(request, &status));
EXPECT_EQ(ZX_OK, status);
END_TEST;
}
bool ReadWriteTest() {
BEGIN_TEST;
NandDevice device;
ASSERT_TRUE(device.IsValid());
ASSERT_TRUE(device.EraseBlock(0));
fzl::VmoMapper mapper;
zx::vmo vmo;
ASSERT_EQ(ZX_OK, mapper.CreateAndMap(device.MaxBufferSize(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
nullptr, &vmo));
memset(mapper.start(), 0x55, mapper.size());
fuchsia_nand_BrokerRequest request = {};
request.length = 4;
request.offset_nand = 4;
request.data_vmo = true;
ASSERT_TRUE(device.Write(vmo, request));
memset(mapper.start(), 0, mapper.size());
ASSERT_TRUE(device.Read(vmo, request));
ASSERT_TRUE(device.CheckPattern(0x55, 0, 4, mapper.start()));
END_TEST;
}
bool ReadWriteOobTest() {
BEGIN_TEST;
NandDevice device;
ASSERT_TRUE(device.IsValid());
ASSERT_TRUE(device.EraseBlock(0));
fzl::VmoMapper mapper;
zx::vmo vmo;
ASSERT_EQ(ZX_OK, mapper.CreateAndMap(device.MaxBufferSize(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
nullptr, &vmo));
const char desired[] = {'a', 'b', 'c', 'd'};
memcpy(mapper.start(), desired, sizeof(desired));
fuchsia_nand_BrokerRequest request = {};
request.length = 1;
request.offset_nand = 2;
request.oob_vmo = true;
ASSERT_TRUE(device.Write(vmo, request));
request.length = 2;
request.offset_nand = 1;
memset(mapper.start(), 0, device.OobSize() * 2);
ASSERT_TRUE(device.Read(vmo, request));
// The "second page" has the data of interest.
ASSERT_EQ(0,
memcmp(reinterpret_cast<char*>(mapper.start()) + device.OobSize(), desired,
sizeof(desired)));
END_TEST;
}
bool ReadWriteDataAndOobTest() {
BEGIN_TEST;
NandDevice device;
ASSERT_TRUE(device.IsValid());
ASSERT_TRUE(device.EraseBlock(0));
fzl::VmoMapper mapper;
zx::vmo vmo;
ASSERT_EQ(ZX_OK, mapper.CreateAndMap(device.MaxBufferSize(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
nullptr, &vmo));
char* buffer = reinterpret_cast<char*>(mapper.start());
memset(buffer, 0x55, device.PageSize() * 2);
memset(buffer + device.PageSize() * 2, 0xaa, device.OobSize() * 2);
fuchsia_nand_BrokerRequest request = {};
request.length = 2;
request.offset_nand = 2;
request.offset_oob_vmo = 2; // OOB is right after data.
request.data_vmo = true;
request.oob_vmo = true;
ASSERT_TRUE(device.Write(vmo, request));
memset(buffer, 0, device.PageSize() * 4);
ASSERT_TRUE(device.Read(vmo, request));
// Verify data.
ASSERT_TRUE(device.CheckPattern(0x55, 0, 2, buffer));
// Verify OOB.
memset(buffer, 0xaa, device.PageSize());
ASSERT_EQ(0, memcmp(buffer + device.PageSize() * 2, buffer, device.OobSize() * 2));
END_TEST;
}
bool EraseTest() {
BEGIN_TEST;
NandDevice device;
ASSERT_TRUE(device.IsValid());
fzl::VmoMapper mapper;
zx::vmo vmo;
ASSERT_EQ(ZX_OK, mapper.CreateAndMap(device.MaxBufferSize(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
nullptr, &vmo));
memset(mapper.start(), 0x55, mapper.size());
fuchsia_nand_BrokerRequest request = {};
request.length = kMinBlockSize;
request.data_vmo = true;
request.offset_nand = device.BlockSize();
ASSERT_TRUE(device.Write(vmo, request));
request.offset_nand = device.BlockSize() * 2;
ASSERT_TRUE(device.Write(vmo, request));
ASSERT_TRUE(device.EraseBlock(1));
ASSERT_TRUE(device.EraseBlock(2));
ASSERT_TRUE(device.Read(vmo, request));
ASSERT_TRUE(device.CheckPattern(0xff, 0, kMinBlockSize, mapper.start()));
request.offset_nand = device.BlockSize();
ASSERT_TRUE(device.Read(vmo, request));
ASSERT_TRUE(device.CheckPattern(0xff, 0, kMinBlockSize, mapper.start()));
END_TEST;
}
} // namespace
BEGIN_TEST_CASE(NandBrokerTests)
RUN_TEST_SMALL(TrivialLifetimeTest)
RUN_TEST_SMALL(QueryTest)
RUN_TEST_SMALL(ReadWriteLimitsTest)
RUN_TEST_SMALL(EraseLimitsTest)
RUN_TEST_SMALL(ReadWriteTest)
RUN_TEST_SMALL(ReadWriteOobTest)
RUN_TEST_SMALL(ReadWriteDataAndOobTest)
RUN_TEST_SMALL(EraseTest)
END_TEST_CASE(NandBrokerTests)