| // 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 |