blob: 947456488e4871e4f2e6509b6adf54583db8b1db [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 <fuchsia/device/c/fidl.h>
#include <fuchsia/device/llcpp/fidl.h>
#include <fuchsia/nand/c/fidl.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fd.h>
#include <lib/fdio/fdio.h>
#include <lib/fdio/unsafe.h>
#include <lib/fdio/watcher.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 {
// Open a device named "broker" from the path provided. Fails if there is no
// device after 5 seconds.
fbl::unique_fd OpenBroker(const char* path) {
fbl::unique_fd broker;
auto callback = [](int dir_fd, int event, const char* filename, void* cookie) {
if (event != WATCH_EVENT_ADD_FILE || strcmp(filename, "broker") != 0) {
return ZX_OK;
}
fbl::unique_fd* broker = reinterpret_cast<fbl::unique_fd*>(cookie);
broker->reset(openat(dir_fd, filename, O_RDWR));
return ZX_ERR_STOP;
};
fbl::unique_fd dir(open(path, O_DIRECTORY));
if (dir) {
zx_time_t deadline = zx_deadline_after(ZX_SEC(5));
fdio_watch_directory(dir.get(), callback, deadline, &broker);
}
return broker;
}
} // namespace.
NandBroker::NandBroker(const char* path) : path_(path), device_(open(path, O_RDWR)) {}
bool NandBroker::Initialize() {
if (!LoadBroker()) {
return false;
}
zx_status_t status = fdio_get_service_handle(device_.release(), caller_.reset_and_get_address());
if (status != ZX_OK) {
printf("Failed to get device handle: %s\n", zx_status_get_string(status));
return false;
}
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 (!caller_) {
return false;
}
zx_status_t status;
return fuchsia_nand_BrokerGetInfo(channel(), &status, &info_) == ZX_OK && status == ZX_OK;
}
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,
info_.nand_class);
}
bool NandBroker::ReadPages(uint32_t first_page, uint32_t count) const {
ZX_DEBUG_ASSERT(count <= info_.pages_per_block);
fuchsia_nand_BrokerRequest request = {};
request.length = count;
request.offset_nand = first_page;
request.offset_oob_vmo = info_.pages_per_block; // OOB is at the end of the VMO.
request.data_vmo = true;
request.oob_vmo = true;
zx::vmo vmo;
if (mapping_.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo) != ZX_OK) {
printf("Failed to duplicate VMO\n");
return false;
}
request.vmo = vmo.release();
zx_status_t status;
uint32_t bit_flips;
zx_status_t io_status = fuchsia_nand_BrokerRead(channel(), &request, &status, &bit_flips);
if (io_status != ZX_OK) {
printf("Failed to issue command to driver: %s\n", zx_status_get_string(io_status));
return false;
}
if (status != ZX_OK) {
printf("Read to %d pages starting at %d failed with %s\n", count, first_page,
zx_status_get_string(status));
return false;
}
if (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 (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, 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 {
fuchsia_nand_BrokerRequest request = {};
request.length = 1;
request.offset_nand = block;
zx_status_t status;
zx_status_t io_status = fuchsia_nand_BrokerErase(channel(), &request, &status);
if (io_status != ZX_OK) {
printf("Failed to issue erase command for block %d: %s\n", block,
zx_status_get_string(io_status));
return false;
}
if (status != ZX_OK) {
printf("Erase block %d failed with %s\n", block, zx_status_get_string(status));
return false;
}
return true;
}
bool NandBroker::LoadBroker() {
ZX_ASSERT(path_);
if (strstr(path_, "/broker") == path_ + strlen(path_) - strlen("/broker")) {
// The passed-in device is already a broker.
return true;
}
// A broker driver may or may not be loaded. Try to load it and see if that
// fails.
fdio_t* io = fdio_unsafe_fd_to_io(device_.get());
if (io == nullptr) {
printf("Could not convert fd to io\n");
return false;
}
zx_status_t call_status = ZX_OK;
const char kBroker[] = "/boot/driver/nand-broker.so";
auto resp = ::llcpp::fuchsia::device::Controller::Call::Bind(
zx::unowned_channel(fdio_unsafe_borrow_channel(io)),
::fidl::StringView(kBroker, sizeof(kBroker) - 1));
auto status = resp.status();
if (resp->result.is_err()) {
call_status = resp->result.err();
}
fdio_unsafe_release(io);
bool bind_failed = (status != ZX_OK || call_status != ZX_OK);
device_ = OpenBroker(path_);
if (!device_) {
if (bind_failed) {
printf("Failed to issue bind command for broker\n");
} else {
printf("Failed to bind broker\n");
}
return false;
}
return true;
}