blob: 78e4826809e1b7ae4733d294fe1ae6d30076cc30 [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/fpromise/result.h>
#include <lib/stdcompat/span.h>
#include <cassert>
#include <cstdint>
#include <iostream>
#include <limits>
#include <map>
#include <string>
#include <type_traits>
#include <vector>
#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.h"
#include "src/storage/volume_image/ftl/raw_nand_image_utils.h"
#include "src/storage/volume_image/options.h"
#include "src/storage/volume_image/utils/block_utils.h"
namespace storage::volume_image {
namespace {
// Write volume page as a succession of physical pages, and use the mapping to
// convert the raw FTL image into a sparse block image.
class FtlPageWriter {
public:
explicit FtlPageWriter(const RawNandOptions& ftl_options) : options_(ftl_options) {}
// Returns |fpromise::ok| on success, when a new |RawNandPage| has been written with
// |page_content| in the data section and the appropiate FTL metadata in the spare area section
// for a volume page, into |writer|.
fpromise::result<void, std::string> WriteVolumePage(uint64_t logical_page,
cpp20::span<const uint8_t> page_content,
Writer* writer) {
std::vector<uint8_t> oob_byte_buffer(options_.oob_bytes_size, 0xFF);
ftl_image_internal::WriteOutOfBandBytes<ftl_image_internal::PageType::kVolumePage>(
safemath::checked_cast<uint32_t>(logical_page), oob_byte_buffer);
uint64_t page_offset = RawNandImageGetPageOffset(physical_page_count_, options_);
auto write_result = RawNandImageWritePage(page_content, oob_byte_buffer, page_offset, writer);
if (write_result.is_error()) {
return write_result.take_error_result();
}
if (logical_to_physical_map_.find(safemath::checked_cast<uint32_t>(logical_page)) !=
logical_to_physical_map_.end()) {
return fpromise::error("FTL Image: |Partition::address().mappings| may not share pages.");
}
logical_to_physical_map_[safemath::checked_cast<uint32_t>(logical_page)] =
safemath::checked_cast<uint32_t>(physical_page_count_);
physical_page_count_++;
return fpromise::ok();
}
// Returns |fpromise::ok| on success, writing all map pages required to support the written volume
// pages, in the next available block, since the FTL does not share blocks between volume and map
// pages.
fpromise::result<void, std::string> WriteMapBlock(Writer* writer) {
uint64_t next_free_page_offset = RawNandImageGetPageOffset(physical_page_count_, options_);
uint64_t start_of_block_offset =
RawNandImageGetNextEraseBlockOffset(next_free_page_offset, options_);
auto result = ftl_image_internal::WriteMapBlock(logical_to_physical_map_, options_,
start_of_block_offset, writer);
return result;
}
private:
const RawNandOptions& options_;
uint64_t physical_page_count_ = 0;
std::map<uint32_t, uint32_t> logical_to_physical_map_;
};
} // namespace
fpromise::result<void, std::string> FtlImageWrite(const RawNandOptions& options,
const Partition& partition, Writer* writer) {
if (options.oob_bytes_size < ftl_image_internal::kFtlMinOobByteSize) {
return fpromise::error("FTL requires at least " +
std::to_string(ftl_image_internal::kFtlMinOobByteSize) +
" bytes in OOB bytes. Requested OOB bytes size is " +
std::to_string(options.oob_bytes_size) + ".");
}
FtlPageWriter ftl_writer(options);
std::vector<uint8_t> page_buffer(options.page_size);
for (const auto& mapping : partition.address().mappings) {
uint64_t byte_count = mapping.size.value_or(mapping.count);
uint64_t logical_page_start = GetBlockFromBytes(mapping.target, options.page_size);
uint64_t written_page_count = GetBlockCount(mapping.target, mapping.count, options.page_size);
uint64_t zeroed_page_count =
GetBlockCount(mapping.target, byte_count, options.page_size) - written_page_count;
uint64_t read_bytes = 0;
// Read from the source reader the bytes that go in each page backed by the partition reader.
for (uint32_t i = 0; i < written_page_count; ++i) {
page_buffer.assign(page_buffer.size(), 0);
uint64_t read_offset = mapping.source + read_bytes;
uint64_t current_offset = mapping.target + read_bytes;
uint64_t current_page_start = GetOffsetFromBlockStart(current_offset, options.page_size);
uint64_t remaining_bytes = mapping.count - read_bytes;
uint64_t buffer_size = remaining_bytes;
if (current_page_start + remaining_bytes > options.page_size) {
buffer_size = options.page_size - current_page_start;
}
auto view = cpp20::span<uint8_t>(page_buffer).subspan(current_page_start, buffer_size);
auto read_result = partition.reader()->Read(read_offset, view);
if (read_result.is_error()) {
return read_result.take_error_result();
}
read_bytes += view.size();
auto write_result = ftl_writer.WriteVolumePage(logical_page_start + i, page_buffer, writer);
if (write_result.is_error()) {
return write_result.take_error_result();
}
}
// We should only write and map this pages if we need to fill with some content, otherwise,
// the FTL will either return garbage when read or will map a page on demand when written to.
if (mapping.options.find(EnumAsString(AddressMapOption::kFill)) == mapping.options.end()) {
continue;
}
// Clear the page buffer.
page_buffer.assign(page_buffer.size(), 0);
for (uint32_t i = 0; i < zeroed_page_count; ++i) {
auto write_result = ftl_writer.WriteVolumePage(logical_page_start + written_page_count + i,
page_buffer, writer);
if (write_result.is_error()) {
return write_result.take_error_result();
}
}
}
return ftl_writer.WriteMapBlock(writer);
}
} // namespace storage::volume_image