| // Copyright 2019 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 "src/media/audio/drivers/virtual_audio/virtual_audio_control_impl.h" |
| |
| #include <lib/async/cpp/task.h> |
| #include <lib/ddk/binding_driver.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/fdf/cpp/dispatcher.h> |
| #include <lib/zx/result.h> |
| #include <zircon/errors.h> |
| |
| #include <optional> |
| |
| #include <ddktl/fidl.h> |
| |
| #include "src/media/audio/drivers/virtual_audio/virtual_audio_codec.h" |
| #include "src/media/audio/drivers/virtual_audio/virtual_audio_composite.h" |
| #include "src/media/audio/drivers/virtual_audio/virtual_audio_dai.h" |
| #include "src/media/audio/drivers/virtual_audio/virtual_audio_device_impl.h" |
| #include "src/media/audio/drivers/virtual_audio/virtual_audio_stream.h" |
| |
| namespace virtual_audio { |
| |
| // static |
| zx_status_t VirtualAudioControlImpl::DdkBind(void* ctx, zx_device_t* parent_bus) { |
| std::unique_ptr<VirtualAudioControlImpl> control(new VirtualAudioControlImpl); |
| |
| // Define entry-point operations for this control device. |
| static zx_protocol_device_t device_ops = { |
| .version = DEVICE_OPS_VERSION, |
| .unbind = &DdkUnbind, |
| .release = &DdkRelease, |
| .message = &DdkMessage, |
| }; |
| |
| // Define other metadata, incl. "control" as our entry-point context. |
| device_add_args_t args = {}; |
| args.version = DEVICE_ADD_ARGS_VERSION; |
| args.name = "virtual_audio"; |
| args.ctx = control.get(); |
| args.ops = &device_ops; |
| args.flags = DEVICE_ADD_NON_BINDABLE; |
| |
| // Add the virtual_audio device node under the given parent. |
| zx_status_t status = device_add(parent_bus, &args, &control->dev_node_); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s could not add device '%s': %d", __func__, args.name, status); |
| return status; |
| } |
| |
| zxlogf(INFO, "%s added device '%s': %d", __func__, args.name, status); |
| |
| // Use the dispatcher supplied by the driver runtime. |
| control->dispatcher_ = fdf::Dispatcher::GetCurrent()->async_dispatcher(); |
| |
| // On successful Add, Devmgr takes ownership (relinquished on DdkRelease), so transfer our |
| // ownership to a local var, and let it go out of scope. |
| [[maybe_unused]] auto temp_ref = control.release(); |
| |
| return ZX_OK; |
| } |
| |
| // static |
| void VirtualAudioControlImpl::DdkUnbind(void* ctx) { |
| ZX_ASSERT(ctx); |
| |
| auto self = static_cast<VirtualAudioControlImpl*>(ctx); |
| if (self->devices_.empty()) { |
| zxlogf(INFO, "%s with no devices; unbinding self", __func__); |
| device_unbind_reply(self->dev_node_); |
| return; |
| } |
| |
| // Close any remaining device bindings, freeing those drivers. |
| auto remaining = std::make_shared<size_t>(self->devices_.size()); |
| |
| for (auto& d : self->devices_) { |
| zxlogf(INFO, "%s with %lu devices; shutting one down", __func__, *remaining); |
| d->ShutdownAsync([remaining, self]() { |
| ZX_ASSERT(*remaining > 0); |
| // After all devices are gone we can remove the control device itself. |
| if (--(*remaining) == 0) { |
| zxlogf(INFO, "DdkUnbind(lambda): after shutting down devices; unbinding self"); |
| device_unbind_reply(self->dev_node_); |
| } |
| }); |
| } |
| self->devices_.clear(); |
| } |
| |
| // static |
| void VirtualAudioControlImpl::DdkRelease(void* ctx) { |
| ZX_ASSERT(ctx); |
| |
| // Always called after DdkUnbind. |
| // By now, all our lists should be empty and we can destroy the ctx. |
| std::unique_ptr<VirtualAudioControlImpl> control_ptr(static_cast<VirtualAudioControlImpl*>(ctx)); |
| ZX_ASSERT(control_ptr->devices_.empty()); |
| } |
| |
| // static |
| void VirtualAudioControlImpl::DdkMessage(void* ctx, fidl_incoming_msg_t msg, |
| device_fidl_txn_t txn) { |
| VirtualAudioControlImpl* self = static_cast<VirtualAudioControlImpl*>(ctx); |
| fidl::WireDispatch<fuchsia_virtualaudio::Control>( |
| self, fidl::IncomingHeaderAndMessage::FromEncodedCMessage(msg), |
| ddk::FromDeviceFIDLTransaction(txn)); |
| } |
| |
| void VirtualAudioControlImpl::GetDefaultConfiguration( |
| GetDefaultConfigurationRequestView request, GetDefaultConfigurationCompleter::Sync& completer) { |
| fidl::Arena arena; |
| switch (request->type) { |
| case fuchsia_virtualaudio::wire::DeviceType::kComposite: |
| completer.ReplySuccess(fidl::ToWire(arena, VirtualAudioComposite::GetDefaultConfig())); |
| break; |
| case fuchsia_virtualaudio::wire::DeviceType::kDai: |
| completer.ReplySuccess( |
| fidl::ToWire(arena, VirtualAudioDai::GetDefaultConfig(request->direction.is_input()))); |
| break; |
| case fuchsia_virtualaudio::wire::DeviceType::kStreamConfig: |
| completer.ReplySuccess( |
| fidl::ToWire(arena, VirtualAudioStream::GetDefaultConfig(request->direction.is_input()))); |
| break; |
| case fuchsia_virtualaudio::wire::DeviceType::kCodec: |
| completer.ReplySuccess(fidl::ToWire( |
| arena, VirtualAudioCodec::GetDefaultConfig( |
| (request->direction.has_is_input() |
| ? static_cast<std::optional<bool>>(request->direction.is_input()) |
| : std::nullopt)))); |
| break; |
| default: |
| ZX_ASSERT_MSG(0, "Unknown device type"); |
| } |
| } |
| |
| void VirtualAudioControlImpl::AddDevice(AddDeviceRequestView request, |
| AddDeviceCompleter::Sync& completer) { |
| auto config = fidl::ToNatural(request->config); |
| ZX_ASSERT(config.device_specific().has_value()); |
| auto result = VirtualAudioDeviceImpl::Create(std::move(config), std::move(request->server), |
| dev_node_, dispatcher_); |
| if (!result.is_ok()) { |
| zxlogf(ERROR, "Device creation failed with status %d", |
| fidl::ToUnderlying(result.error_value())); |
| completer.ReplyError(result.error_value()); |
| return; |
| } |
| devices_.insert(result.value()); |
| completer.ReplySuccess(); |
| } |
| |
| void VirtualAudioControlImpl::GetNumDevices(GetNumDevicesCompleter::Sync& completer) { |
| uint32_t num_inputs = 0; |
| uint32_t num_outputs = 0; |
| uint32_t num_unspecified_direction = 0; |
| for (auto& d : devices_) { |
| if (!d->is_bound()) { |
| devices_.erase(d); |
| continue; |
| } |
| if (d->is_input().has_value()) { |
| if (d->is_input().value()) { |
| num_inputs++; |
| } else { |
| num_outputs++; |
| } |
| } else { |
| num_unspecified_direction++; |
| } |
| } |
| completer.Reply(num_inputs, num_outputs, num_unspecified_direction); |
| } |
| |
| void VirtualAudioControlImpl::RemoveAll(RemoveAllCompleter::Sync& completer) { |
| if (devices_.empty()) { |
| completer.Reply(); |
| return; |
| } |
| |
| // This callback waits until all devices have shut down. We wrap the async completer in a |
| // shared_ptr so the callback can be copied into each ShutdownAsync call. |
| struct ShutdownState { |
| explicit ShutdownState(RemoveAllCompleter::Sync& sync) : completer(sync.ToAsync()) {} |
| RemoveAllCompleter::Async completer; |
| size_t remaining; |
| }; |
| auto state = std::make_shared<ShutdownState>(completer); |
| state->remaining = devices_.size(); |
| |
| for (auto& d : devices_) { |
| d->ShutdownAsync([state]() { |
| ZX_ASSERT(state->remaining > 0); |
| // After all devices are gone, notify the completer. |
| if ((--state->remaining) == 0) { |
| state->completer.Reply(); |
| } |
| }); |
| } |
| devices_.clear(); |
| } |
| |
| } // namespace virtual_audio |