1// Copyright 2018 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <fbl/alloc_checker.h> 6 7#include "debug-logging.h" 8#include "usb-audio-path.h" 9 10namespace audio { 11namespace usb { 12 13fbl::unique_ptr<AudioPath> AudioPath::Create(uint32_t unit_count) { 14 fbl::AllocChecker ac; 15 16 fbl::unique_ptr<fbl::RefPtr<AudioUnit>[]> units(new (&ac) fbl::RefPtr<AudioUnit>[unit_count]); 17 if (!ac.check()) { 18 GLOBAL_LOG(ERROR, "Failed to allocate %u units for AudioPath!", unit_count); 19 return nullptr; 20 } 21 22 fbl::unique_ptr<AudioPath> ret(new (&ac) AudioPath(fbl::move(units), unit_count)); 23 if (!ac.check()) { 24 GLOBAL_LOG(ERROR, "Failed to allocate AudioPath!"); 25 return nullptr; 26 } 27 28 return ret; 29} 30 31void AudioPath::AddUnit(uint32_t ndx, fbl::RefPtr<AudioUnit> unit) { 32 ZX_DEBUG_ASSERT(ndx < unit_count_); 33 ZX_DEBUG_ASSERT(unit != nullptr); 34 units_[ndx] = fbl::move(unit); 35} 36 37zx_status_t AudioPath::Setup(const usb_protocol_t& proto) { 38 // If setup is being called, we should have allocated a units_ array, and it 39 // must be a minimum of 2 units long (the input and the output terminal). 40 // All of its members must be non-null. The first element in the array must 41 // be an output terminal while the last element in the array must be an 42 // input terminal. Check all of this before proceeding. 43 if ((units_ == nullptr) || (unit_count_ < 2)) { 44 GLOBAL_LOG(ERROR, "Bad units array during %s (ptr %p, count %u)\n", 45 __PRETTY_FUNCTION__, units_.get(), unit_count_); 46 return ZX_ERR_INTERNAL; 47 } 48 49 for (uint32_t i = 0; i < unit_count_; ++i) { 50 if (units_[i] == nullptr) { 51 GLOBAL_LOG(ERROR, "Empty unit slot %s (ndx %u)\n", __PRETTY_FUNCTION__, i); 52 return ZX_ERR_INTERNAL; 53 } 54 } 55 56 const auto& first_unit = *units_[0]; 57 if (first_unit.type() != AudioUnit::Type::OutputTerminal) { 58 GLOBAL_LOG(ERROR, 59 "First element of audio path must be an OutputTerminal, " 60 "but a unit of type \"%s\" was discovered instead!\n", 61 first_unit.type_name()); 62 return ZX_ERR_INTERNAL; 63 } 64 65 const auto& last_unit = *units_[unit_count_ - 1]; 66 if (last_unit.type() != AudioUnit::Type::InputTerminal) { 67 GLOBAL_LOG(ERROR, 68 "First element of audio path must be an InputTerminal, " 69 "but a unit of type \"%s\" was discovered instead!\n", 70 last_unit.type_name()); 71 return ZX_ERR_INTERNAL; 72 } 73 74 // Locate and stash a pointer to the terminal which serves as the bridge to 75 // the host. If this is the output terminal, then this path is an audio 76 // input to the system, and vice-versa. There should be exactly one stream 77 // terminal in our path. 78 // 79 // If the stream terminal is an output terminal, then this is an audio input 80 // path. Otherwise it is an audio output path. 81 const auto& out_term = static_cast<const OutputTerminal&>(first_unit); 82 const auto& in_term = static_cast<const InputTerminal&>(last_unit); 83 if (out_term.is_stream_terminal() == in_term.is_stream_terminal()) { 84 GLOBAL_LOG(ERROR, "%s stream terminals found in audio path!\n", 85 out_term.is_stream_terminal() ? "Multiple" : "No"); 86 return ZX_ERR_INTERNAL; 87 } 88 89 if (out_term.is_stream_terminal()) { 90 stream_terminal_.reset(&out_term); 91 direction_ = Direction::Input; 92 } else { 93 stream_terminal_.reset(&in_term); 94 direction_ = Direction::Output; 95 } 96 97 // Now walk the array of AudioUnits configuring our path. In particular... 98 // 99 // ++ If we find SelectorUnits, make sure that they are configured to select 100 // the input which comes immediately before them. 101 // ++ If we find MixerUnits, make sure that they are configured to pass 102 // through audio from the input which comes immediately before them. 103 // ++ If we find FeatureUnits, make sure to stash a pointer to the first one 104 // we find. This is where our volume control knob will be located (if 105 // any). 106 // 107 // If any mixers or selectors we encounter are already in use, abort. We 108 // don't know how to properly configure a device where multiple paths 109 // exist which share mixer/selector units. 110 for (uint32_t i = 1; i < unit_count_ - 1; ++i) { 111 const auto& unit = units_[i]; 112 113 // Skip anything which is not a selector, mixer, or feature unit. 114 switch (unit->type()) { 115 case AudioUnit::Type::SelectorUnit: 116 case AudioUnit::Type::MixerUnit: 117 case AudioUnit::Type::FeatureUnit: 118 break; 119 default: 120 continue; 121 } 122 123 // Make sure the unit is not already in use. We don't know how to share 124 // any of these units with other paths. 125 if (unit->in_use()) { 126 GLOBAL_LOG(ERROR, 127 "AudioPath with in/out term ids = (%u/%u) encountered a %s " 128 "(id %u) which is already in use by another path.\n", 129 in_term.id(), out_term.id(), unit->type_name(), unit->id()); 130 return ZX_ERR_NOT_SUPPORTED; 131 } 132 133 if (unit->type() == AudioUnit::Type::SelectorUnit) { 134 // Make certain that the upstream unit for this audio path is the 135 // unit which has been selected. 136 auto& selector_unit = static_cast<SelectorUnit&>(*unit); 137 uint8_t upstream_id = static_cast<uint8_t>(units_[i + 1]->id()); 138 zx_status_t status = selector_unit.Select(proto, upstream_id); 139 if (status != ZX_OK) { 140 GLOBAL_LOG(ERROR, 141 "AudioPath with in/out term ids = (%u/%u) failed to set " 142 "selector id %u to source from upstream unit id %u (status %d)\n", 143 in_term.id(), out_term.id(), unit->id(), upstream_id, status); 144 return status; 145 } 146 } else 147 if (unit->type() == AudioUnit::Type::MixerUnit) { 148 // TODO(johngro): (configure the mixer here) 149 } else 150 if (unit->type() == AudioUnit::Type::FeatureUnit) { 151 // Right now, we don't know how to deal with a path which has 152 // multiple volume knobs. 153 if (feature_unit_ != nullptr) { 154 GLOBAL_LOG(ERROR, 155 "AudioPath with in/out term ids = (%u/%u) encountered " 156 "a multiple feature units in the path. We encountered " 157 "id %u, but already have id %u cached.\n", 158 in_term.id(), out_term.id(), unit->id(), feature_unit_->id()); 159 return ZX_ERR_NOT_SUPPORTED; 160 } 161 162 feature_unit_ = fbl::RefPtr<FeatureUnit>::Downcast(unit); 163 } 164 } 165 166 // Things look good. Flag all of the units in our path as being in use now. 167 for (uint32_t i = 1; i < unit_count_ - 1; ++i) { 168 units_[i]->set_in_use(); 169 } 170 171 // If this path has a feature unit, then default the volume controls to 0dB 172 // gain and unmuted. 173 if (feature_unit_ != nullptr) { 174 feature_unit_->SetMute(proto, false); 175 feature_unit_->SetVol(proto, 0.0f); 176 feature_unit_->SetAgc(proto, false); 177 } 178 179 return ZX_OK; 180} 181 182} // namespace usb 183} // namespace audio 184 185