text: // 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
  