blob: 85f5f7b78b3fd79883cce9453491dc388bc8e448 [file] [log] [blame]
// 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