blob: a058d35e2e1fdf2e866fbd94289d5da691d8f66f [file] [log] [blame]
// Copyright 2021 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.device.runtime.test/cpp/driver/fidl.h>
#include <fidl/fuchsia.device.runtime.test/cpp/wire.h>
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/fdf/cpp/channel_read.h>
#include <lib/fdf/cpp/dispatcher.h>
#include <lib/fidl/cpp/wire/arena.h>
#include <lib/sync/completion.h>
#include <ddktl/device.h>
using fuchsia_device_runtime_test::TestDeviceChild;
class Device;
using DeviceType = ddk::Device<Device, ddk::Unbindable, ddk::Messageable<TestDeviceChild>::Mixin>;
class Device : public DeviceType {
public:
static zx_status_t Bind(void* ctx, zx_device_t* device);
Device(zx_device_t* parent, fdf::Channel ch_to_parent)
: DeviceType(parent), ch_to_parent_(std::move(ch_to_parent)), unbind_txn_(nullptr) {}
// TestDeviceChild protocol implementation.
void GetParentDataOverRuntimeChannel(
GetParentDataOverRuntimeChannelRequestView request,
GetParentDataOverRuntimeChannelCompleter::Sync& completer) override;
void SendRequestSync(fdf::Arena arena, void* req, uint32_t req_size,
GetParentDataOverRuntimeChannelCompleter::Sync& completer);
void SendRequestAsync(fdf::Arena arena, void* req, uint32_t req_size,
GetParentDataOverRuntimeChannelCompleter::Sync& completer);
// Device protocol implementation.
void DdkUnbind(ddk::UnbindTxn txn) {
dispatcher_.ShutdownAsync();
unbind_txn_ = std::move(txn);
}
void DdkRelease() { delete this; }
zx_status_t Init();
void ShutdownHandler(fdf_dispatcher_t* dispatcher);
private:
fdf::Channel ch_to_parent_;
fdf::Dispatcher dispatcher_;
ddk::UnbindTxn unbind_txn_;
};
void Device::GetParentDataOverRuntimeChannel(
GetParentDataOverRuntimeChannelRequestView request,
GetParentDataOverRuntimeChannelCompleter::Sync& completer) {
fdf::Arena arena('GPDA');
// Send a request to the parent driver over the runtime channel.
auto req = fuchsia_device_runtime_test::wire::RuntimeRequest::kGetData;
uint32_t total_size = sizeof(fdf_txid_t) + sizeof(req);
void* ptr = arena.Allocate(total_size);
void* data_start = static_cast<uint8_t*>(ptr) + sizeof(fdf_txid_t);
memcpy(data_start, &req, sizeof(req));
if (request->sync) {
SendRequestSync(std::move(arena), ptr, total_size, completer);
} else {
SendRequestAsync(std::move(arena), ptr, total_size, completer);
}
}
void Device::SendRequestSync(fdf::Arena arena, void* req, uint32_t req_size,
GetParentDataOverRuntimeChannelCompleter::Sync& completer) {
auto read =
ch_to_parent_.Call(0, zx::time::infinite(), arena, req, req_size, cpp20::span<zx_handle_t>());
if (read.is_error()) {
completer.ReplyError(read.status_value());
return;
}
// Reply to the test's fidl request with the data.
void* data_start = static_cast<uint8_t*>(read->data) + sizeof(fdf_txid_t);
size_t data_size = read->num_bytes - sizeof(fdf_txid_t);
fidl::Arena fidl_arena;
fidl::VectorView<uint8_t> out_data(fidl_arena, data_size);
auto* out_ptr = out_data.data();
memcpy(out_ptr, data_start, data_size);
out_data.set_count(data_size);
completer.ReplySuccess(std::move(out_data));
}
void Device::SendRequestAsync(fdf::Arena arena, void* req, uint32_t req_size,
GetParentDataOverRuntimeChannelCompleter::Sync& completer) {
auto write_status = ch_to_parent_.Write(0, arena, req, req_size, cpp20::span<zx_handle_t>());
if (write_status.is_error()) {
completer.ReplyError(write_status.status_value());
return;
}
auto channel_read = std::make_unique<fdf::ChannelRead>(
ch_to_parent_.get(), 0 /* options */,
[async_completer = completer.ToAsync()](fdf_dispatcher_t* dispatcher,
fdf::ChannelRead* channel_read,
zx_status_t status) mutable {
fdf::UnownedChannel channel(channel_read->channel());
auto read = channel->Read(0);
if (read.is_error()) {
async_completer.ReplyError(read.status_value());
return;
}
// Reply to the test's fidl request with the data.
void* data_start = static_cast<uint8_t*>(read->data) + sizeof(fdf_txid_t);
size_t data_size = read->num_bytes - sizeof(fdf_txid_t);
fidl::Arena fidl_arena;
fidl::VectorView<uint8_t> out_data(fidl_arena, data_size);
auto* out_ptr = out_data.data();
memcpy(out_ptr, data_start, data_size);
out_data.set_count(data_size);
async_completer.ReplySuccess(std::move(out_data));
delete channel_read;
});
zx_status_t status = channel_read->Begin(dispatcher_.get());
ZX_ASSERT(status == ZX_OK);
channel_read.release(); // Deleted on callback.
}
void Device::ShutdownHandler(fdf_dispatcher_t* dispatcher) { unbind_txn_.Reply(); }
zx_status_t Device::Init() {
auto dispatcher = fdf::SynchronizedDispatcher::Create(
{}, "child-driver", fit::bind_member(this, &Device::ShutdownHandler));
if (dispatcher.is_error()) {
return dispatcher.status_value();
}
dispatcher_ = *std::move(dispatcher);
return ZX_OK;
}
// static
zx_status_t Device::Bind(void* ctx, zx_device_t* device) {
// Connect to our parent driver.
auto client_end = DdkConnectRuntimeProtocol<fuchsia_device_runtime_test::Service::Parent>(device);
if (client_end.is_error()) {
return client_end.status_value();
}
auto dev = std::make_unique<Device>(device, client_end->TakeHandle());
zx_status_t status = dev->Init();
if (status != ZX_OK) {
return status;
}
status = dev->DdkAdd("child");
if (status == ZX_OK) {
// devmgr is now in charge of the memory for dev
[[maybe_unused]] auto ptr = dev.release();
}
return status;
}
static zx_driver_ops_t kDriverOps = []() -> zx_driver_ops_t {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = Device::Bind;
return ops;
}();
ZIRCON_DRIVER(driver_runtime_test_child, kDriverOps, "zircon", "0.1");