blob: cb42947fc050bf4d3c7c5b69e81c7edf0d29ed4a [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_image.h"
#include <lib/fit/function.h>
#include <lib/stdcompat/span.h>
#include <stdint.h>
#include <iterator>
#include <string>
#include <variant>
#include <fbl/algorithm.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <safemath/safe_conversions.h>
#include "src/storage/volume_image/address_descriptor.h"
#include "src/storage/volume_image/ftl/ftl_image_internal.h"
#include "src/storage/volume_image/ftl/options.h"
#include "src/storage/volume_image/ftl/raw_nand_image_utils.h"
#include "src/storage/volume_image/options.h"
#include "src/storage/volume_image/partition.h"
#include "src/storage/volume_image/utils/block_utils.h"
#include "src/storage/volume_image/utils/reader.h"
#include "src/storage/volume_image/utils/writer.h"
#include "src/storage/volume_image/volume_descriptor.h"
namespace storage::volume_image {
namespace {
constexpr uint32_t kFtlUnsetPageMapping = std::numeric_limits<uint32_t>::max();
class FakeWriter final : public Writer {
public:
// On success data backing this writer is updated at [|offset|, |offset| +
// |buffer.size()|] to |buffer|.
//
// On error the returned result to contains a string describing the error.
fpromise::result<void, std::string> Write(uint64_t offset,
cpp20::span<const uint8_t> buffer) final {
if (offset > pages_.size()) {
std::fill_n(std::back_inserter(pages_), offset - pages_.size(), -1);
}
pages_.insert(pages_.end(), buffer.begin(), buffer.end());
return fpromise::ok();
}
cpp20::span<const uint8_t> pages() const { return pages_; }
private:
std::vector<uint8_t> pages_;
// Options for the nand device.
RawNandOptions options_;
};
void VisitBlocksOnBuffer(
uint32_t block_size, uint64_t offset, cpp20::span<uint8_t> buffer,
fit::function<void(uint32_t block_number, cpp20::span<uint8_t> block_view)> visitor) {
uint64_t block_start = GetBlockFromBytes(offset, block_size);
uint64_t block_count = GetBlockCount(offset, buffer.size(), block_size);
if (block_count == 0) {
return;
}
size_t offset_from_start = GetOffsetFromBlockStart(offset, block_size);
// Fill first block, might not be aligned.
auto first_block_view =
buffer.subspan(0, std::min(buffer.size(), block_size - offset_from_start));
visitor(safemath::checked_cast<uint32_t>(block_start), first_block_view);
// Fill all remaning blocks are aligned from this point of view.
for (size_t i = 0; i < block_count - 1; ++i) {
uint64_t buffer_offset = first_block_view.size() + i * block_size;
uint32_t remaining_bytes =
safemath::checked_cast<uint32_t>(buffer.size() - buffer_offset - i * block_size);
auto aligned_block_view = buffer.subspan(buffer_offset, std::min(remaining_bytes, block_size));
visitor(safemath::checked_cast<uint32_t>(block_start + i), aligned_block_view);
}
}
// Will fill each block with a pattern based on the requested block number.
class FakeReader final : public Reader {
public:
static void FillBlock(uint64_t block_number, cpp20::span<uint8_t> buffer) {
uint8_t* bytes = reinterpret_cast<uint8_t*>(&block_number);
for (size_t i = 0; i < buffer.size(); ++i) {
buffer[i] = bytes[i % sizeof(uint64_t)];
}
}
explicit FakeReader(uint32_t block_size) : block_size_(block_size) {}
uint64_t length() const override { return 0; }
// On success data backing this writer is updated at [|offset|, |offset| +
// |buffer.size()|] to |buffer|.
//
// On error the returned result to contains a string describing the error.
fpromise::result<void, std::string> Read(uint64_t offset,
cpp20::span<uint8_t> buffer) const final {
VisitBlocksOnBuffer(block_size_, offset, buffer, FillBlock);
return fpromise::ok();
}
private:
uint32_t block_size_ = 0;
};
[[maybe_unused]] std::vector<uint8_t> GetBlockContents(uint64_t offset, uint32_t size) {
std::vector<uint8_t> contents(size, 0xFF);
FakeReader::FillBlock(offset, contents);
return contents;
}
VolumeDescriptor MakeVolumeDescriptor() {
VolumeDescriptor descriptor;
descriptor.block_size = 32;
return descriptor;
}
// Only pages with mappings are written.
struct MapPage {
uint32_t logical_number = 0;
std::vector<uint32_t> entries;
};
void CheckMapPage(const MapPage& expected_map_page, cpp20::span<const uint8_t> actual_contents,
const RawNandOptions& options) {
auto expected_map_page_contents =
cpp20::span<const uint8_t>(reinterpret_cast<const uint8_t*>(expected_map_page.entries.data()),
expected_map_page.entries.size() * sizeof(uint32_t));
std::vector<uint8_t> expected_oob(16, 0xFF);
ftl_image_internal::WriteOutOfBandBytes<ftl_image_internal::PageType::kMapPage>(
expected_map_page.logical_number, expected_oob);
auto actual_page = actual_contents.subspan(0, options.page_size);
auto actual_oob = actual_contents.subspan(options.page_size, options.oob_bytes_size);
EXPECT_THAT(actual_oob, testing::ElementsAreArray(expected_oob));
// Treat as fatal failure so callers can abort.
ASSERT_THAT(actual_page, testing::ElementsAreArray(expected_map_page_contents));
}
void CheckVolumePage(uint64_t source_offset, uint64_t target_offset, uint64_t length,
uint32_t logical_page_number, uint32_t physical_page_number,
const RawNandOptions& options, const Reader* reader,
cpp20::span<const uint8_t> contents) {
std::vector<uint8_t> expected_page(options.page_size, 0xFF);
std::vector<uint8_t> expected_oob(options.oob_bytes_size, 0xFF);
uint64_t offset_from_page = GetOffsetFromBlockStart(target_offset, options.page_size);
uint64_t page_offset = RawNandImageGetPageOffset(physical_page_number, options);
auto page_view = contents.subspan(page_offset + offset_from_page, length);
auto oob_view = contents.subspan(page_offset + options.page_size, options.oob_bytes_size);
ASSERT_TRUE(reader->Read(source_offset, expected_page).is_ok());
ftl_image_internal::WriteOutOfBandBytes<ftl_image_internal::PageType::kVolumePage>(
logical_page_number, expected_oob);
EXPECT_THAT(oob_view, testing::ElementsAreArray(expected_oob));
ASSERT_THAT(page_view,
testing::ElementsAreArray(cpp20::span<uint8_t>(expected_page).subspan(0, length)));
}
void CheckVolumePagesInMapping(const AddressMap& mapping, const RawNandOptions& options,
uint32_t logical_page_start, uint32_t physical_page_start,
const Reader* reader, cpp20::span<const uint8_t> contents) {
uint64_t read_bytes = 0;
uint64_t page_count = GetBlockCount(mapping.target, mapping.count, options.page_size);
for (uint32_t page_offset = 0; page_offset < page_count; ++page_offset) {
uint64_t target_offset = mapping.target + read_bytes;
uint64_t source_offset = mapping.source + read_bytes;
uint64_t length =
std::min(options.page_size - GetOffsetFromBlockStart(target_offset, options.page_size),
mapping.count - read_bytes);
CheckVolumePage(source_offset, target_offset, length, logical_page_start + page_offset,
physical_page_start + page_offset, options, reader, contents);
read_bytes += length;
}
}
TEST(FtlImageTest, FtlImageWriteWithASinglePageAlignedMappingIsOk) {
RawNandOptions options;
options.oob_bytes_size = 16;
options.page_size = 16;
options.page_count = 100;
options.pages_per_block = 4;
auto volume_descriptor = MakeVolumeDescriptor();
AddressDescriptor address_descriptor;
address_descriptor.mappings = {{.source = 32, .target = 128, .count = 16}};
// Two pages, 4 mappings per page. Two extra pages set max value(-1).
MapPage map_page = {
.logical_number = 2,
.entries = {0, kFtlUnsetPageMapping, kFtlUnsetPageMapping, kFtlUnsetPageMapping}};
auto reader = std::make_unique<FakeReader>(volume_descriptor.block_size);
auto partition = Partition(volume_descriptor, address_descriptor, std::move(reader));
// One volume page and one map page.
uint64_t expected_volume_page_count = 1;
uint64_t expected_map_pages = 1;
// Map pages should be on a different block than volume pages.
uint64_t expected_map_page_offset =
fbl::round_up(expected_volume_page_count, options.pages_per_block) *
RawNandImageGetAdjustedPageSize(options);
uint64_t written_content_size =
expected_map_page_offset + expected_map_pages * RawNandImageGetAdjustedPageSize(options);
FakeWriter writer;
auto write_result = FtlImageWrite(options, partition, &writer);
ASSERT_TRUE(write_result.is_ok()) << write_result.error();
ASSERT_THAT(writer.pages(), testing::SizeIs(written_content_size));
auto view = cpp20::span<const uint8_t>(writer.pages());
// Check volume page, which should be the first physical page written plus OOB size.
CheckVolumePagesInMapping(partition.address().mappings[0], options, 8, 0, partition.reader(),
writer.pages());
// Check that everything in between is 0xFF, so there are no unexpected values.
uint64_t skipped_offset = expected_volume_page_count * RawNandImageGetAdjustedPageSize(options);
auto not_written_contents =
view.subspan(skipped_offset, expected_map_page_offset - skipped_offset);
EXPECT_THAT(not_written_contents, testing::Each(testing::Eq(0xFF)));
// Check the map page.
CheckMapPage(map_page,
view.subspan(expected_map_page_offset, RawNandImageGetAdjustedPageSize(options)),
options);
}
TEST(FtlImageTest, FtlImageWriteWithMultipleMappingsSharingPagesIsError) {
RawNandOptions options;
options.oob_bytes_size = 16;
options.page_size = 16;
options.page_count = 100;
options.pages_per_block = 4;
auto volume_descriptor = MakeVolumeDescriptor();
AddressDescriptor address_descriptor;
address_descriptor.mappings = {
{.source = 32, .target = 0, .count = 16},
{.source = 32, .target = 128, .count = 12},
// This mapping shares pages with the previous one.
{.source = 32, .target = 140, .count = 48},
};
auto reader = std::make_unique<FakeReader>(volume_descriptor.block_size);
auto partition = Partition(volume_descriptor, address_descriptor, std::move(reader));
FakeWriter writer;
auto write_result = FtlImageWrite(options, partition, &writer);
ASSERT_TRUE(write_result.is_error());
}
TEST(FtlImageTest, FtlImageWriteWithMultiplePageAlignedMappingIsOk) {
RawNandOptions options;
options.oob_bytes_size = 16;
options.page_size = 16;
options.page_count = 100;
options.pages_per_block = 4;
auto volume_descriptor = MakeVolumeDescriptor();
AddressDescriptor address_descriptor;
address_descriptor.mappings = {{.source = 32, .target = 128, .count = 48}};
// Two pages, 4 mappings per page. Two extra pages set max value(-1).
MapPage map_page = {.logical_number = 2, .entries = {0, 1, 2, kFtlUnsetPageMapping}};
auto reader = std::make_unique<FakeReader>(volume_descriptor.block_size);
auto partition = Partition(volume_descriptor, address_descriptor, std::move(reader));
// One volume page and one map page.
uint64_t expected_volume_page_count = 3;
uint64_t expected_map_pages = 1;
// Map pages should be on a different block than volume pages.
uint64_t expected_map_page_offset =
fbl::round_up(expected_volume_page_count, options.pages_per_block) *
RawNandImageGetAdjustedPageSize(options);
uint64_t written_content_size =
expected_map_page_offset + expected_map_pages * RawNandImageGetAdjustedPageSize(options);
FakeWriter writer;
auto write_result = FtlImageWrite(options, partition, &writer);
ASSERT_TRUE(write_result.is_ok()) << write_result.error();
ASSERT_THAT(writer.pages(), testing::SizeIs(written_content_size));
auto view = cpp20::span<const uint8_t>(writer.pages());
// Check volume page, which should be the first physical page written plus OOB size.
CheckVolumePagesInMapping(partition.address().mappings[0], options, 8, 0, partition.reader(),
writer.pages());
// Check that everything in between is 0xFF, so there are no unexpected values.
uint64_t skipped_offset = expected_volume_page_count * RawNandImageGetAdjustedPageSize(options);
auto not_written_contents =
view.subspan(skipped_offset, expected_map_page_offset - skipped_offset);
EXPECT_THAT(not_written_contents, testing::Each(testing::Eq(0xFF)));
// Check the map page.
CheckMapPage(map_page,
view.subspan(expected_map_page_offset, RawNandImageGetAdjustedPageSize(options)),
options);
}
TEST(FtlImageTest, FtlImageWriteWithMultipleAlignedMappingsIsOk) {
RawNandOptions options;
options.oob_bytes_size = 16;
options.page_size = 16;
options.page_count = 100;
options.pages_per_block = 4;
auto volume_descriptor = MakeVolumeDescriptor();
AddressDescriptor address_descriptor;
address_descriptor.mappings = {
{.source = 32, .target = 128, .count = 48},
{.source = 16, .target = 96, .count = 32},
{.source = 80, .target = 80, .count = 16},
};
uint64_t adjusted_page_size = RawNandImageGetAdjustedPageSize(options);
// Two pages, 4 mappings per page. Two extra pages set max value(-1).
std::vector<MapPage> map_pages = {
{.logical_number = 1, .entries = {kFtlUnsetPageMapping, 5, 3, 4}},
{.logical_number = 2, .entries = {0, 1, 2, kFtlUnsetPageMapping}},
};
auto reader = std::make_unique<FakeReader>(volume_descriptor.block_size);
auto partition = Partition(volume_descriptor, address_descriptor, std::move(reader));
// One volume page and one map page.
uint64_t expected_volume_page_count = 6;
uint64_t expected_map_pages = 2;
// Map pages should be on a different block than volume pages.
uint64_t expected_map_page_offset =
fbl::round_up(expected_volume_page_count, options.pages_per_block) * adjusted_page_size;
uint64_t written_content_size =
expected_map_page_offset + expected_map_pages * adjusted_page_size;
FakeWriter writer;
auto write_result = FtlImageWrite(options, partition, &writer);
ASSERT_TRUE(write_result.is_ok()) << write_result.error();
ASSERT_THAT(writer.pages(), testing::SizeIs(written_content_size));
auto view = cpp20::span<const uint8_t>(writer.pages());
// Check volume page, which should be the first physical page written plus OOB size.
CheckVolumePagesInMapping(partition.address().mappings[0], options, 8, 0, partition.reader(),
writer.pages());
CheckVolumePagesInMapping(partition.address().mappings[1], options, 6, 3, partition.reader(),
writer.pages());
CheckVolumePagesInMapping(partition.address().mappings[2], options, 5, 5, partition.reader(),
writer.pages());
// Check that everything in between is 0xFF, so there are no unexpected values.
uint64_t skipped_offset = expected_volume_page_count * RawNandImageGetAdjustedPageSize(options);
auto not_written_contents =
view.subspan(skipped_offset, expected_map_page_offset - skipped_offset);
EXPECT_THAT(not_written_contents, testing::Each(testing::Eq(0xFF)));
for (uint32_t i = 0; i < map_pages.size(); ++i) {
const auto& map_page = map_pages[i];
// Check the map page.
CheckMapPage(
map_page,
view.subspan(expected_map_page_offset + i * adjusted_page_size, adjusted_page_size),
options);
}
}
TEST(FtlImageTest, FtlImageWriteWithASinglePageUnalignedMappingIsOk) {
RawNandOptions options;
options.oob_bytes_size = 16;
options.page_size = 16;
options.page_count = 100;
options.pages_per_block = 4;
auto volume_descriptor = MakeVolumeDescriptor();
AddressDescriptor address_descriptor;
address_descriptor.mappings = {{.source = 32, .target = 129, .count = 15}};
// Two pages, 4 mappings per page. Two extra pages set max value(-1).
MapPage map_page = {
.logical_number = 2,
.entries = {0, kFtlUnsetPageMapping, kFtlUnsetPageMapping, kFtlUnsetPageMapping}};
auto reader = std::make_unique<FakeReader>(volume_descriptor.block_size);
auto partition = Partition(volume_descriptor, address_descriptor, std::move(reader));
// One volume page and one map page.
uint64_t expected_volume_page_count = 1;
uint64_t expected_map_pages = 1;
// Map pages should be on a different block than volume pages.
uint64_t expected_map_page_offset =
fbl::round_up(expected_volume_page_count, options.pages_per_block) *
RawNandImageGetAdjustedPageSize(options);
uint64_t written_content_size =
expected_map_page_offset + expected_map_pages * RawNandImageGetAdjustedPageSize(options);
FakeWriter writer;
auto write_result = FtlImageWrite(options, partition, &writer);
ASSERT_TRUE(write_result.is_ok()) << write_result.error();
ASSERT_THAT(writer.pages(), testing::SizeIs(written_content_size));
auto view = cpp20::span<const uint8_t>(writer.pages());
// Check volume page, which should be the first physical page written plus OOB size.
CheckVolumePagesInMapping(partition.address().mappings[0], options, 8, 0, partition.reader(),
writer.pages());
// Check that everything in between is 0xFF, so there are no unexpected values.
uint64_t skipped_offset = expected_volume_page_count * RawNandImageGetAdjustedPageSize(options);
auto not_written_contents =
view.subspan(skipped_offset, expected_map_page_offset - skipped_offset);
EXPECT_THAT(not_written_contents, testing::Each(testing::Eq(0xFF)));
// Check the map page.
CheckMapPage(map_page,
view.subspan(expected_map_page_offset, RawNandImageGetAdjustedPageSize(options)),
options);
}
TEST(FtlImageTest, FtlImageWriteWithAMultiplePageUnalignedMappingIsOk) {
RawNandOptions options;
options.oob_bytes_size = 16;
options.page_size = 16;
options.page_count = 100;
options.pages_per_block = 4;
auto volume_descriptor = MakeVolumeDescriptor();
AddressDescriptor address_descriptor;
address_descriptor.mappings = {{.source = 32, .target = 129, .count = 17}};
// Two pages, 4 mappings per page. Two extra pages set max value(-1).
MapPage map_page = {.logical_number = 2,
.entries = {0, 1, kFtlUnsetPageMapping, kFtlUnsetPageMapping}};
auto reader = std::make_unique<FakeReader>(volume_descriptor.block_size);
auto partition = Partition(volume_descriptor, address_descriptor, std::move(reader));
// One volume page and one map page.
uint64_t expected_volume_page_count = 2;
uint64_t expected_map_pages = 1;
// Map pages should be on a different block than volume pages.
uint64_t expected_map_page_offset =
fbl::round_up(expected_volume_page_count, options.pages_per_block) *
RawNandImageGetAdjustedPageSize(options);
uint64_t written_content_size =
expected_map_page_offset + expected_map_pages * RawNandImageGetAdjustedPageSize(options);
FakeWriter writer;
auto write_result = FtlImageWrite(options, partition, &writer);
ASSERT_TRUE(write_result.is_ok()) << write_result.error();
ASSERT_THAT(writer.pages(), testing::SizeIs(written_content_size));
auto view = cpp20::span<const uint8_t>(writer.pages());
// Check volume page, which should be the first physical page written plus OOB size.
CheckVolumePagesInMapping(partition.address().mappings[0], options, 8, 0, partition.reader(),
writer.pages());
// Check that everything in between is 0xFF, so there are no unexpected values.
uint64_t skipped_offset = expected_volume_page_count * RawNandImageGetAdjustedPageSize(options);
auto not_written_contents =
view.subspan(skipped_offset, expected_map_page_offset - skipped_offset);
EXPECT_THAT(not_written_contents, testing::Each(testing::Eq(0xFF)));
// Check the map page.
CheckMapPage(map_page,
view.subspan(expected_map_page_offset, RawNandImageGetAdjustedPageSize(options)),
options);
}
TEST(FtlImageTest, FtlImageWriteWithAMultiplePageUnalignedAndMultipleMappingsIsOk) {
RawNandOptions options;
options.oob_bytes_size = 16;
options.page_size = 16;
options.page_count = 100;
options.pages_per_block = 4;
auto volume_descriptor = MakeVolumeDescriptor();
AddressDescriptor address_descriptor;
address_descriptor.mappings = {
{.source = 32, .target = 129, .count = 43},
{.source = 16, .target = 97, .count = 26},
{.source = 80, .target = 81, .count = 9},
};
uint64_t adjusted_page_size = RawNandImageGetAdjustedPageSize(options);
// Two pages, 4 mappings per page. Two extra pages set max value(-1).
std::vector<MapPage> map_pages = {
{.logical_number = 1, .entries = {kFtlUnsetPageMapping, 5, 3, 4}},
{.logical_number = 2, .entries = {0, 1, 2, kFtlUnsetPageMapping}},
};
auto reader = std::make_unique<FakeReader>(volume_descriptor.block_size);
auto partition = Partition(volume_descriptor, address_descriptor, std::move(reader));
// One volume page and one map page.
uint64_t expected_volume_page_count = 6;
uint64_t expected_map_pages = 2;
// Map pages should be on a different block than volume pages.
uint64_t expected_map_page_offset =
fbl::round_up(expected_volume_page_count, options.pages_per_block) * adjusted_page_size;
uint64_t written_content_size =
expected_map_page_offset + expected_map_pages * adjusted_page_size;
FakeWriter writer;
auto write_result = FtlImageWrite(options, partition, &writer);
ASSERT_TRUE(write_result.is_ok()) << write_result.error();
ASSERT_THAT(writer.pages(), testing::SizeIs(written_content_size));
auto view = cpp20::span<const uint8_t>(writer.pages());
// Check volume page, which should be the first physical page written plus OOB size.
CheckVolumePagesInMapping(partition.address().mappings[0], options, 8, 0, partition.reader(),
writer.pages());
CheckVolumePagesInMapping(partition.address().mappings[1], options, 6, 3, partition.reader(),
writer.pages());
CheckVolumePagesInMapping(partition.address().mappings[2], options, 5, 5, partition.reader(),
writer.pages());
// Check that everything in between is 0xFF, so there are no unexpected values.
uint64_t skipped_offset = expected_volume_page_count * RawNandImageGetAdjustedPageSize(options);
auto not_written_contents =
view.subspan(skipped_offset, expected_map_page_offset - skipped_offset);
EXPECT_THAT(not_written_contents, testing::Each(testing::Eq(0xFF)));
for (uint32_t i = 0; i < map_pages.size(); ++i) {
const auto& map_page = map_pages[i];
// Check the map page.
CheckMapPage(
map_page,
view.subspan(expected_map_page_offset + i * adjusted_page_size, adjusted_page_size),
options);
}
}
TEST(FtlImageTest, FtlImageWriteWithAMultiplePagesAndMultipleMappingsIsOk) {
RawNandOptions options;
options.oob_bytes_size = 16;
options.page_size = 16;
options.page_count = 100;
options.pages_per_block = 4;
auto volume_descriptor = MakeVolumeDescriptor();
AddressDescriptor address_descriptor;
address_descriptor.mappings = {
{.source = 32, .target = 128, .count = 43},
{.source = 16, .target = 97, .count = 31},
{.source = 80, .target = 81, .count = 15},
};
uint64_t adjusted_page_size = RawNandImageGetAdjustedPageSize(options);
// Two pages, 4 mappings per page. Two extra pages set max value(-1).
std::vector<MapPage> map_pages = {
{.logical_number = 1, .entries = {kFtlUnsetPageMapping, 5, 3, 4}},
{.logical_number = 2, .entries = {0, 1, 2, kFtlUnsetPageMapping}},
};
auto reader = std::make_unique<FakeReader>(volume_descriptor.block_size);
auto partition = Partition(volume_descriptor, address_descriptor, std::move(reader));
// One volume page and one map page.
uint64_t expected_volume_page_count = 6;
uint64_t expected_map_pages = 2;
// Map pages should be on a different block than volume pages.
uint64_t expected_map_page_offset =
fbl::round_up(expected_volume_page_count, options.pages_per_block) * adjusted_page_size;
uint64_t written_content_size =
expected_map_page_offset + expected_map_pages * adjusted_page_size;
FakeWriter writer;
auto write_result = FtlImageWrite(options, partition, &writer);
ASSERT_TRUE(write_result.is_ok()) << write_result.error();
ASSERT_THAT(writer.pages(), testing::SizeIs(written_content_size));
auto view = cpp20::span<const uint8_t>(writer.pages());
// Check volume page, which should be the first physical page written plus OOB size.
CheckVolumePagesInMapping(partition.address().mappings[0], options, 8, 0, partition.reader(),
writer.pages());
CheckVolumePagesInMapping(partition.address().mappings[1], options, 6, 3, partition.reader(),
writer.pages());
CheckVolumePagesInMapping(partition.address().mappings[2], options, 5, 5, partition.reader(),
writer.pages());
// Check that everything in between is 0xFF, so there are no unexpected values.
uint64_t skipped_offset = expected_volume_page_count * RawNandImageGetAdjustedPageSize(options);
auto not_written_contents =
view.subspan(skipped_offset, expected_map_page_offset - skipped_offset);
EXPECT_THAT(not_written_contents, testing::Each(testing::Eq(0xFF)));
for (uint32_t i = 0; i < map_pages.size(); ++i) {
const auto& map_page = map_pages[i];
// Check the map page.
CheckMapPage(
map_page,
view.subspan(expected_map_page_offset + i * adjusted_page_size, adjusted_page_size),
options);
}
}
TEST(FtlImageTest, FtlImageWriteWithBiggerSizeThanMappingAndNoFillingHasNoEffectIsOk) {
// The FTL doesn't need to map pages that need to be 'allocated' but not written, since this will
// done lazily when trying to write into the desired location.
RawNandOptions options;
options.oob_bytes_size = 16;
options.page_size = 16;
options.page_count = 100;
options.pages_per_block = 4;
auto volume_descriptor = MakeVolumeDescriptor();
AddressDescriptor address_descriptor;
address_descriptor.mappings = {{.source = 32, .target = 128, .count = 16, .size = 32}};
// Two pages, 4 mappings per page. Two extra pages set max value(-1).
MapPage map_page = {
.logical_number = 2,
.entries = {0, kFtlUnsetPageMapping, kFtlUnsetPageMapping, kFtlUnsetPageMapping}};
auto reader = std::make_unique<FakeReader>(volume_descriptor.block_size);
auto partition = Partition(volume_descriptor, address_descriptor, std::move(reader));
// One volume page and one map page.
uint64_t expected_volume_page_count = 1;
uint64_t expected_map_pages = 1;
// Map pages should be on a different block than volume pages.
uint64_t expected_map_page_offset =
fbl::round_up(expected_volume_page_count, options.pages_per_block) *
RawNandImageGetAdjustedPageSize(options);
uint64_t written_content_size =
expected_map_page_offset + expected_map_pages * RawNandImageGetAdjustedPageSize(options);
FakeWriter writer;
auto write_result = FtlImageWrite(options, partition, &writer);
ASSERT_TRUE(write_result.is_ok()) << write_result.error();
ASSERT_THAT(writer.pages(), testing::SizeIs(written_content_size));
auto view = cpp20::span<const uint8_t>(writer.pages());
// Check volume page, which should be the first physical page written plus OOB size.
CheckVolumePagesInMapping(partition.address().mappings[0], options, 8, 0, partition.reader(),
writer.pages());
// Check that everything in between is 0xFF, so there are no unexpected values.
uint64_t skipped_offset = expected_volume_page_count * RawNandImageGetAdjustedPageSize(options);
auto not_written_contents =
view.subspan(skipped_offset, expected_map_page_offset - skipped_offset);
EXPECT_THAT(not_written_contents, testing::Each(testing::Eq(0xFF)));
// Check the map page.
CheckMapPage(map_page,
view.subspan(expected_map_page_offset, RawNandImageGetAdjustedPageSize(options)),
options);
}
class ZeroReader final : public Reader {
public:
uint64_t length() const override { return std::numeric_limits<uint64_t>::max(); }
fpromise::result<void, std::string> Read(uint64_t offset,
cpp20::span<uint8_t> buffer) const final {
std::fill(buffer.begin(), buffer.end(), 0);
return fpromise::ok();
}
};
TEST(FtlImageTest, FtlImageWriteWithBiggerSizeThanMappingAndWithFillingMapsZeroedPagesAndIsOk) {
// The FTL doesn't need to map pages that need to be 'allocated' but not written, since this will
// done lazily when trying to write into the desired location.
RawNandOptions options;
options.oob_bytes_size = 16;
options.page_size = 16;
options.page_count = 100;
options.pages_per_block = 4;
auto volume_descriptor = MakeVolumeDescriptor();
AddressDescriptor address_descriptor;
address_descriptor.mappings = {{.source = 32,
.target = 128,
.count = 16,
.size = 50,
.options = {{EnumAsString(AddressMapOption::kFill), 0}}}};
// Two pages, 4 mappings per page. Two extra pages set max value(-1).
MapPage map_page = {.logical_number = 2, .entries = {0, 1, 2, 3}};
auto reader = std::make_unique<FakeReader>(volume_descriptor.block_size);
auto partition = Partition(volume_descriptor, address_descriptor, std::move(reader));
// One volume page and one map page.
uint64_t expected_volume_page_count = 4;
uint64_t expected_map_pages = 1;
// Map pages should be on a different block than volume pages.
uint64_t expected_map_page_offset =
fbl::round_up(expected_volume_page_count, options.pages_per_block) *
RawNandImageGetAdjustedPageSize(options);
uint64_t written_content_size =
expected_map_page_offset + expected_map_pages * RawNandImageGetAdjustedPageSize(options);
FakeWriter writer;
auto write_result = FtlImageWrite(options, partition, &writer);
ASSERT_TRUE(write_result.is_ok()) << write_result.error();
ASSERT_THAT(writer.pages(), testing::SizeIs(written_content_size));
auto view = cpp20::span<const uint8_t>(writer.pages());
// Check volume page, which should be the first physical page written plus OOB size.
CheckVolumePagesInMapping(partition.address().mappings[0], options, 8, 0, partition.reader(),
writer.pages());
// Check zeroed volume pages, which when the fill option is set,
AddressMap zeroed_mapping = {.source = 48, .target = 144, .count = 34};
ZeroReader zero_reader;
CheckVolumePagesInMapping(zeroed_mapping, options, 9, 1, &zero_reader, writer.pages());
// Check that everything in between is 0xFF, so there are no unexpected values.
uint64_t skipped_offset = expected_volume_page_count * RawNandImageGetAdjustedPageSize(options);
auto not_written_contents =
view.subspan(skipped_offset, expected_map_page_offset - skipped_offset);
EXPECT_THAT(not_written_contents, testing::Each(testing::Eq(0xFF)));
// Check the map page.
CheckMapPage(map_page,
view.subspan(expected_map_page_offset, RawNandImageGetAdjustedPageSize(options)),
options);
}
} // namespace
} // namespace storage::volume_image