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