| // 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 <stdio.h> |
| |
| #include <new> |
| |
| #include <fuchsia/device/c/fidl.h> |
| #include <fuchsia/nand/c/fidl.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/unsafe.h> |
| #include <lib/fdio/watcher.h> |
| #include <lib/zx/vmo.h> |
| #include <pretty/hexdump.h> |
| #include <zircon/assert.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.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; |
| } |
| |
| 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; |
| } |
| |
| 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; |
| const char kBroker[] = "/boot/driver/nand-broker.so"; |
| zx_status_t status = fuchsia_device_ControllerBind(fdio_unsafe_borrow_channel(io), |
| kBroker, sizeof(kBroker) - 1, |
| &call_status); |
| fdio_unsafe_release(io); |
| if (status != ZX_OK || call_status != ZX_OK) { |
| fprintf(stderr, "Failed to issue bind command\n"); |
| return false; |
| } |
| |
| device_ = OpenBroker(path_); |
| if (!device_) { |
| printf("Failed to bind broker\n"); |
| return false; |
| } |
| return true; |
| } |