blob: 9c2a52d02202f1fed8256eb46ff8bf82becd9991 [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/intrusive_double_list.h>
#include <fbl/vector.h>
#include <zircon/device/audio.h>
#include <zircon/hw/usb/audio.h>
#include <utility>
#include "usb-audio.h"
#include "usb-audio-descriptors.h"
namespace audio {
namespace usb {
class UsbAudioDevice;
class AudioPath;
class UsbAudioStreamInterface :
public fbl::DoublyLinkedListable<fbl::unique_ptr<UsbAudioStreamInterface>> {
public:
// A small helper struct which maps from a Fuchsia format range to the
// alternate interface ID which supports that range.
struct FormatMapEntry {
const audio_stream_format_range_t range_;
// The alternate interface ID, endpoint address, and maximum request
// size which need to be used when configuring the stream interface to
// use the format described by range_.
const uint8_t alt_id_;
const uint8_t ep_addr_;
const uint16_t max_req_size_;
FormatMapEntry(const audio_stream_format_range_t& range,
uint8_t alt_id,
uint8_t ep_addr,
uint16_t max_req_size)
: range_(range),
alt_id_(alt_id),
ep_addr_(ep_addr),
max_req_size_(max_req_size) {}
};
// Note that UsbAudioStreamInterfaces are entirely owned by UsbAudioDevice
// instances. The stream interface needs to hold a pointer to its parent,
// so it is critically important that the owning parent is certain that
// the stream interface (and all of its children) have been properly shut
// down before exiting. At all times, the lifetime of the stream interface
// needs to be a subset of the lifetime of the device parent.
//
// Note, the iterator passed to the create method *must* be pointing at a
// valid interface header with class == audio and subclass == streaming
// interface. The interface ID encountered in this first header will become
// the interface ID of this StreamInterface object.
static fbl::unique_ptr<UsbAudioStreamInterface>
Create(UsbAudioDevice* parent, DescriptorListMemory::Iterator* iter);
// Called to add a new alternate streaming interface to this StreamInterface
// object. The iterator must be pointing at a valid audio stream interface
// descriptor which shares a an IID with this object.
zx_status_t AddInterface(DescriptorListMemory::Iterator* iter);
// Called after all of the interface descriptors have been discovered and
// added to this stream interface to allow the stream interface a chance to
// build its list of format ranges and the alternate interface ID which
// support them.
zx_status_t BuildFormatMap();
// Called from the UsbAudioStream to lookup the index of a format which
// matches the user's request. Note, this does not actually cause the
// interface to switch to this format. Use ActivateFormat, passing the
// index retrieved from there, to achieve that.
zx_status_t LookupFormat(uint32_t frames_per_second,
uint16_t channels,
audio_sample_format_t sample_format,
size_t* out_format_ndx);
// Called from the UsbAudioStream to activate the chosen format interface
// and to configure the specific frame rate for that interface.
zx_status_t ActivateFormat(size_t ndx, uint32_t frames_per_second);
// Called from the UsbAudioStream to activate the alternate idle interface
// (if any). Will return ZX_ERR_NOT_SUPPORTED if there is no idle
// interface.
zx_status_t ActivateIdleFormat();
// Called at the end of device probing to link a discovered audio path to
// this stream interface.
void LinkPath(fbl::unique_ptr<AudioPath> path);
uint8_t iid() const { return iid_; }
uint16_t max_req_size() const { return max_req_size_; }
const fbl::unique_ptr<AudioPath>& path() { return path_; }
const fbl::Vector<FormatMapEntry>& formats() { return format_map_; }
// Properties shared by all formats of this stream interface.
uint8_t term_link() const { return term_link_; }
uint8_t ep_addr() const { return ep_addr_; }
uint8_t ep_attr() const { return ep_attr_; }
Direction direction() const {
return (ep_addr() & USB_ENDPOINT_DIR_MASK)
? Direction::Input
: Direction::Output;
}
EndpointSyncType ep_sync_type() const {
return static_cast<EndpointSyncType>(ep_attr() & USB_ENDPOINT_SYNCHRONIZATION_MASK);
}
// Accessor for debug logging.
const char* log_prefix() const;
private:
friend class fbl::unique_ptr<UsbAudioStreamInterface>;
// An internal helper class which contains all of the information we need to
// support an alternate interface setting which supports a given format.
class Format : public fbl::DoublyLinkedListable<fbl::unique_ptr<Format>> {
public:
Format(const UsbAudioStreamInterface* parent,
fbl::RefPtr<DescriptorListMemory> desc_list,
const usb_interface_descriptor_t* interface_hdr,
const usb_audio_as_header_desc* class_hdr)
: parent_(parent),
desc_list_(std::move(desc_list)),
interface_hdr_(interface_hdr),
class_hdr_(class_hdr) {}
const char* log_prefix() const { return parent_->log_prefix(); }
uint8_t iid() const { return interface_hdr_->bInterfaceNumber; }
uint8_t alt_id() const { return interface_hdr_->bAlternateSetting; }
uint8_t term_link() const { return class_hdr_->bTerminalLink; }
uint16_t format_tag() const { return class_hdr_->wFormatTag; }
uint8_t ep_addr() const { return ep_desc_->bEndpointAddress; }
uint8_t ep_attr() const { return ep_desc_->bmAttributes; }
uint16_t max_req_size() const { return ep_desc_->wMaxPacketSize; }
uint8_t frame_rate_cnt() const { return fmt_desc_->bSamFreqType; }
uint8_t ch_count() const { return fmt_desc_->bNrChannels; }
uint8_t bit_resolution() const { return fmt_desc_->bBitResolution; }
uint8_t subframe_bytes() const { return fmt_desc_->bSubFrameSize; }
// Min/Max continuous frame rates. Valid *only* after initialize has
// been successfully called, and *only* if frame_rate_cnt() == 0.
uint32_t min_cont_frame_rate() const {
ZX_DEBUG_ASSERT(frame_rate_cnt() == 0);
return UnpackFrameRate(fmt_desc_->tSamFreq[0]);
}
uint32_t max_cont_frame_rate() const {
ZX_DEBUG_ASSERT(frame_rate_cnt() == 0);
return UnpackFrameRate(fmt_desc_->tSamFreq[1]);
}
// Fetch discrete frame rate #ndx. Valid *only* after initialize has
// been successfully called, and *only* if ndx < frame_rate_cnt()
uint32_t frame_rate(uint8_t ndx) const {
ZX_DEBUG_ASSERT(ndx < frame_rate_cnt());
return UnpackFrameRate(fmt_desc_->tSamFreq[ndx]);
}
zx_status_t Init(DescriptorListMemory::Iterator* iter);
private:
friend class fbl::unique_ptr<Format>;
~Format() = default;
// Packing format described in section 2.2.5 of USB Device Class
// Definition for Audio Data Formats.
static inline uint32_t UnpackFrameRate(const usb_audio_as_samp_freq& rate) {
return (static_cast<uint32_t>(rate.freq[2]) << 16) |
(static_cast<uint32_t>(rate.freq[1]) << 8) |
static_cast<uint32_t>(rate.freq[0]);
}
// Determined at construction time
const UsbAudioStreamInterface* parent_;
const fbl::RefPtr<DescriptorListMemory> desc_list_;
const usb_interface_descriptor_t* const interface_hdr_;
const usb_audio_as_header_desc* const class_hdr_;
// Determined at initialization time
const usb_audio_as_format_type_i_desc* fmt_desc_ = nullptr;
const usb_endpoint_descriptor_t* ep_desc_ = nullptr;
const usb_audio_as_isoch_ep_desc* class_ep_desc_ = nullptr;
};
UsbAudioStreamInterface(UsbAudioDevice* parent,
fbl::RefPtr<DescriptorListMemory> desc_list,
uint8_t iid)
: parent_(*parent),
iid_(iid),
desc_list_(std::move(desc_list)) {
ZX_DEBUG_ASSERT(parent != nullptr);
}
~UsbAudioStreamInterface() = default;
// The reference to our parent. Note, because of this unmanaged reference,
// it is critically important that the surrounding code ensure that we never
// outlive our parent device.
UsbAudioDevice& parent_;
// The unique interface ID for this group of alternate interface descriptions.
const uint8_t iid_;
// Cached, unmanaged pointers to our interface and class descriptors. The
// memory which backs these descriptors is kept alive by the top level
// desc_list_ reference.
//
// TODO(johngro) : this desc_list_ memory is contained in our parent
// UsbAudioDevice. Since we have already committed to having a lifetime
// which is strictly <= the lifetime of our parent, we should probably just
// access the descriptor memory using our parent instead of holding our own
// reference to it.
const fbl::RefPtr<DescriptorListMemory> desc_list_;
// A pointer to an "idle" interface; IOW an interface which defines no
// endpoints. While not all audio streaming interfaces have one of these,
// many seem to. In theory, this allows a stream interface to save
// isochronos bandwidth by selecting an alternate interface which requires
// no isoch bandwidth allocation when the device is idle.
const usb_interface_descriptor_t* idle_hdr_ = nullptr;
// The terminal link ID which is shared by all of the valid formats we have
// discovered.
uint8_t term_link_ = 0xFF;
// The endpoint address and attributes which are shared by all of the valid
// formats we have discovered.
uint8_t ep_addr_ = 0xFF;
uint8_t ep_attr_ = 0x0;
// The largest maximum request size computed across all of our discovered
// endpoints.
uint16_t max_req_size_ = 0;
// A list of the formats (generic descriptors followed by a class specific
// interface descriptor) we have discovered.
fbl::DoublyLinkedList<fbl::unique_ptr<Format>> formats_;
// The path through the control interface's terminal/unit graph that this
// streaming interface is linked to.
fbl::unique_ptr<AudioPath> path_;
// A vector which contains the mappings from Fuchsia format ranges to the
// alternate interface ID of the interface which supports that format range.
fbl::Vector<FormatMapEntry> format_map_;
};
} // namespace usb
} // namespace audio