blob: 40d2006e80f8bfbfecd9e10045442c6675c29b09 [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 "ftl_raw_nand_image_writer.h"
#include <lib/fit/result.h>
#include <cstdint>
#include <iostream>
#include <tuple>
#include <type_traits>
#include <fbl/algorithm.h>
#include <safemath/safe_conversions.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"
namespace storage::volume_image {
fit::result<std::tuple<FtlRawNandImageWriter, RawNandOptions>, std::string>
FtlRawNandImageWriter::Create(const RawNandOptions& device_options,
fbl::Span<const RawNandImageFlag> flags, ImageFormat format,
Writer* writer) {
if (writer == nullptr) {
return fit::error(
"Failed to created |FtlRawNandImageWriter|. Argument |writer| must be a non null.");
}
if (device_options.oob_bytes_size == 0 || device_options.pages_per_block == 0) {
return fit::error(
"Failed to create |FtlRawNandImageWriter|. Arguments |device_options| must have non zero "
"|oob_bytes_size| and non-zero |pages_per_block|.");
}
RawNandOptions ftl_options = device_options;
uint32_t multiplier = 1;
// Find the number of pages to coalesce. Needs to be a divisor of the pages per block, since
// we can only coalesce pages within the same block.
while (ftl_options.oob_bytes_size < ftl_image_internal::kFtlMinOobByteSize) {
// Keep multiplier within block_boundaries.
do {
multiplier++;
} while (device_options.pages_per_block % multiplier != 0 &&
multiplier <= device_options.pages_per_block);
if (static_cast<uint32_t>(multiplier) > device_options.pages_per_block) {
return fit::error(
"FtlRawNandImageWriter failed to create. Not enough spare bytes in block for the FTL.");
}
ftl_options.page_size = device_options.page_size * multiplier;
ftl_options.oob_bytes_size =
safemath::checked_cast<uint8_t>(device_options.oob_bytes_size * multiplier);
ftl_options.page_count = device_options.page_count / multiplier;
ftl_options.pages_per_block = device_options.pages_per_block / multiplier;
}
RawNandImageHeader header;
header.format = format;
header.page_size = safemath::checked_cast<uint32_t>(device_options.page_size);
header.oob_size = device_options.oob_bytes_size;
for (auto flag : flags) {
header.flags |= static_cast<std::underlying_type<RawNandImageFlag>::type>(flag);
}
auto write_result =
writer->Write(0, fbl::Span<const uint8_t>(reinterpret_cast<const uint8_t*>(&header),
sizeof(RawNandImageHeader)));
if (write_result.is_error()) {
return write_result.take_error_result();
}
return fit::ok(
std::make_tuple(FtlRawNandImageWriter(device_options, multiplier, writer), ftl_options));
}
fit::result<void, std::string> FtlRawNandImageWriter::Write(uint64_t offset,
fbl::Span<const uint8_t> data) {
uint64_t device_adjusted_page_size = RawNandImageGetAdjustedPageSize(options_);
uint64_t adjusted_page_size = scale_factor_ * device_adjusted_page_size;
uint64_t page_offset = offset % adjusted_page_size;
uint64_t page_number = offset / adjusted_page_size;
uint64_t physical_page_per_logical = scale_factor_;
uint64_t base_image_page_offset =
RawNandImageGetPageOffset(page_number * scale_factor_, options_) + sizeof(RawNandImageHeader);
// It is a page write.
if (page_offset == 0) {
if (data.size() != physical_page_per_logical * options_.page_size) {
return fit::error(
"FtlRawNandImageWriter requires buffer size match the number of physical pages per "
"logical page.");
}
// Write each page.
for (uint64_t physical_page_offset = 0; physical_page_offset < physical_page_per_logical;
++physical_page_offset) {
auto relative_physical_page_offset = physical_page_offset * options_.page_size;
auto physical_page_view = data.subspan(relative_physical_page_offset, options_.page_size);
auto image_relative_page_offset = physical_page_offset * device_adjusted_page_size;
auto write_result =
writer_->Write(base_image_page_offset + image_relative_page_offset, physical_page_view);
if (write_result.is_error()) {
return write_result;
}
}
return fit::ok();
}
// Write each oob area.
if (page_offset == physical_page_per_logical * options_.page_size) {
if (data.size() != physical_page_per_logical * options_.oob_bytes_size) {
return fit::error(
"FtlRawNandImageWriter requires buffer size match the number of physical oob area per "
"logical oob area per logical page.");
}
// Write each page.
for (uint64_t physical_page_offset = 0; physical_page_offset < physical_page_per_logical;
++physical_page_offset) {
auto relative_physical_page_offset = physical_page_offset * options_.oob_bytes_size;
auto physical_page_view =
data.subspan(relative_physical_page_offset, options_.oob_bytes_size);
auto image_relative_page_oob_offset =
physical_page_offset * device_adjusted_page_size + options_.page_size;
auto write_result = writer_->Write(base_image_page_offset + image_relative_page_oob_offset,
physical_page_view);
if (write_result.is_error()) {
return write_result;
}
}
return fit::ok();
}
return fit::error(
"FtlRawNandImageWriter write failed. Unaligned page or unaligned oob write. Actual "
"offset " +
std::to_string(page_offset));
}
} // namespace storage::volume_image