blob: 42668b94eeac3c07dadf13821f67743f87cd3f28 [file] [log] [blame]
// Copyright 2018 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 "nand-broker.h"
#include <fcntl.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/watcher.h>
#include <lib/stdcompat/string_view.h>
#include <lib/zx/vmo.h>
#include <stdio.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <new>
#include <pretty/hexdump.h>
namespace {
const char* kBrokerFilename = "broker";
// Looks for a device named "broker" from the path provided. Fails if there is no
// device after 5 seconds.
zx_status_t FindBroker(const std::string& path) {
auto callback = [](int dir_fd, int event, const char* filename, void* cookie) {
if (event != WATCH_EVENT_ADD_FILE || strcmp(filename, kBrokerFilename) != 0) {
return ZX_OK;
}
return ZX_ERR_STOP;
};
fbl::unique_fd dir(open(path.c_str(), O_DIRECTORY));
if (!dir) {
return ZX_ERR_NOT_FOUND;
}
zx_time_t deadline = zx_deadline_after(ZX_SEC(5));
return fdio_watch_directory(dir.get(), callback, deadline, nullptr);
}
} // namespace.
NandBroker::NandBroker(const char* path) : path_(path) {}
bool NandBroker::Initialize() {
auto broker_channel = LoadBroker();
if (broker_channel.is_error()) {
printf("Failed to get device handle: %s\n", zx_status_get_string(broker_channel.error_value()));
return false;
}
broker_client_.Bind(std::move(broker_channel.value()));
if (!Query()) {
printf("Failed to open or query the device\n");
return false;
}
const uint32_t size = (info_.page_size() + info_.oob_size()) * info_.pages_per_block();
if (mapping_.CreateAndMap(size, "nand-broker-vmo") != ZX_OK) {
printf("Failed to allocate VMO\n");
return false;
}
return true;
}
void NandBroker::SetFtl(std::unique_ptr<FtlInfo> ftl) { ftl_ = std::move(ftl); }
bool NandBroker::Query() {
if (!broker_client_.is_valid()) {
return false;
}
auto result = broker_client_->GetInfo();
if (result.is_ok() && result->status() == ZX_OK) {
info_ = std::move(result->info().value());
return true;
}
return false;
}
void NandBroker::ShowInfo() const {
printf(
"Page size: %d\nPages per block: %d\nTotal Blocks: %d\nOOB size: %d\nECC bits: %d\n"
"Nand class: %d\n",
info_.page_size(), info_.pages_per_block(), info_.num_blocks(), info_.oob_size(),
info_.ecc_bits(), static_cast<int>(info_.nand_class()));
}
bool NandBroker::ReadPages(uint32_t first_page, uint32_t count) const {
ZX_DEBUG_ASSERT(count <= info_.pages_per_block());
zx::vmo vmo;
if (mapping_.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo) != ZX_OK) {
printf("Failed to duplicate VMO\n");
return false;
}
fuchsia_nand::BrokerRequestData request{{
.vmo = std::move(vmo),
.length = count,
.offset_nand = first_page,
.offset_oob_vmo = info_.pages_per_block(), // OOB is at the end of the VMO.
.data_vmo = true,
.oob_vmo = true,
}};
auto result = broker_client_->Read(std::move(request));
if (result.is_error()) {
printf("Failed to issue command to driver: %s\n", result.error_value().status_string());
return false;
}
if (result->status() != ZX_OK) {
printf("Read to %d pages starting at %d failed with %s\n", count, first_page,
zx_status_get_string(result->status()));
return false;
}
if (result->corrected_bit_flips() > info_.ecc_bits()) {
printf("Read to %d pages starting at %d unable to correct all bit flips\n", count, first_page);
} else if (result->corrected_bit_flips()) {
// If the nand protocol is modified to provide more info, we could display something
// like average bit flips.
printf("Read to %d pages starting at %d corrected %d errors\n", count, first_page,
result->corrected_bit_flips());
}
return true;
}
bool NandBroker::DumpPage(uint32_t page) const {
if (!ReadPages(page, 1)) {
return false;
}
ZX_DEBUG_ASSERT(info_.page_size() % 16 == 0);
uint32_t address = page * info_.page_size();
hexdump8_ex(data(), 16, address);
int skip = 0;
for (uint32_t line = 16; line < info_.page_size(); line += 16) {
if (memcmp(data() + line, data() + line - 16, 16) == 0) {
skip++;
if (skip < 50) {
printf(".");
}
continue;
}
if (skip) {
printf("\n");
skip = 0;
}
hexdump8_ex(data() + line, 16, address + line);
}
if (skip) {
printf("\n");
}
printf("OOB:\n");
hexdump8_ex(oob(), info_.oob_size(), address + info_.page_size());
return true;
}
bool NandBroker::EraseBlock(uint32_t block) const {
auto result = broker_client_->Erase({{.request = {{.length = 1, .offset_nand = block}}}});
if (result.is_error()) {
printf("Failed to issue erase command for block %d: %s\n", block,
result.error_value().lossy_description());
return false;
}
if (result->status() != ZX_OK) {
printf("Erase block %d failed with %s\n", block, zx_status_get_string(result->status()));
return false;
}
return true;
}
zx::result<fidl::ClientEnd<fuchsia_nand::Broker>> NandBroker::LoadBroker() {
std::string broker_path;
if (!cpp20::ends_with(std::string_view{path_}, "/broker")) {
auto controller_channel = component::Connect<fuchsia_device::Controller>(path_);
// A broker driver may or may not be loaded. Try to load it and see if that
// fails.
if (controller_channel.is_error()) {
printf("Could not connect to device controller at: %s\n", path_.c_str());
return controller_channel.take_error();
}
const char kBroker[] = "/boot/meta/nand-broker.cm";
auto resp = fidl::WireCall(controller_channel.value())->Bind(kBroker);
zx_status_t call_status = ZX_OK;
auto status = resp.status();
if (resp->is_error()) {
call_status = resp->error_value();
}
bool bind_failed = (status != ZX_OK || call_status != ZX_OK);
status = FindBroker(path_);
if (status != ZX_OK) {
if (bind_failed) {
printf("Failed to issue bind command for broker\n");
} else {
printf("Failed to bind broker\n");
}
return zx::error(status);
}
broker_path = path_ + '/' + kBrokerFilename;
} else {
// The passed-in device is already a broker.
broker_path = path_;
}
return component::Connect<fuchsia_nand::Broker>(broker_path);
}