blob: a427cb8017b6affd986a438b9c2a3a09353a03c4 [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/alloc_checker.h>
#include <utility>
#include "debug-logging.h"
#include "usb-audio-path.h"
namespace audio {
namespace usb {
fbl::unique_ptr<AudioPath> AudioPath::Create(uint32_t unit_count) {
fbl::AllocChecker ac;
fbl::unique_ptr<fbl::RefPtr<AudioUnit>[]> units(new (&ac) fbl::RefPtr<AudioUnit>[unit_count]);
if (!ac.check()) {
GLOBAL_LOG(ERROR, "Failed to allocate %u units for AudioPath!", unit_count);
return nullptr;
}
fbl::unique_ptr<AudioPath> ret(new (&ac) AudioPath(std::move(units), unit_count));
if (!ac.check()) {
GLOBAL_LOG(ERROR, "Failed to allocate AudioPath!");
return nullptr;
}
return ret;
}
void AudioPath::AddUnit(uint32_t ndx, fbl::RefPtr<AudioUnit> unit) {
ZX_DEBUG_ASSERT(ndx < unit_count_);
ZX_DEBUG_ASSERT(unit != nullptr);
units_[ndx] = std::move(unit);
}
zx_status_t AudioPath::Setup(const usb_protocol_t& proto) {
// If setup is being called, we should have allocated a units_ array, and it
// must be a minimum of 2 units long (the input and the output terminal).
// All of its members must be non-null. The first element in the array must
// be an output terminal while the last element in the array must be an
// input terminal. Check all of this before proceeding.
if ((units_ == nullptr) || (unit_count_ < 2)) {
GLOBAL_LOG(ERROR, "Bad units array during %s (ptr %p, count %u)\n",
__PRETTY_FUNCTION__, units_.get(), unit_count_);
return ZX_ERR_INTERNAL;
}
for (uint32_t i = 0; i < unit_count_; ++i) {
if (units_[i] == nullptr) {
GLOBAL_LOG(ERROR, "Empty unit slot %s (ndx %u)\n", __PRETTY_FUNCTION__, i);
return ZX_ERR_INTERNAL;
}
}
const auto& first_unit = *units_[0];
if (first_unit.type() != AudioUnit::Type::OutputTerminal) {
GLOBAL_LOG(ERROR,
"First element of audio path must be an OutputTerminal, "
"but a unit of type \"%s\" was discovered instead!\n",
first_unit.type_name());
return ZX_ERR_INTERNAL;
}
const auto& last_unit = *units_[unit_count_ - 1];
if (last_unit.type() != AudioUnit::Type::InputTerminal) {
GLOBAL_LOG(ERROR,
"First element of audio path must be an InputTerminal, "
"but a unit of type \"%s\" was discovered instead!\n",
last_unit.type_name());
return ZX_ERR_INTERNAL;
}
// Locate and stash a pointer to the terminal which serves as the bridge to
// the host. If this is the output terminal, then this path is an audio
// input to the system, and vice-versa. There should be exactly one stream
// terminal in our path.
//
// If the stream terminal is an output terminal, then this is an audio input
// path. Otherwise it is an audio output path.
const auto& out_term = static_cast<const OutputTerminal&>(first_unit);
const auto& in_term = static_cast<const InputTerminal&>(last_unit);
if (out_term.is_stream_terminal() == in_term.is_stream_terminal()) {
GLOBAL_LOG(ERROR, "%s stream terminals found in audio path!\n",
out_term.is_stream_terminal() ? "Multiple" : "No");
return ZX_ERR_INTERNAL;
}
if (out_term.is_stream_terminal()) {
stream_terminal_.reset(&out_term);
direction_ = Direction::Input;
} else {
stream_terminal_.reset(&in_term);
direction_ = Direction::Output;
}
// Now walk the array of AudioUnits configuring our path. In particular...
//
// ++ If we find SelectorUnits, make sure that they are configured to select
// the input which comes immediately before them.
// ++ If we find MixerUnits, make sure that they are configured to pass
// through audio from the input which comes immediately before them.
// ++ If we find FeatureUnits, make sure to stash a pointer to the first one
// we find. This is where our volume control knob will be located (if
// any).
//
// If any mixers or selectors we encounter are already in use, abort. We
// don't know how to properly configure a device where multiple paths
// exist which share mixer/selector units.
for (uint32_t i = 1; i < unit_count_ - 1; ++i) {
const auto& unit = units_[i];
// Skip anything which is not a selector, mixer, or feature unit.
switch (unit->type()) {
case AudioUnit::Type::SelectorUnit:
case AudioUnit::Type::MixerUnit:
case AudioUnit::Type::FeatureUnit:
break;
default:
continue;
}
// Make sure the unit is not already in use. We don't know how to share
// any of these units with other paths.
if (unit->in_use()) {
GLOBAL_LOG(ERROR,
"AudioPath with in/out term ids = (%u/%u) encountered a %s "
"(id %u) which is already in use by another path.\n",
in_term.id(), out_term.id(), unit->type_name(), unit->id());
return ZX_ERR_NOT_SUPPORTED;
}
if (unit->type() == AudioUnit::Type::SelectorUnit) {
// Make certain that the upstream unit for this audio path is the
// unit which has been selected.
auto& selector_unit = static_cast<SelectorUnit&>(*unit);
uint8_t upstream_id = static_cast<uint8_t>(units_[i + 1]->id());
zx_status_t status = selector_unit.Select(proto, upstream_id);
if (status != ZX_OK) {
GLOBAL_LOG(ERROR,
"AudioPath with in/out term ids = (%u/%u) failed to set "
"selector id %u to source from upstream unit id %u (status %d)\n",
in_term.id(), out_term.id(), unit->id(), upstream_id, status);
return status;
}
} else
if (unit->type() == AudioUnit::Type::MixerUnit) {
// TODO(johngro): (configure the mixer here)
} else
if (unit->type() == AudioUnit::Type::FeatureUnit) {
// Right now, we don't know how to deal with a path which has
// multiple volume knobs.
if (feature_unit_ != nullptr) {
GLOBAL_LOG(ERROR,
"AudioPath with in/out term ids = (%u/%u) encountered "
"a multiple feature units in the path. We encountered "
"id %u, but already have id %u cached.\n",
in_term.id(), out_term.id(), unit->id(), feature_unit_->id());
return ZX_ERR_NOT_SUPPORTED;
}
feature_unit_ = fbl::RefPtr<FeatureUnit>::Downcast(unit);
}
}
// Things look good. Flag all of the units in our path as being in use now.
for (uint32_t i = 1; i < unit_count_ - 1; ++i) {
units_[i]->set_in_use();
}
// If this path has a feature unit, then default the volume controls to 0dB
// gain and unmuted.
if (feature_unit_ != nullptr) {
feature_unit_->SetMute(proto, false);
feature_unit_->SetVol(proto, 0.0f);
feature_unit_->SetAgc(proto, false);
}
return ZX_OK;
}
} // namespace usb
} // namespace audio