| // 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 <fidl/fuchsia.nand/cpp/wire.h> |
| #include <fuchsia/hardware/nand/c/banjo.h> |
| #include <fuchsia/hardware/nand/cpp/banjo.h> |
| #include <lib/ddk/binding_driver.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/sync/completion.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <zircon/assert.h> |
| #include <zircon/types.h> |
| |
| #include <memory> |
| |
| #include <ddktl/device.h> |
| #include <fbl/alloc_checker.h> |
| |
| namespace { |
| |
| void nand_fidl_from_banjo(const nand_info_t& source, |
| fuchsia_hardware_nand::wire::Info* destination) { |
| destination->page_size = source.page_size; |
| destination->pages_per_block = source.pages_per_block; |
| destination->num_blocks = source.num_blocks; |
| destination->ecc_bits = source.ecc_bits; |
| destination->oob_size = source.oob_size; |
| destination->nand_class = static_cast<fuchsia_hardware_nand::wire::Class>(source.nand_class); |
| memcpy(destination->partition_guid.data(), &source.partition_guid, NAND_GUID_LEN); |
| } |
| |
| // Wrapper for a nand_operation_t. |
| class Operation { |
| public: |
| explicit Operation(size_t op_size) { |
| raw_buffer_.reset(new uint8_t[op_size]); |
| |
| memset(raw_buffer_.get(), 0, op_size); |
| } |
| ~Operation() {} |
| |
| nand_operation_t* GetOperation() { |
| return reinterpret_cast<nand_operation_t*>(raw_buffer_.get()); |
| } |
| |
| // Waits for the operation to complete and returns the operation's status. |
| zx_status_t Submit(ddk::NandProtocolClient& proxy) { |
| proxy.Queue(GetOperation(), OnCompletion, this); |
| |
| zx_status_t status = sync_completion_wait(&event_, ZX_TIME_INFINITE); |
| sync_completion_reset(&event_); |
| return status != ZX_OK ? status : status_; |
| } |
| |
| private: |
| static void OnCompletion(void* cookie, zx_status_t status, nand_operation_t* op) { |
| Operation* operation = reinterpret_cast<Operation*>(cookie); |
| operation->status_ = status; |
| sync_completion_signal(&operation->event_); |
| } |
| |
| sync_completion_t event_; |
| zx_status_t status_ = ZX_ERR_INTERNAL; |
| std::unique_ptr<uint8_t[]> raw_buffer_; |
| }; |
| |
| class Broker; |
| using DeviceType = ddk::Device<Broker, ddk::Messageable<fuchsia_nand::Broker>::Mixin>; |
| |
| // Exposes a control device (nand-broker) for a nand protocol device. |
| class Broker : public DeviceType { |
| public: |
| explicit Broker(zx_device_t* parent) : DeviceType(parent), nand_(parent) {} |
| ~Broker() {} |
| |
| zx_status_t Bind(); |
| void DdkRelease() { delete this; } |
| |
| // fidl interface. |
| void GetInfo(GetInfoCompleter::Sync& completer) { |
| fidl::Arena allocator; |
| fidl::ObjectView<fuchsia_hardware_nand::wire::Info> info(allocator); |
| completer.Reply(Query(info.get()), info); |
| } |
| void Read(ReadRequestView request, ReadCompleter::Sync& completer) { |
| uint32_t corrected_bits; |
| completer.Reply(Queue(NAND_OP_READ, request->request, &corrected_bits), corrected_bits); |
| } |
| void Write(WriteRequestView request, WriteCompleter::Sync& completer) { |
| completer.Reply(Queue(NAND_OP_WRITE, request->request, nullptr)); |
| } |
| void Erase(EraseRequestView request, EraseCompleter::Sync& completer) { |
| completer.Reply(Queue(NAND_OP_ERASE, request->request, nullptr)); |
| } |
| void ReadBytes(ReadBytesRequestView request, ReadBytesCompleter::Sync& completer) { |
| completer.Reply(QueueBytes(NAND_OP_READ_BYTES, request->request)); |
| } |
| void WriteBytes(WriteBytesRequestView request, WriteBytesCompleter::Sync& completer) { |
| completer.Reply(QueueBytes(NAND_OP_WRITE_BYTES, request->request)); |
| } |
| |
| private: |
| zx_status_t Query(fuchsia_hardware_nand::wire::Info* info); |
| zx_status_t Queue(uint32_t command, fuchsia_nand::wire::BrokerRequestData& request, |
| uint32_t* corrected_bits); |
| zx_status_t QueueBytes(uint32_t command, fuchsia_nand::wire::BrokerRequestDataBytes& request); |
| |
| ddk::NandProtocolClient nand_; |
| size_t op_size_ = 0; |
| }; |
| |
| zx_status_t Broker::Bind() { |
| if (!nand_.is_valid()) { |
| zxlogf(ERROR, "nand-broker: device does not support nand protocol"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| fuchsia_hardware_nand::wire::Info info; |
| Query(&info); |
| if (!op_size_) { |
| zxlogf(ERROR, "nand-broker: unable to query the nand driver"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| zxlogf(INFO, "nand-broker: %d blocks of %d pages each. Page size: %d", info.num_blocks, |
| info.pages_per_block, info.page_size); |
| |
| return DdkAdd("broker"); |
| } |
| |
| zx_status_t Broker::Query(fuchsia_hardware_nand::wire::Info* info) { |
| nand_info_t temp_info; |
| nand_.Query(&temp_info, &op_size_); |
| nand_fidl_from_banjo(temp_info, info); |
| return ZX_OK; |
| } |
| |
| zx_status_t Broker::QueueBytes(uint32_t command, |
| fuchsia_nand::wire::BrokerRequestDataBytes& request) { |
| Operation operation(op_size_); |
| nand_operation_t* op = operation.GetOperation(); |
| op->rw_bytes.command = command; |
| op->rw_bytes.offset_nand = request.offset_nand; |
| op->rw_bytes.offset_data_vmo = request.offset_data_vmo; |
| op->rw_bytes.data_vmo = request.vmo.get(); |
| op->rw_bytes.length = request.length; |
| |
| return operation.Submit(nand_); |
| } |
| |
| zx_status_t Broker::Queue(uint32_t command, fuchsia_nand::wire::BrokerRequestData& request, |
| uint32_t* corrected_bits) { |
| Operation operation(op_size_); |
| nand_operation_t* op = operation.GetOperation(); |
| op->rw.command = command; |
| |
| switch (command) { |
| case NAND_OP_READ: |
| case NAND_OP_WRITE: |
| op->rw.length = request.length; |
| op->rw.offset_nand = request.offset_nand; |
| op->rw.offset_data_vmo = request.offset_data_vmo; |
| op->rw.offset_oob_vmo = request.offset_oob_vmo; |
| op->rw.data_vmo = request.data_vmo ? request.vmo.get() : ZX_HANDLE_INVALID; |
| op->rw.oob_vmo = request.oob_vmo ? request.vmo.get() : ZX_HANDLE_INVALID; |
| break; |
| case NAND_OP_ERASE: |
| op->erase.first_block = request.offset_nand; |
| op->erase.num_blocks = request.length; |
| break; |
| default: |
| ZX_DEBUG_ASSERT(false); |
| } |
| |
| zx_status_t status = operation.Submit(nand_); |
| |
| if (command == NAND_OP_READ) { |
| *corrected_bits = op->rw.corrected_bit_flips; |
| } |
| |
| return status; |
| } |
| |
| zx_status_t NandBrokerBind(void* ctx, zx_device_t* parent) { |
| zxlogf(INFO, "nand-broker: binding"); |
| fbl::AllocChecker checker; |
| std::unique_ptr<Broker> device(new (&checker) Broker(parent)); |
| if (!checker.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx_status_t status = device->Bind(); |
| if (status == ZX_OK) { |
| // devmgr is now in charge of the device. |
| [[maybe_unused]] Broker* dummy = device.release(); |
| } |
| return status; |
| } |
| |
| static constexpr zx_driver_ops_t nand_broker_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = NandBrokerBind; |
| return ops; |
| }(); |
| |
| } // namespace |
| |
| ZIRCON_DRIVER(nand_broker, nand_broker_ops, "zircon", "0.1"); |