blob: be2e2e81ffa458770189156429b957bbf95edfb0 [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 <fuchsia/nand/c/fidl.h>
#include <lib/sync/completion.h>
#include <stdio.h>
#include <string.h>
#include <zircon/assert.h>
#include <zircon/types.h>
#include <memory>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/protocol/nand.h>
#include <ddktl/device.h>
#include <ddktl/protocol/nand.h>
#include <fbl/alloc_checker.h>
namespace {
// 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::Unbindable, ddk::Messageable>;
// 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; }
// Device protocol implementation.
void DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); }
zx_status_t DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn);
// fidl interface.
zx_status_t GetInfo(fuchsia_hardware_nand_Info* info) { return Query(info); }
zx_status_t Read(const fuchsia_nand_BrokerRequest& request, uint32_t* corrected_bits) {
return Queue(NAND_OP_READ, request, corrected_bits);
}
zx_status_t Write(const fuchsia_nand_BrokerRequest& request) {
return Queue(NAND_OP_WRITE, request, nullptr);
}
zx_status_t Erase(const fuchsia_nand_BrokerRequest& request) {
return Queue(NAND_OP_ERASE, request, nullptr);
}
private:
zx_status_t Query(fuchsia_hardware_nand_Info* info);
zx_status_t Queue(uint32_t command, const fuchsia_nand_BrokerRequest& request,
uint32_t* corrected_bits);
ddk::NandProtocolClient nand_;
size_t op_size_ = 0;
};
zx_status_t GetInfo(void* ctx, fidl_txn_t* txn) {
Broker* device = reinterpret_cast<Broker*>(ctx);
fuchsia_hardware_nand_Info info;
zx_status_t status = device->GetInfo(&info);
return fuchsia_nand_BrokerGetInfo_reply(txn, status, &info);
}
zx_status_t Read(void* ctx, const fuchsia_nand_BrokerRequest* request, fidl_txn_t* txn) {
Broker* device = reinterpret_cast<Broker*>(ctx);
uint32_t corrected_bits = 0;
zx_status_t status = device->Read(*request, &corrected_bits);
return fuchsia_nand_BrokerRead_reply(txn, status, corrected_bits);
}
zx_status_t Write(void* ctx, const fuchsia_nand_BrokerRequest* request, fidl_txn_t* txn) {
Broker* device = reinterpret_cast<Broker*>(ctx);
zx_status_t status = device->Write(*request);
return fuchsia_nand_BrokerWrite_reply(txn, status);
}
zx_status_t Erase(void* ctx, const fuchsia_nand_BrokerRequest* request, fidl_txn_t* txn) {
Broker* device = reinterpret_cast<Broker*>(ctx);
zx_status_t status = device->Erase(*request);
return fuchsia_nand_BrokerErase_reply(txn, status);
}
// clang-format off
fuchsia_nand_Broker_ops_t fidl_ops = {
.GetInfo = GetInfo,
.Read = Read,
.Write = Write,
.Erase = Erase
};
// clang-format on
zx_status_t Broker::Bind() {
if (!nand_.is_valid()) {
zxlogf(ERROR, "nand-broker: device '%s' does not support nand protocol",
device_get_name(parent()));
return ZX_ERR_NOT_SUPPORTED;
}
fuchsia_hardware_nand_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::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) {
return fuchsia_nand_Broker_dispatch(this, txn, msg, &fidl_ops);
}
zx_status_t Broker::Query(fuchsia_hardware_nand_Info* info) {
nand_.Query(info, &op_size_);
return ZX_OK;
}
zx_status_t Broker::Queue(uint32_t command, const fuchsia_nand_BrokerRequest& 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 : ZX_HANDLE_INVALID;
op->rw.oob_vmo = request.oob_vmo ? request.vmo : 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;
}
if ((command == NAND_OP_READ || command == NAND_OP_WRITE) && request.vmo != ZX_HANDLE_INVALID) {
zx_handle_close(request.vmo);
}
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.
__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
// clang-format off
ZIRCON_DRIVER_BEGIN(nand_broker, nand_broker_ops, "zircon", "0.1", 2)
BI_ABORT_IF_AUTOBIND,
BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_NAND)
ZIRCON_DRIVER_END(nand_broker)
// clang-format on