| // 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"); |