blob: 498dcc08980c300c576e12974d9092ab4fd81f71 [file] [log] [blame]
// Copyright 2020 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 "src/storage/volume_image/ftl/ftl_raw_nand_image_writer.h"
#include <lib/fpromise/result.h>
#include <lib/stdcompat/span.h>
#include <array>
#include <cinttypes>
#include <cstdint>
#include <type_traits>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/storage/volume_image/ftl/options.h"
#include "src/storage/volume_image/ftl/raw_nand_image.h"
#include "src/storage/volume_image/ftl/raw_nand_image_utils.h"
#include "src/storage/volume_image/utils/writer.h"
namespace storage::volume_image {
namespace {
constexpr uint32_t kPageSize = 8;
constexpr uint32_t kOobBytesSize = 9;
constexpr uint32_t kPagesPerBlock = 16;
constexpr uint32_t kBlockCount = 5;
constexpr uint32_t kPageCount = kPagesPerBlock * kBlockCount;
constexpr ImageFormat kFormat = ImageFormat::kRawImage;
constexpr std::array<RawNandImageFlag, 1> kFlags = {RawNandImageFlag::kRequireWipeBeforeFlash};
struct RawNandPage {
std::vector<uint8_t> data_;
std::vector<uint8_t> oob_;
};
class RamRawNandImageWriter : public Writer {
public:
explicit RamRawNandImageWriter(RawNandOptions options) : options_(options) {}
fpromise::result<void, std::string> Write(uint64_t offset,
cpp20::span<const uint8_t> data) final {
uint32_t data_offset = 0;
if (offset < sizeof(RawNandImageHeader)) {
data_offset = static_cast<uint32_t>(sizeof(RawNandImageHeader) - offset);
if (data.size() < data_offset) {
data_offset = static_cast<uint32_t>(data.size());
}
auto header_data = data.subspan(offset, data_offset);
memcpy(reinterpret_cast<uint8_t*>(&header_) + offset, header_data.data(), header_data.size());
}
// No image data write.
if (data_offset >= data.size()) {
return fpromise::ok();
}
uint64_t image_offset = offset - sizeof(RawNandImageHeader);
uint64_t image_page_offset = image_offset % RawNandImageGetAdjustedPageSize(options_);
uint32_t image_page_number =
static_cast<uint32_t>(image_offset / RawNandImageGetAdjustedPageSize(options_));
// Its a page write.
if (image_page_offset == 0) {
if (data.size() != options_.page_size) {
return fpromise::error("Bad page data buffer.");
}
pages_[image_page_number].data_ = std::vector(data.begin(), data.end());
return fpromise::ok();
}
// Its oob data.
if (image_page_offset == options_.page_size) {
if (data.size() != options_.oob_bytes_size) {
return fpromise::error("Bad oob buffer size.");
}
pages_[image_page_number].oob_ = std::vector(data.begin(), data.end());
return fpromise::ok();
}
return fpromise::error("Unaligned page write.");
}
const auto& pages() { return pages_; }
const auto& header() const { return header_; }
private:
std::map<uint32_t, RawNandPage> pages_;
RawNandImageHeader header_;
RawNandOptions options_;
};
RawNandOptions MakeOptions() {
RawNandOptions options;
options.oob_bytes_size = kOobBytesSize;
options.page_size = kPageSize;
options.page_count = kPageCount;
options.pages_per_block = kPagesPerBlock;
return options;
}
TEST(FtlRawNandImageWriterTest, CreateWithoutWriterIsError) {
ASSERT_TRUE(FtlRawNandImageWriter::Create(MakeOptions(), kFlags, kFormat, nullptr).is_error());
}
TEST(FtlRawNandImageWriterTest, CreateWithZeroOobSizeIsError) {
RawNandOptions device_options = MakeOptions();
RamRawNandImageWriter writer(device_options);
device_options.oob_bytes_size = 0;
ASSERT_TRUE(FtlRawNandImageWriter::Create(device_options, kFlags, kFormat, &writer).is_error());
}
TEST(FtlRawNandImageWriterTest, CreateWithZeroPagesPerBlockIsError) {
RawNandOptions device_options = MakeOptions();
device_options.pages_per_block = 0;
RamRawNandImageWriter writer(device_options);
ASSERT_TRUE(FtlRawNandImageWriter::Create(device_options, kFlags, kFormat, &writer).is_error());
}
TEST(FtlRawNandImageWriterTest, CreateWithNotEnoughOObPerBlockIsError) {
RawNandOptions device_options = MakeOptions();
device_options.pages_per_block = 2;
device_options.oob_bytes_size = 1;
RamRawNandImageWriter writer(device_options);
ASSERT_TRUE(FtlRawNandImageWriter::Create(device_options, kFlags, kFormat, &writer).is_error());
}
TEST(FtlRawNandImageWriterTest, CreateWithValidOptionsAndWriterIsOkAndProducesCorrectFtlOptions) {
RawNandOptions device_options = MakeOptions();
RamRawNandImageWriter writer(device_options);
auto writer_or = FtlRawNandImageWriter::Create(device_options, kFlags, kFormat, &writer);
ASSERT_TRUE(writer_or.is_ok()) << writer_or.error();
auto [raw_image_writer, ftl_options] = writer_or.take_value();
ASSERT_EQ(raw_image_writer.scale_factor(), 2u);
EXPECT_EQ(ftl_options.oob_bytes_size,
device_options.oob_bytes_size * raw_image_writer.scale_factor());
EXPECT_EQ(ftl_options.page_size, device_options.page_size * raw_image_writer.scale_factor());
EXPECT_EQ(ftl_options.page_count, device_options.page_count / raw_image_writer.scale_factor());
EXPECT_EQ(ftl_options.pages_per_block,
device_options.pages_per_block / raw_image_writer.scale_factor());
const auto& header = writer.header();
EXPECT_EQ(header.magic, RawNandImageHeader::kMagic);
EXPECT_EQ(header.version_major, RawNandImageHeader::kMajorVersion);
EXPECT_EQ(header.version_minor, RawNandImageHeader::kMinorVersion);
EXPECT_NE(header.flags & static_cast<std::underlying_type<RawNandImageFlag>::type>(kFlags[0]),
static_cast<std::underlying_type<RawNandImageFlag>::type>(0));
EXPECT_EQ(header.format, kFormat);
EXPECT_EQ(header.page_size, device_options.page_size);
EXPECT_EQ(header.oob_size, device_options.oob_bytes_size);
EXPECT_THAT(header.reserved, testing::Each(testing::Eq(0xFF)));
}
TEST(FtlRawNandImageWriterTest, WriteWithUnAlignedOffsetIsError) {
RawNandOptions device_options = MakeOptions();
RamRawNandImageWriter writer(device_options);
auto writer_or = FtlRawNandImageWriter::Create(device_options, kFlags, kFormat, &writer);
ASSERT_TRUE(writer_or.is_ok()) << writer_or.error();
auto [raw_image_writer, ftl_options] = writer_or.take_value();
std::vector<uint8_t> page_buffer(ftl_options.page_size, 0xFF);
std::vector<uint8_t> oob_buffer(ftl_options.oob_bytes_size, 0xFF);
EXPECT_TRUE(raw_image_writer.Write(1, page_buffer).is_error());
EXPECT_TRUE(raw_image_writer.Write(ftl_options.page_size + 1, oob_buffer).is_error());
}
TEST(FtlRawNandImageWriterTest, WriteAtAlignedOffsetWithWrongBufferSizeIsError) {
RawNandOptions device_options = MakeOptions();
RamRawNandImageWriter writer(device_options);
auto writer_or = FtlRawNandImageWriter::Create(device_options, kFlags, kFormat, &writer);
ASSERT_TRUE(writer_or.is_ok()) << writer_or.error();
auto [raw_image_writer, ftl_options] = writer_or.take_value();
std::vector<uint8_t> page_buffer(ftl_options.page_size - 1, 0xFF);
std::vector<uint8_t> oob_buffer(ftl_options.oob_bytes_size + 1, 0xFF);
EXPECT_TRUE(raw_image_writer.Write(0, page_buffer).is_error());
EXPECT_TRUE(raw_image_writer.Write(ftl_options.page_size, oob_buffer).is_error());
}
// Fills |buffer| with a sequence starting at shift up to upper limit, and then jumps back to zero.
void FillBuffer(cpp20::span<uint8_t> buffer, uint64_t shift) {
for (uint8_t& b : buffer) {
b = static_cast<uint8_t>(shift);
shift = (shift + 1) % std::numeric_limits<uint8_t>::max();
}
}
TEST(FtlRawNandImageWriterTest, WriteAtAlignedOffsetWithExpectedBufferSizeIsOk) {
constexpr int kLogicalPagesToWrite = 10;
RawNandOptions device_options = MakeOptions();
RamRawNandImageWriter writer(device_options);
auto writer_or = FtlRawNandImageWriter::Create(device_options, kFlags, kFormat, &writer);
ASSERT_TRUE(writer_or.is_ok()) << writer_or.error();
auto [raw_image_writer, ftl_options] = writer_or.take_value();
std::vector<uint8_t> page_buffer(ftl_options.page_size, 0xFF);
std::vector<uint8_t> oob_buffer(ftl_options.oob_bytes_size, 0xFF);
for (uint32_t i = 0; i < kLogicalPagesToWrite; ++i) {
FillBuffer(page_buffer, i);
FillBuffer(oob_buffer, i + device_options.oob_bytes_size);
auto page_write_result =
raw_image_writer.Write(RawNandImageGetPageOffset(i, ftl_options), page_buffer);
ASSERT_TRUE(page_write_result.is_ok()) << page_write_result.error();
auto oob_write_result = raw_image_writer.Write(
RawNandImageGetPageOffset(i, ftl_options) + ftl_options.page_size, oob_buffer);
ASSERT_TRUE(oob_write_result.is_ok()) << oob_write_result.error();
}
// Check header
const auto& header = writer.header();
EXPECT_EQ(header.magic, RawNandImageHeader::kMagic);
EXPECT_EQ(header.version_major, RawNandImageHeader::kMajorVersion);
EXPECT_EQ(header.version_minor, RawNandImageHeader::kMinorVersion);
EXPECT_NE(header.flags & static_cast<std::underlying_type<RawNandImageFlag>::type>(kFlags[0]),
static_cast<std::underlying_type<RawNandImageFlag>::type>(0));
EXPECT_EQ(header.format, kFormat);
EXPECT_EQ(header.page_size, device_options.page_size);
EXPECT_EQ(header.oob_size, device_options.oob_bytes_size);
EXPECT_THAT(header.reserved, testing::Each(testing::Eq(0xFF)));
for (uint32_t i = 0; i < kLogicalPagesToWrite; ++i) {
SCOPED_TRACE("Logical Page " + std::to_string(i));
FillBuffer(page_buffer, i);
FillBuffer(oob_buffer, i + device_options.oob_bytes_size);
auto page_view = cpp20::span<const uint8_t>(page_buffer);
auto oob_view = cpp20::span<const uint8_t>(oob_buffer);
EXPECT_THAT(writer.pages().at(2 * i).data_,
testing::ElementsAreArray(page_view.subspan(0, device_options.page_size)));
EXPECT_THAT(writer.pages().at(2 * i + 1).data_,
testing::ElementsAreArray(
page_view.subspan(device_options.page_size, device_options.page_size)));
EXPECT_THAT(writer.pages().at(2 * i).oob_,
testing::ElementsAreArray(oob_view.subspan(0, device_options.oob_bytes_size)));
EXPECT_THAT(writer.pages().at(2 * i + 1).oob_,
testing::ElementsAreArray(oob_view.subspan(device_options.oob_bytes_size,
device_options.oob_bytes_size)));
}
}
} // namespace
} // namespace storage::volume_image