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