blob: 0c1effa38098b1c50e7af882588f37a73c645eba [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#pragma once
#include <fbl/array.h>
#include <fbl/intrusive_wavl_tree.h>
#include <fbl/macros.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <zircon/hw/usb/audio.h>
#include <utility>
#include "usb-audio-descriptors.h"
// Notes: usb-audio-units.h contains a collection of definitions of classes used
// when building the graph of Terminals/Units which make up the inside of a USB
// Audio Control interface.
namespace audio {
namespace usb {
class AudioUnit : public fbl::WAVLTreeContainable<fbl::RefPtr<AudioUnit>>,
public fbl::RefCounted<AudioUnit> {
public:
static constexpr uint32_t kInvalidID = 0xFFFFFFFF;
enum class Type : uint8_t {
InputTerminal = USB_AUDIO_AC_INPUT_TERMINAL,
OutputTerminal = USB_AUDIO_AC_OUTPUT_TERMINAL,
MixerUnit = USB_AUDIO_AC_MIXER_UNIT,
SelectorUnit = USB_AUDIO_AC_SELECTOR_UNIT,
FeatureUnit = USB_AUDIO_AC_FEATURE_UNIT,
ProcessingUnit = USB_AUDIO_AC_PROCESSING_UNIT,
ExtensionUnit = USB_AUDIO_AC_EXTENSION_UNIT,
};
static fbl::RefPtr<AudioUnit> Create(const DescriptorListMemory::Iterator& iter, uint8_t iid);
Type type() const { return static_cast<Type>(desc_->bDescriptorSubtype); }
const char* type_name() const;
uint8_t iid() const { return iid_; }
uint32_t id() const { return desc_->bID; }
uint32_t GetKey() const { return id(); }
// the 16 bit index which needs to be used any time a command needs to be
// sent to this unit (the wIndex field). This is formed from the unit ID
// (high byte) and the control interface id (low byte).
uint16_t index() const { return static_cast<uint16_t>((id() << 8) | iid()); }
// Every audio unit needs to define which source(s) feed it. This
// information is contained in the unit/terminal's descriptors, but where it
// lives (and whether or not it is simply implied, such as in the case of an
// InputTerminal) depends entirely on the type of unit/terminal in question.
// Because of this, we simply model the interface using pure virtual
// methods.
virtual uint32_t source_count() const = 0;
virtual uint32_t source_id(uint32_t ndx) const = 0;
// A hook which allows certain audio units/terminals to read their
// capabilities at startup. Not all of the units need to do this, so the
// default hook implementation is a no-op.
virtual zx_status_t Probe(const usb_protocol_t& proto) { return ZX_OK; }
// A state flag used by the audio control interface class when it is
// searching the terminal/unit graph for audio paths to publish.
bool& visited() { return visited_; }
// A flag indicating whether or not there is at least one audio path in the
// system attempting to use this unit/terminal
bool in_use() const { return in_use_; }
void set_in_use() { in_use_ = true; }
protected:
friend class fbl::RefPtr<AudioUnit>;
AudioUnit(fbl::RefPtr<DescriptorListMemory> desc_list,
const usb_audio_ac_ut_desc* desc,
uint8_t iid)
: desc_list_(std::move(desc_list)), desc_(desc), iid_(iid) {}
virtual ~AudioUnit() {}
DISALLOW_COPY_ASSIGN_AND_MOVE(AudioUnit);
template <typename T>
zx_status_t CtrlReq(const usb_protocol_t& proto, uint8_t code, uint16_t val, T* data) {
return CtrlReq(proto, code, val, sizeof(*data), data);
}
// See note in UsbAudioControlInterface. Holding a constant RefPtr to our
// descriptor list ensures that we can never accidentally release the list
// while we still exist.
const fbl::RefPtr<DescriptorListMemory> desc_list_;
const usb_audio_ac_ut_desc* const desc_;
// The interface id of the control interface that this unit/terminal belongs
// to. All units need to know this number in order to properly address
// get/set commands.
const uint8_t iid_;
private:
zx_status_t CtrlReq(const usb_protocol_t& proto,
uint8_t code, uint16_t val, uint16_t len, void* data);
// State flags used when building valid audio paths.
bool visited_ = false;
bool in_use_ = false;
};
class Terminal : public AudioUnit {
public:
uint16_t terminal_type() const { return term_desc_->wTerminalType; }
bool is_stream_terminal() const { return terminal_type() == USB_AUDIO_TERMINAL_USB_STREAMING; }
bool is_usb_terminal() const {
// See Universal Serial Bus Device Class Definition for Terminal Types,
// rev 1.0 Section 2.1
return (terminal_type() & 0xFF00) == 0x0100;
}
protected:
Terminal(fbl::RefPtr<DescriptorListMemory> desc_list,
const usb_audio_ac_terminal_desc* desc,
uint8_t iid)
: AudioUnit(std::move(desc_list), reinterpret_cast<const usb_audio_ac_ut_desc*>(desc), iid),
term_desc_(desc) {}
DISALLOW_COPY_ASSIGN_AND_MOVE(Terminal);
private:
const usb_audio_ac_terminal_desc* const term_desc_;
};
class InputTerminal : public Terminal {
public:
uint32_t source_count() const final { return 0; }
uint32_t source_id(uint32_t ndx) const final { return kInvalidID; }
private:
friend class AudioUnit;
static fbl::RefPtr<InputTerminal> Create(const DescriptorListMemory::Iterator& iter,
uint8_t iid);
InputTerminal(fbl::RefPtr<DescriptorListMemory> desc_list,
const usb_audio_ac_input_terminal_desc* desc,
uint8_t iid)
: Terminal(std::move(desc_list),
reinterpret_cast<const usb_audio_ac_terminal_desc*>(desc),
iid) {}
DISALLOW_COPY_ASSIGN_AND_MOVE(InputTerminal);
const usb_audio_ac_input_terminal_desc* input_desc() const {
return reinterpret_cast<const usb_audio_ac_input_terminal_desc*>(desc_);
}
};
class OutputTerminal : public Terminal {
public:
uint32_t source_count() const final { return 1; }
uint32_t source_id(uint32_t ndx) const final {
return (ndx == 0) ? output_desc()->bSourceID : kInvalidID;
}
private:
friend class AudioUnit;
static fbl::RefPtr<OutputTerminal> Create(const DescriptorListMemory::Iterator& iter,
uint8_t iid);
OutputTerminal(fbl::RefPtr<DescriptorListMemory> desc_list,
const usb_audio_ac_output_terminal_desc* desc,
uint8_t iid)
: Terminal(std::move(desc_list),
reinterpret_cast<const usb_audio_ac_terminal_desc*>(desc), iid) {}
DISALLOW_COPY_ASSIGN_AND_MOVE(OutputTerminal);
const usb_audio_ac_output_terminal_desc* output_desc() const {
return reinterpret_cast<const usb_audio_ac_output_terminal_desc*>(desc_);
}
};
class MixerUnit : public AudioUnit {
public:
uint32_t source_count() const final { return mixer_desc()->bNrInPins; }
uint32_t source_id(uint32_t ndx) const final {
return (ndx < source_count()) ? mixer_desc()->baSourceID[ndx] : kInvalidID;
}
const usb_audio_ac_mixer_unit_desc_0* mixer_desc() const {
return reinterpret_cast<const usb_audio_ac_mixer_unit_desc_0*>(desc_);
}
const usb_audio_ac_mixer_unit_desc_1* mixer_desc_1() const { return mixer_desc_1_; }
const usb_audio_ac_mixer_unit_desc_2* mixer_desc_2() const { return mixer_desc_2_; }
// TODO(johngro): Add a probe method to mixer so that we can read all of the
// mix/max/cur settings for the mixer crossbar. Because of the way that we
// are organizing our graph, this method may need to be extended to have
// access to the set of all units present in the control interface.
private:
friend class AudioUnit;
static fbl::RefPtr<MixerUnit> Create(const DescriptorListMemory::Iterator& iter, uint8_t iid);
MixerUnit(fbl::RefPtr<DescriptorListMemory> desc_list,
const usb_audio_ac_mixer_unit_desc_0* desc_0,
const usb_audio_ac_mixer_unit_desc_1* desc_1,
const usb_audio_ac_mixer_unit_desc_2* desc_2,
uint8_t iid)
: AudioUnit(std::move(desc_list),
reinterpret_cast<const usb_audio_ac_ut_desc*>(desc_0),
iid),
mixer_desc_1_(desc_1),
mixer_desc_2_(desc_2) {}
DISALLOW_COPY_ASSIGN_AND_MOVE(MixerUnit);
const usb_audio_ac_mixer_unit_desc_1* const mixer_desc_1_;
const usb_audio_ac_mixer_unit_desc_2* const mixer_desc_2_;
};
class SelectorUnit : public AudioUnit {
public:
uint32_t source_count() const final { return selector_desc()->bNrInPins; }
uint32_t source_id(uint32_t ndx) const final {
return (ndx < source_count()) ? selector_desc()->baSourceID[ndx] : kInvalidID;
}
const usb_audio_ac_selector_unit_desc_0* selector_desc() const {
return reinterpret_cast<const usb_audio_ac_selector_unit_desc_0*>(desc_);
}
const usb_audio_ac_selector_unit_desc_1* selector_desc_1() const { return selector_desc_1_; }
// Select the input to the selector unit identified by the desired upstream
// unit's id;
zx_status_t Select(const usb_protocol_t& proto, uint8_t upstream_id);
private:
friend class AudioUnit;
static fbl::RefPtr<SelectorUnit> Create(const DescriptorListMemory::Iterator& iter,
uint8_t iid);
SelectorUnit(fbl::RefPtr<DescriptorListMemory> desc_list,
const usb_audio_ac_selector_unit_desc_0* desc_0,
const usb_audio_ac_selector_unit_desc_1* desc_1,
uint8_t iid)
: AudioUnit(std::move(desc_list),
reinterpret_cast<const usb_audio_ac_ut_desc*>(desc_0),
iid),
selector_desc_1_(desc_1) {}
DISALLOW_COPY_ASSIGN_AND_MOVE(SelectorUnit);
const usb_audio_ac_selector_unit_desc_1* const selector_desc_1_;
};
class FeatureUnit : public AudioUnit {
public:
uint32_t source_count() const final { return 1; }
uint32_t source_id(uint32_t ndx) const final { return feature_desc()->bSourceID; }
bool has_vol() const { return (master_feat_ | ch_feat_) & USB_AUDIO_FU_BMA_VOLUME; }
bool has_agc() const { return (master_feat_ | ch_feat_) & USB_AUDIO_FU_BMA_AUTOMATIC_GAIN; }
bool has_mute() const { return (master_feat_ | ch_feat_) & USB_AUDIO_FU_BMA_MUTE; }
float vol_min_db() const { return static_cast<float>(vol_min_) * kDbPerTick; }
float vol_max_db() const { return static_cast<float>(vol_max_) * kDbPerTick; }
float vol_res_db() const { return static_cast<float>(vol_res_) * kDbPerTick; }
float vol_cur_db() const { return static_cast<float>(vol_cur_) * kDbPerTick; }
bool mute_cur() const { return !!mute_cur_; }
bool agc_cur() const { return !!agc_cur_; }
const usb_audio_ac_feature_unit_desc_0* feature_desc() const {
return reinterpret_cast<const usb_audio_ac_feature_unit_desc_0*>(desc_);
}
const usb_audio_ac_feature_unit_desc_1* feature_desc_1() const { return feature_desc_1_; }
zx_status_t Probe(const usb_protocol_t& proto) final;
// Do the best we can to set the volume/mute/agc. Return the value actually set.
float SetVol (const usb_protocol_t& proto, float db);
bool SetMute(const usb_protocol_t& proto, bool mute);
bool SetAgc (const usb_protocol_t& proto, bool enabled);
private:
friend class AudioUnit;
// Section 5.2.2.4.3.2 of the USB Audio 1.0 spec
static constexpr float kDbPerTick = 1.0f / 256.0f;
// A small struct used to track the various features supported by a channel
// controlled by this feature unit.
struct Features {
bool has_vol() const { return supported_ & USB_AUDIO_FU_BMA_VOLUME; }
bool has_mute() const { return supported_ & USB_AUDIO_FU_BMA_MUTE; }
bool has_agc() const { return supported_ & USB_AUDIO_FU_BMA_AUTOMATIC_GAIN; }
uint32_t supported_ = 0;
int16_t vol_min_ = 0;
int16_t vol_max_ = 0;
int16_t vol_res_ = 0;
int16_t vol_cur_ = 0;
bool mute_cur = false;
};
static fbl::RefPtr<FeatureUnit> Create(const DescriptorListMemory::Iterator& iter, uint8_t iid);
// Map a feature ordinal to its corresponding bit in the bmaControls
// bitmask. Thankfully, as of the USB Audio 1.0 spec, this is just a simple
// offset and shift operation.
static constexpr uint32_t FeatureToBit(uint8_t ord) { return 1u << (ord - 1); }
FeatureUnit(fbl::RefPtr<DescriptorListMemory> desc_list,
const usb_audio_ac_feature_unit_desc_0* desc_0,
const usb_audio_ac_feature_unit_desc_1* desc_1,
fbl::unique_ptr<Features[]> feature_mem,
size_t feature_len,
uint8_t iid)
: AudioUnit(std::move(desc_list),
reinterpret_cast<const usb_audio_ac_ut_desc*>(desc_0),
iid),
feature_desc_1_(desc_1),
features_(feature_mem.release(), feature_len) {}
DISALLOW_COPY_ASSIGN_AND_MOVE(FeatureUnit);
template <typename T>
zx_status_t FeatCtrlReq(const usb_protocol_t& proto,
uint8_t code, uint8_t ctrl, uint8_t ch, T* data) {
// See Section 5.2.2.4 in the USB Audio 1.0 spec for the encoding of val.
uint16_t val = static_cast<uint16_t>((static_cast<uint16_t>(ctrl) << 8) | ch);
return CtrlReq<T>(proto, code, val, data);
}
template <typename T>
void SetFeature(const usb_protocol_t& proto, uint8_t feature, T val) {
auto mask = FeatureToBit(feature);
if (master_feat_ & mask) {
FeatCtrlReq(proto, USB_AUDIO_SET_CUR, feature, 0, &val);
} else {
for (size_t i = 1; i < features_.size(); ++i) {
uint8_t ch = static_cast<uint8_t>(i);
FeatCtrlReq(proto, USB_AUDIO_SET_CUR, feature, ch, &val);
}
}
}
const usb_audio_ac_feature_unit_desc_1* const feature_desc_1_;
const fbl::Array<Features> features_;
uint32_t master_feat_ = 0;
uint32_t ch_feat_ = 0;
int16_t vol_min_ = 0;
int16_t vol_max_ = 0;
int16_t vol_res_ = 0;
int16_t vol_cur_ = 0;
uint8_t mute_cur_ = 0;
uint8_t agc_cur_ = 0;
};
class ProcessingUnit : public AudioUnit {
public:
uint32_t source_count() const final { return processing_desc()->bNrInPins; }
uint32_t source_id(uint32_t ndx) const final {
return (ndx < source_count()) ? processing_desc()->baSourceID[ndx] : kInvalidID;
}
const usb_audio_ac_processing_unit_desc_0* processing_desc() const {
return reinterpret_cast<const usb_audio_ac_processing_unit_desc_0*>(desc_);
}
const usb_audio_ac_processing_unit_desc_1* processing_desc_1() const {
return processing_desc_1_;
}
const usb_audio_ac_processing_unit_desc_2* processing_desc_2() const {
return processing_desc_2_;
}
private:
friend class AudioUnit;
static fbl::RefPtr<ProcessingUnit> Create(const DescriptorListMemory::Iterator& iter,
uint8_t iid);
ProcessingUnit(fbl::RefPtr<DescriptorListMemory> desc_list,
const usb_audio_ac_processing_unit_desc_0* desc_0,
const usb_audio_ac_processing_unit_desc_1* desc_1,
const usb_audio_ac_processing_unit_desc_2* desc_2,
uint8_t iid)
: AudioUnit(std::move(desc_list),
reinterpret_cast<const usb_audio_ac_ut_desc*>(desc_0),
iid),
processing_desc_1_(desc_1),
processing_desc_2_(desc_2) {}
DISALLOW_COPY_ASSIGN_AND_MOVE(ProcessingUnit);
const usb_audio_ac_processing_unit_desc_1* const processing_desc_1_;
const usb_audio_ac_processing_unit_desc_2* const processing_desc_2_;
};
class ExtensionUnit : public AudioUnit {
public:
uint32_t source_count() const final { return extension_desc()->bNrInPins; }
uint32_t source_id(uint32_t ndx) const final {
return (ndx < source_count()) ? extension_desc()->baSourceID[ndx] : kInvalidID;
}
const usb_audio_ac_extension_unit_desc_0* extension_desc() const {
return reinterpret_cast<const usb_audio_ac_extension_unit_desc_0*>(desc_);
}
const usb_audio_ac_extension_unit_desc_1* extension_desc_1() const { return extension_desc_1_; }
const usb_audio_ac_extension_unit_desc_2* extension_desc_2() const { return extension_desc_2_; }
private:
friend class AudioUnit;
static fbl::RefPtr<ExtensionUnit> Create(const DescriptorListMemory::Iterator& iter,
uint8_t iid);
ExtensionUnit(fbl::RefPtr<DescriptorListMemory> desc_list,
const usb_audio_ac_extension_unit_desc_0* desc_0,
const usb_audio_ac_extension_unit_desc_1* desc_1,
const usb_audio_ac_extension_unit_desc_2* desc_2,
uint8_t iid)
: AudioUnit(std::move(desc_list),
reinterpret_cast<const usb_audio_ac_ut_desc*>(desc_0),
iid),
extension_desc_1_(desc_1),
extension_desc_2_(desc_2) {}
DISALLOW_COPY_ASSIGN_AND_MOVE(ExtensionUnit);
const usb_audio_ac_extension_unit_desc_1* const extension_desc_1_;
const usb_audio_ac_extension_unit_desc_2* const extension_desc_2_;
};
} // namespace usb
} // namespace audio