blob: 759ff1110dd50ae84b6a2339faef7dd20aa34b92 [file] [log] [blame]
// Copyright 2018 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 <fbl/auto_call.h>
#include <utility>
#include "debug-logging.h"
#include "usb-audio-control-interface.h"
#include "usb-audio-device.h"
#include "usb-audio-units.h"
namespace audio {
namespace usb {
// We use our parent's log prefix
const char* UsbAudioControlInterface::log_prefix() const {
return parent_.log_prefix();
}
UsbAudioControlInterface::UsbAudioControlInterface(UsbAudioDevice* parent)
: parent_(*parent) {
ZX_DEBUG_ASSERT(parent != nullptr);
}
UsbAudioControlInterface::~UsbAudioControlInterface() {
}
fbl::unique_ptr<UsbAudioControlInterface> UsbAudioControlInterface::Create(UsbAudioDevice* parent) {
if (parent == nullptr) {
GLOBAL_LOG(ERROR, "null parent passed to %s\n", __PRETTY_FUNCTION__);
return nullptr;
}
fbl::AllocChecker ac;
fbl::unique_ptr<UsbAudioControlInterface> ret(new (&ac) UsbAudioControlInterface(parent));
if (ac.check()) {
return ret;
}
return nullptr;
}
zx_status_t UsbAudioControlInterface::Initialize(DescriptorListMemory::Iterator* iter) {
ZX_DEBUG_ASSERT(iter != nullptr);
ZX_DEBUG_ASSERT(iter->desc_list() != nullptr);
// It is an error to attempt to initialize this class twice.
if (desc_list_ != nullptr) {
LOG(ERROR, "Attempted to initialize control interface twice\n");
return ZX_ERR_BAD_STATE;
}
desc_list_ = iter->desc_list();
interface_hdr_ = iter->hdr_as<usb_interface_descriptor_t>();
// These should already have been checked before Initialize was called.
ZX_DEBUG_ASSERT(interface_hdr_ != nullptr);
ZX_DEBUG_ASSERT(interface_hdr_->bInterfaceClass == USB_CLASS_AUDIO);
ZX_DEBUG_ASSERT(interface_hdr_->bInterfaceSubClass == USB_SUBCLASS_AUDIO_CONTROL);
// Parse all of the descriptors which belong to this audio control
// interface. As soon as we find something which does not belong to the
// interface, break out of the parse loop, leaving the iterator pointing at
// the next descriptor (if any). Then try to make sense of the descriptors
// we did find.
while (iter->Next()) {
{
auto hdr = iter->hdr();
if (!hdr || (hdr->bDescriptorType != USB_AUDIO_CS_INTERFACE)) {
break;
}
}
auto hdr = iter->hdr_as<usb_audio_desc_header>();
if (!hdr) {
LOG(WARN, "Badly formed audio control descriptor header @ offset %zu\n",
iter->offset());
continue;
}
if (hdr->bDescriptorSubtype == USB_AUDIO_AC_HEADER) {
if (class_hdr_ == nullptr) {
class_hdr_ = iter->hdr_as<usb_audio_ac_header_desc>();
if (class_hdr_ == nullptr) {
LOG(WARN, "Badly formed audio control class specific header @ offset %zu\n",
iter->offset());
}
} else {
LOG(WARN, "Duplicate audio control class specific header @ offset %zu\n",
iter->offset());
}
continue;
}
auto unit = AudioUnit::Create(*iter, interface_hdr_->bInterfaceNumber);
if (unit == nullptr) {
LOG(WARN, "Failed to create audio Terminal/Unit (type %u) @ offset %zu\n",
hdr->bDescriptorSubtype, iter->offset());
} else {
// Add our new unit to the collection we are building up. There
// should be no collision; all unit IDs are supposed to be
// unique within a given control interface. If we encounter a
// collision, log a warning and move on (eg, just try to do the
// best we can).
uint32_t id = unit->id();
if (!units_.insert_or_find(std::move(unit))) {
LOG(WARN, "Collision when attempting to add unit id %u; skipping this unit\n", id);
}
}
}
// Next, give each Unit/Terminal a chance to probe any state they will need
// to operate which will require performing actual USB transactions.
for (auto& unit : units_) {
zx_status_t res = unit.Probe(parent_.usb_proto());
if (res != ZX_OK) {
LOG(ERROR, "Failed to probe %s (id %u) during initialization!\n",
unit.type_name(), unit.id());
return res;
}
}
// OK - now that we have our set of descriptors, attempt to find the audio
// paths through this graph that we intend to publish. The algorithm used
// here is not particularly sophisticated. Basically, we are going to start
// at each output terminal in the set and attempt to trace our way back to
// an input terminal that forms a path from host to pin (or vice versa).
// Pin-to-pin or host-to-host paths are ignored, although if we someday want
// to recognize sidetone paths, we should probably pay some attention to the
// pin to pin paths.
//
// We explore the graph using a depth first recursive search using a state
// bit stored in the terminal/unit classes to avoid cycles. Since the unit
// IDs used by terminal/units are 8-bits, we can only recurse an absolute
// maximum of 256 times, which should be safe from stack overflow for the
// class of hardware this driver is intended for.
//
// Once any valid path from output to input has been found, we stop the
// search, even if there may be another path to consider. For most simple
// devices out there, this should be sufficient, however as time goes on we
// may discover more complicated devices that will require us to revisit
// this algorithm and make it a bit smarter. Failing that, a custom driver
// would be needed for these more complicated hypothetical devices.
for (auto iter = units_.begin(); iter.IsValid(); ++iter) {
// We are only interested in output terminals. Skip all of the rest.
if (iter->type() != AudioUnit::Type::OutputTerminal) {
continue;
}
// Do the search. If it succeed, we will get a reference to an
// AudioPath object back.
LOG(TRACE, "Beginning trace for Output Terminal id %u\n", iter->id());
auto path = TracePath(static_cast<const OutputTerminal&>(*iter), iter);
if (path != nullptr) {
LOG(TRACE, "Found valid path!\n");
zx_status_t status = path->Setup(parent_.usb_proto());
if (status != ZX_OK) {
LOG(TRACE, "Failed to setup path! (status %d)\n", status);
} else {
paths_.push_back(std::move(path));
}
} else {
LOG(TRACE, "No valid path found\n");
}
}
// Now that we have found all of our valid paths, go over our list of
// discovered units and mute any volume controls in feature units which are
// not currently being used by any audio paths.
for (auto& unit : units_) {
if ((unit.type() == AudioUnit::Type::FeatureUnit) && !unit.in_use()) {
auto& feature_unit = static_cast<FeatureUnit&>(unit);
feature_unit.SetMute(parent_.usb_proto(), true);
}
// TODO(johngro) : If we encounter un-used mixer nodes, we should set
// all of their inputs to maximum dB down in an attempt to effectively
// mute them.
}
return ZX_OK;
}
fbl::unique_ptr<AudioPath> UsbAudioControlInterface::TracePath(const OutputTerminal& out_term,
const UnitMap::iterator& current,
uint32_t level) {
// Flag the current node as having been visited and setup a cleanup task to
// clear the flag as we unwind.
auto cleanup = fbl::MakeAutoCall([&current]() { current->visited() = false; });
ZX_DEBUG_ASSERT(!current->visited());
current->visited() = true;
LOG(TRACE, "Visiting unit id %u, type %s\n", current->id(), current->type_name());
// If we have reached an input terminal, then check to see if it is of the
// proper type. If so, create a new path object and start to unwind the
// stack, stashing the references to the unit which define the path in the
// process. Otherwise, this is a dead end. Just return null and keep
// looking.
if (current->type() == AudioUnit::Type::InputTerminal) {
// We have found a valid path of one of these terminals is a USB stream
// terminal, while the other terminal is anything which is not a USB
// terminal (stream or otherwise).
const auto& in_term = static_cast<const InputTerminal&>(*current);
if ((out_term.is_stream_terminal() && !in_term.is_usb_terminal()) ||
(!out_term.is_stream_terminal() && in_term.is_usb_terminal())) {
auto ret = AudioPath::Create(level + 1);
if (ret != nullptr) {
ret->AddUnit(level, current.CopyPointer());
}
return ret;
}
LOG(TRACE, "Skipping incompatible input terminal (in type 0x%04hx, out type 0x%04hx)\n",
in_term.terminal_type(), out_term.terminal_type());
return nullptr;
}
for (uint32_t i = 0; i < current->source_count(); ++i) {
uint32_t source_id = current->source_id(i);
auto next = units_.find(source_id);
if (!next.IsValid()) {
LOG(WARN, "Can't find upstream unit id %u while tracing from unit id %u.\n",
source_id, current->id());
continue;
}
if (next->visited()) {
LOG(TRACE, "Skipping already visited unit id %u while tracing from unit id %u\n",
source_id, current->id());
continue;
}
// Recurse down this path. If it finds a valid path, stash ourselves in
// the path and unwind.
auto path = TracePath(out_term, next, level + 1);
if (path != nullptr) {
path->AddUnit(level, current.CopyPointer());
return path;
}
}
return nullptr;
}
} // namespace usb
} // namespace audio