blob: a6cb6740a4f71a22b998271691ed17230819de3e [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 "garnet/drivers/audio/virtual_audio/virtual_audio_control_impl.h"
#include <ddk/debug.h>
#include <lib/async/cpp/task.h>
#include "garnet/drivers/audio/virtual_audio/virtual_audio_device_impl.h"
#include "garnet/drivers/audio/virtual_audio/virtual_audio_stream.h"
namespace virtual_audio {
// static
//
// Unbind any published children (which will remove them), shut down any async
// loop, ensure nothing is in flight, then remove ourselves from the dev tree.
//
// Unbind proceeds "down" from parent to child, while Release proceeds "up"
// (called for parent once all children have been released).
void VirtualAudioControlImpl::DdkUnbind(void* ctx) {
ZX_DEBUG_ASSERT(ctx != nullptr);
auto self = static_cast<VirtualAudioControlImpl*>(ctx);
// Close any remaining control (or stream) bindings, freeing those drivers.
self->ReleaseBindings();
// Now remove the control device itself (this later calls our DdkRelease).
device_remove(self->dev_node());
}
// static
//
// Always called after DdkUnbind, which should guarantee that lists are emptied.
// Any last cleanup or logical consistency checks would be done here. By the
// time this is called, all child devices have already been released.
void VirtualAudioControlImpl::DdkRelease(void* ctx) {
ZX_DEBUG_ASSERT(ctx != nullptr);
// DevMgr has returned ownership of whatever we provided as driver ctx (our
// VirtualAudioControlImpl). When this functions returns, this unique_ptr will
// go out of scope, triggering ~VirtualAudioControlImpl.
std::unique_ptr<VirtualAudioControlImpl> control_ptr(
static_cast<VirtualAudioControlImpl*>(ctx));
// By now, all our lists should be empty.
ZX_DEBUG_ASSERT(control_ptr->bindings_.size() == 0);
ZX_DEBUG_ASSERT(control_ptr->input_bindings_.size() == 0);
ZX_DEBUG_ASSERT(control_ptr->output_bindings_.size() == 0);
}
// static
//
zx_status_t VirtualAudioControlImpl::DdkMessage(void* ctx, fidl_msg_t* msg,
fidl_txn_t* txn) {
ZX_DEBUG_ASSERT(ctx != nullptr);
return fuchsia_virtualaudio_Forwarder_dispatch(ctx, txn, msg, &fidl_ops_);
}
// static
//
fuchsia_virtualaudio_Forwarder_ops_t VirtualAudioControlImpl::fidl_ops_ = {
.SendControl =
[](void* ctx, zx_handle_t control_request) {
ZX_DEBUG_ASSERT(ctx != nullptr);
return static_cast<VirtualAudioControlImpl*>(ctx)->SendControl(
zx::channel(control_request));
},
.SendInput =
[](void* ctx, zx_handle_t input_request) {
ZX_DEBUG_ASSERT(ctx != nullptr);
return static_cast<VirtualAudioControlImpl*>(ctx)->SendInput(
zx::channel(input_request));
},
.SendOutput =
[](void* ctx, zx_handle_t output_request) {
ZX_DEBUG_ASSERT(ctx != nullptr);
return static_cast<VirtualAudioControlImpl*>(ctx)->SendOutput(
zx::channel(output_request));
},
};
// A client connected to fuchsia.virtualaudio.Control hosted by the virtual
// audio service, which is forwarding the server-side binding to us.
zx_status_t VirtualAudioControlImpl::SendControl(
zx::channel control_request_channel) {
if (!control_request_channel.is_valid()) {
zxlogf(TRACE, "%s: channel from request handle is invalid\n", __func__);
return ZX_ERR_INVALID_ARGS;
}
// VirtualAudioControlImpl is a singleton so just save the binding in a list.
// Note, using the default dispatcher means that we will be running on the
// same that drives all of our peer devices in the /dev/test device host.
// We should ensure there are no long VirtualAudioControl operations.
bindings_.AddBinding(this,
fidl::InterfaceRequest<fuchsia::virtualaudio::Control>(
std::move(control_request_channel)));
return ZX_OK;
}
// A client connected to fuchsia.virtualaudio.Input hosted by the
// virtual audio service, which is forwarding the server-side binding to us.
zx_status_t VirtualAudioControlImpl::SendInput(
zx::channel input_request_channel) {
if (!input_request_channel.is_valid()) {
zxlogf(TRACE, "%s: channel from request handle is invalid\n", __func__);
return ZX_ERR_INVALID_ARGS;
}
// Create an VirtualAudioDeviceImpl for this binding; save it in our list.
// Note, using the default dispatcher means that we will be running on the
// same that drives all of our peer devices in the /dev/test device host.
// We should be mindful of this if doing long VirtualAudioInput operations.
input_bindings_.AddBinding(
VirtualAudioDeviceImpl::Create(this, true),
fidl::InterfaceRequest<fuchsia::virtualaudio::Input>(
std::move(input_request_channel)));
return ZX_OK;
}
zx_status_t VirtualAudioControlImpl::SendOutput(
zx::channel output_request_channel) {
if (!output_request_channel.is_valid()) {
zxlogf(TRACE, "%s: channel from request handle is invalid\n", __func__);
return ZX_ERR_INVALID_ARGS;
}
// Create a VirtualAudioDeviceImpl for this binding; save it in our list.
// Note, using the default dispatcher means that we will be running on the
// same that drives all of our peer devices in the /dev/test device host.
// We should be mindful of this if doing long VirtualAudioOutput operations.
output_bindings_.AddBinding(
VirtualAudioDeviceImpl::Create(this, false),
fidl::InterfaceRequest<fuchsia::virtualaudio::Output>(
std::move(output_request_channel)));
return ZX_OK;
}
// Reset any remaining bindings of Controls, Inputs and Outputs.
// This is called during Unbind, at which time child drivers should be gone (and
// input_bindings_ and output_bindings_ empty).
void VirtualAudioControlImpl::ReleaseBindings() {
bindings_.CloseAll();
input_bindings_.CloseAll();
output_bindings_.CloseAll();
zxlogf(TRACE,
"%s for %p: now we have %lu/%lu/%lu control/input/output bindings\n",
__func__, this, bindings_.size(), input_bindings_.size(),
output_bindings_.size());
}
void VirtualAudioControlImpl::PostToDispatcher(
fit::closure task_to_post) const {
async::PostTask(dispatcher(), std::move(task_to_post));
}
// Allow subsequent new stream creation -- but do not automatically reactivate
// any streams that may have been deactivated (removed) by the previous Disable.
// Upon construction, the default state of this object is Enabled. The (empty)
// callback is used to synchronize with other in-flight asynchronous operations.
void VirtualAudioControlImpl::Enable(EnableCallback enable_callback) {
enabled_ = true;
enable_callback();
}
// Deactivate active streams and prevent subsequent new stream creation. Audio
// devices vanish from the dev tree (VirtualAudioStream objects are freed), but
// Input and Output channels remain open and can be reconfigured. Once Enable is
// called; they can be re-added without losing configuration state. The (empty)
// callback is used to synchronize with other in-flight asynchronous operations.
void VirtualAudioControlImpl::Disable(DisableCallback disable_callback) {
if (enabled_) {
for (auto& binding : input_bindings_.bindings()) {
binding->impl()->RemoveStream();
}
for (auto& binding : output_bindings_.bindings()) {
binding->impl()->RemoveStream();
}
enabled_ = false;
}
disable_callback();
}
} // namespace virtual_audio