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