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/auto_call.h>
6
7 #include <utility>
8
9 #include "debug-logging.h"
10 #include "usb-audio-control-interface.h"
11 #include "usb-audio-device.h"
12 #include "usb-audio-units.h"
13
14 namespace audio {
15 namespace usb {
16
17 // We use our parent's log prefix
log_prefix() const18 const char* UsbAudioControlInterface::log_prefix() const {
19 return parent_.log_prefix();
20 }
21
UsbAudioControlInterface(UsbAudioDevice * parent)22 UsbAudioControlInterface::UsbAudioControlInterface(UsbAudioDevice* parent)
23 : parent_(*parent) {
24 ZX_DEBUG_ASSERT(parent != nullptr);
25 }
26
~UsbAudioControlInterface()27 UsbAudioControlInterface::~UsbAudioControlInterface() {
28 }
29
Create(UsbAudioDevice * parent)30 fbl::unique_ptr<UsbAudioControlInterface> UsbAudioControlInterface::Create(UsbAudioDevice* parent) {
31 if (parent == nullptr) {
32 GLOBAL_LOG(ERROR, "null parent passed to %s\n", __PRETTY_FUNCTION__);
33 return nullptr;
34 }
35
36 fbl::AllocChecker ac;
37 fbl::unique_ptr<UsbAudioControlInterface> ret(new (&ac) UsbAudioControlInterface(parent));
38 if (ac.check()) {
39 return ret;
40 }
41
42 return nullptr;
43 }
44
Initialize(DescriptorListMemory::Iterator * iter)45 zx_status_t UsbAudioControlInterface::Initialize(DescriptorListMemory::Iterator* iter) {
46 ZX_DEBUG_ASSERT(iter != nullptr);
47 ZX_DEBUG_ASSERT(iter->desc_list() != nullptr);
48
49 // It is an error to attempt to initialize this class twice.
50 if (desc_list_ != nullptr) {
51 LOG(ERROR, "Attempted to initialize control interface twice\n");
52 return ZX_ERR_BAD_STATE;
53 }
54
55 desc_list_ = iter->desc_list();
56 interface_hdr_ = iter->hdr_as<usb_interface_descriptor_t>();
57
58 // These should already have been checked before Initialize was called.
59 ZX_DEBUG_ASSERT(interface_hdr_ != nullptr);
60 ZX_DEBUG_ASSERT(interface_hdr_->bInterfaceClass == USB_CLASS_AUDIO);
61 ZX_DEBUG_ASSERT(interface_hdr_->bInterfaceSubClass == USB_SUBCLASS_AUDIO_CONTROL);
62
63 // Parse all of the descriptors which belong to this audio control
64 // interface. As soon as we find something which does not belong to the
65 // interface, break out of the parse loop, leaving the iterator pointing at
66 // the next descriptor (if any). Then try to make sense of the descriptors
67 // we did find.
68 while (iter->Next()) {
69 {
70 auto hdr = iter->hdr();
71 if (!hdr || (hdr->bDescriptorType != USB_AUDIO_CS_INTERFACE)) {
72 break;
73 }
74 }
75
76 auto hdr = iter->hdr_as<usb_audio_desc_header>();
77 if (!hdr) {
78 LOG(WARN, "Badly formed audio control descriptor header @ offset %zu\n",
79 iter->offset());
80 continue;
81 }
82
83 if (hdr->bDescriptorSubtype == USB_AUDIO_AC_HEADER) {
84 if (class_hdr_ == nullptr) {
85 class_hdr_ = iter->hdr_as<usb_audio_ac_header_desc>();
86 if (class_hdr_ == nullptr) {
87 LOG(WARN, "Badly formed audio control class specific header @ offset %zu\n",
88 iter->offset());
89 }
90 } else {
91 LOG(WARN, "Duplicate audio control class specific header @ offset %zu\n",
92 iter->offset());
93 }
94
95 continue;
96 }
97
98 auto unit = AudioUnit::Create(*iter, interface_hdr_->bInterfaceNumber);
99 if (unit == nullptr) {
100 LOG(WARN, "Failed to create audio Terminal/Unit (type %u) @ offset %zu\n",
101 hdr->bDescriptorSubtype, iter->offset());
102 } else {
103 // Add our new unit to the collection we are building up. There
104 // should be no collision; all unit IDs are supposed to be
105 // unique within a given control interface. If we encounter a
106 // collision, log a warning and move on (eg, just try to do the
107 // best we can).
108 uint32_t id = unit->id();
109 if (!units_.insert_or_find(std::move(unit))) {
110 LOG(WARN, "Collision when attempting to add unit id %u; skipping this unit\n", id);
111 }
112 }
113 }
114
115 // Next, give each Unit/Terminal a chance to probe any state they will need
116 // to operate which will require performing actual USB transactions.
117 for (auto& unit : units_) {
118 zx_status_t res = unit.Probe(parent_.usb_proto());
119 if (res != ZX_OK) {
120 LOG(ERROR, "Failed to probe %s (id %u) during initialization!\n",
121 unit.type_name(), unit.id());
122 return res;
123 }
124 }
125
126 // OK - now that we have our set of descriptors, attempt to find the audio
127 // paths through this graph that we intend to publish. The algorithm used
128 // here is not particularly sophisticated. Basically, we are going to start
129 // at each output terminal in the set and attempt to trace our way back to
130 // an input terminal that forms a path from host to pin (or vice versa).
131 // Pin-to-pin or host-to-host paths are ignored, although if we someday want
132 // to recognize sidetone paths, we should probably pay some attention to the
133 // pin to pin paths.
134 //
135 // We explore the graph using a depth first recursive search using a state
136 // bit stored in the terminal/unit classes to avoid cycles. Since the unit
137 // IDs used by terminal/units are 8-bits, we can only recurse an absolute
138 // maximum of 256 times, which should be safe from stack overflow for the
139 // class of hardware this driver is intended for.
140 //
141 // Once any valid path from output to input has been found, we stop the
142 // search, even if there may be another path to consider. For most simple
143 // devices out there, this should be sufficient, however as time goes on we
144 // may discover more complicated devices that will require us to revisit
145 // this algorithm and make it a bit smarter. Failing that, a custom driver
146 // would be needed for these more complicated hypothetical devices.
147 for (auto iter = units_.begin(); iter.IsValid(); ++iter) {
148 // We are only interested in output terminals. Skip all of the rest.
149 if (iter->type() != AudioUnit::Type::OutputTerminal) {
150 continue;
151 }
152
153 // Do the search. If it succeed, we will get a reference to an
154 // AudioPath object back.
155 LOG(TRACE, "Beginning trace for Output Terminal id %u\n", iter->id());
156 auto path = TracePath(static_cast<const OutputTerminal&>(*iter), iter);
157 if (path != nullptr) {
158 LOG(TRACE, "Found valid path!\n");
159
160 zx_status_t status = path->Setup(parent_.usb_proto());
161 if (status != ZX_OK) {
162 LOG(TRACE, "Failed to setup path! (status %d)\n", status);
163 } else {
164 paths_.push_back(std::move(path));
165 }
166 } else {
167 LOG(TRACE, "No valid path found\n");
168 }
169 }
170
171 // Now that we have found all of our valid paths, go over our list of
172 // discovered units and mute any volume controls in feature units which are
173 // not currently being used by any audio paths.
174 for (auto& unit : units_) {
175 if ((unit.type() == AudioUnit::Type::FeatureUnit) && !unit.in_use()) {
176 auto& feature_unit = static_cast<FeatureUnit&>(unit);
177 feature_unit.SetMute(parent_.usb_proto(), true);
178 }
179
180 // TODO(johngro) : If we encounter un-used mixer nodes, we should set
181 // all of their inputs to maximum dB down in an attempt to effectively
182 // mute them.
183 }
184
185 return ZX_OK;
186 }
187
TracePath(const OutputTerminal & out_term,const UnitMap::iterator & current,uint32_t level)188 fbl::unique_ptr<AudioPath> UsbAudioControlInterface::TracePath(const OutputTerminal& out_term,
189 const UnitMap::iterator& current,
190 uint32_t level) {
191 // Flag the current node as having been visited and setup a cleanup task to
192 // clear the flag as we unwind.
193 auto cleanup = fbl::MakeAutoCall([¤t]() { current->visited() = false; });
194 ZX_DEBUG_ASSERT(!current->visited());
195 current->visited() = true;
196 LOG(TRACE, "Visiting unit id %u, type %s\n", current->id(), current->type_name());
197
198 // If we have reached an input terminal, then check to see if it is of the
199 // proper type. If so, create a new path object and start to unwind the
200 // stack, stashing the references to the unit which define the path in the
201 // process. Otherwise, this is a dead end. Just return null and keep
202 // looking.
203 if (current->type() == AudioUnit::Type::InputTerminal) {
204 // We have found a valid path of one of these terminals is a USB stream
205 // terminal, while the other terminal is anything which is not a USB
206 // terminal (stream or otherwise).
207 const auto& in_term = static_cast<const InputTerminal&>(*current);
208 if ((out_term.is_stream_terminal() && !in_term.is_usb_terminal()) ||
209 (!out_term.is_stream_terminal() && in_term.is_usb_terminal())) {
210 auto ret = AudioPath::Create(level + 1);
211 if (ret != nullptr) {
212 ret->AddUnit(level, current.CopyPointer());
213 }
214 return ret;
215 }
216
217 LOG(TRACE, "Skipping incompatible input terminal (in type 0x%04hx, out type 0x%04hx)\n",
218 in_term.terminal_type(), out_term.terminal_type());
219 return nullptr;
220 }
221
222 for (uint32_t i = 0; i < current->source_count(); ++i) {
223 uint32_t source_id = current->source_id(i);
224 auto next = units_.find(source_id);
225 if (!next.IsValid()) {
226 LOG(WARN, "Can't find upstream unit id %u while tracing from unit id %u.\n",
227 source_id, current->id());
228 continue;
229 }
230
231 if (next->visited()) {
232 LOG(TRACE, "Skipping already visited unit id %u while tracing from unit id %u\n",
233 source_id, current->id());
234 continue;
235 }
236
237 // Recurse down this path. If it finds a valid path, stash ourselves in
238 // the path and unwind.
239 auto path = TracePath(out_term, next, level + 1);
240 if (path != nullptr) {
241 path->AddUnit(level, current.CopyPointer());
242 return path;
243 }
244 }
245
246 return nullptr;
247 }
248
249 } // namespace usb
250 } // namespace audio
251