blob: 9af1decd0a810d671961b69cbf19fd945cc61372 [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.
#include <audio-proto-utils/format-utils.h>
#include <fbl/algorithm.h>
#include <fbl/auto_call.h>
#include <utility>
#include "debug-logging.h"
#include "usb-audio-device.h"
#include "usb-audio-path.h"
#include "usb-audio-stream-interface.h"
namespace audio {
namespace usb {
// We use our parent's log prefix
const char* UsbAudioStreamInterface::log_prefix() const {
return parent_.log_prefix();
}
fbl::unique_ptr<UsbAudioStreamInterface> UsbAudioStreamInterface::Create(
UsbAudioDevice* parent,
DescriptorListMemory::Iterator* iter) {
ZX_DEBUG_ASSERT(parent != nullptr);
ZX_DEBUG_ASSERT(iter != nullptr);
auto ihdr = iter->hdr_as<usb_interface_descriptor_t>();
ZX_DEBUG_ASSERT(ihdr); // The caller should have already verified this.
uint8_t iid = ihdr->bInterfaceNumber;
fbl::AllocChecker ac;
fbl::unique_ptr<UsbAudioStreamInterface> ret(
new (&ac) UsbAudioStreamInterface(parent, iter->desc_list(), iid));
if (ac.check()) {
zx_status_t res = ret->AddInterface(iter);
if (res == ZX_OK) {
return ret;
}
LOG_EX(ERROR, *parent,
"Failed to add initial interface (id %u) to UsbAudioStreamInterface (res %d)\n",
iid, res);
} else {
iter->Next(); // Success or failure, we are expected to consume this header.
LOG_EX(ERROR, *parent,
"Out of memory attempting to allocate UsbAudioStreamInterface (id %u)\n", iid);
}
return nullptr;
}
zx_status_t UsbAudioStreamInterface::AddInterface(DescriptorListMemory::Iterator* iter) {
// All of these checks should have been made by the caller already.
ZX_DEBUG_ASSERT(iter != nullptr);
ZX_DEBUG_ASSERT(iter->desc_list() == desc_list_);
auto ihdr = iter->hdr_as<usb_interface_descriptor_t>();
ZX_DEBUG_ASSERT(ihdr != nullptr);
ZX_DEBUG_ASSERT(ihdr->bInterfaceNumber == iid());
// No matter what, we need to consume the current descriptor header.
iter->Next();
// Make sure that this header represents a unique alternate setting.
auto alt_id = ihdr->bAlternateSetting;
auto fmt_iter = formats_.find_if([alt_id](const Format& fmt) -> bool {
return alt_id == fmt.alt_id();
});
if (fmt_iter.IsValid() || ((idle_hdr_ && (idle_hdr_->bAlternateSetting == alt_id)))) {
LOG(WARN,
"Skipping duplicate alternate setting ID in streaming interface descriptor. "
"(iid %u, alt_id %u)\n",
ihdr->bInterfaceNumber, alt_id);
// Don't return an error if we encounter a malformed header. Just skip
// it and do the best we can with what we have.
return ZX_OK;
}
// Examine the next descriptor. If it is an audio streaming class specific
// interface descriptor, then this top level descriptor is part of a
// described format. Otherwise, this is an empty alternate interface which
// is probably meant to be selected when this streaming interface is idle
// and should not be using any bus resources.
auto next_hdr = iter->hdr_as<usb_audio_desc_header>();
if ((next_hdr != nullptr) &&
(next_hdr->bDescriptorType == USB_AUDIO_CS_INTERFACE) &&
(next_hdr->bDescriptorSubtype == USB_AUDIO_AS_GENERAL)) {
auto aud_hdr = iter->hdr_as<usb_audio_as_header_desc>();
iter->Next();
if (aud_hdr == nullptr) {
LOG(WARN,
"Skipping badly formed alternate setting ID in streaming interface descriptor "
"(iid %u, alt_id %u).\n",
ihdr->bInterfaceNumber, alt_id);
return ZX_OK;
}
fbl::AllocChecker ac;
auto format = fbl::make_unique_checked<Format>(&ac, this, iter->desc_list(), ihdr, aud_hdr);
if (!ac.check()) {
LOG(ERROR, "Out of memory attempt to add Format to StreamInterface\n");
return ZX_ERR_NO_MEMORY;
}
zx_status_t status = format->Init(iter);
if (status != ZX_OK) {
LOG(WARN,
"Skipping bad format streaming interface descriptor. (iid %u, alt_id %u)\n",
ihdr->bInterfaceNumber, alt_id);
return ZX_OK;
}
// Make sure that the endpoint address and terminal link ID of this
// format matches all previously encountered formats.
//
// TODO(johngro) : It is unclear whether or not it makes any sense to
// have formats which link to different audio paths or have different
// endpoint addresses (implying potentially different directions). For
// now we simply skip these formats if we encounter them.
//
// If we ever encounter a device which has a mix of these parameters, we
// need come back and determine if there is a good generic approach for
// dealing with the situation.
if (!formats_.is_empty()) {
if (format->term_link() != term_link_) {
LOG(WARN,
"Skipping format (iid %u, alt_id %u) with non-uniform terminal ID "
"(expected %u, got %u)\n",
ihdr->bInterfaceNumber, alt_id, term_link_, format->term_link());
return ZX_OK;
}
if ((format->ep_addr() != ep_addr_) || (format->ep_attr() != ep_attr_)) {
LOG(ERROR,
"Skipping format (iid %u, alt_id %u) with non-uniform endpoint "
"address/attributes (expected 0x%02x/0x%02x, got 0x%02x/0x%02x)\n",
ihdr->bInterfaceNumber, alt_id,
ep_addr_, ep_attr_,
format->ep_addr(), format->ep_attr());
return ZX_OK;
}
} else {
term_link_ = format->term_link();
ep_addr_ = format->ep_addr();
ep_attr_ = format->ep_attr();
}
max_req_size_ = fbl::max(max_req_size_, format->max_req_size());
formats_.push_back(std::move(format));
} else {
if (idle_hdr_ == nullptr) {
idle_hdr_ = ihdr;
} else {
LOG(WARN,
"Skipping duplicate \"idle\" interface descriptor in streaming interface "
"descriptor. (iid %u, alt_id %u)\n",
ihdr->bInterfaceNumber, ihdr->bAlternateSetting);
}
}
return ZX_OK;
}
zx_status_t UsbAudioStreamInterface::BuildFormatMap() {
if (format_map_.size()) {
LOG(WARN, "Attempted to re-build format map for streaming interface (iid %u)\n", iid());
return ZX_ERR_BAD_STATE;
}
// Make a pass over our list of formats and figure out how big our format
// map vector may need to be.
//
// Note: this is a rough worst case bound on how big the vector needs to be.
// Someday, we could come back here and compute a much tighter bound if we
// wanted to.
size_t worst_case_map_entries = 0;
for (const auto& fmt : formats_) {
// A frame rate count of 0 indicates a continuous format range which
// requires only one format range entry.
worst_case_map_entries += fmt.frame_rate_cnt() ? fmt.frame_rate_cnt() : 1;
}
// Now reserve our memory.
fbl::AllocChecker ac;
format_map_.reserve(worst_case_map_entries, &ac);
if (!ac.check()) {
LOG(ERROR, "Out of memory attempting to reserve %zu format ranges\n",
worst_case_map_entries);
return ZX_ERR_NO_MEMORY;
}
// Now iterate over our set and build the map.
for (const auto& fmt : formats_) {
// Record the min/max number of channels.
audio_stream_format_range_t range;
range.min_channels = fmt.ch_count();
range.max_channels = fmt.ch_count();
// Encode the sample container type from the type I format descriptor
// as an audio device driver audio_sample_format_t. If we encounter
// anything that we don't know how to encode, log a warning and skip the
// format.
//
auto tag = fmt.format_tag();
if (tag == USB_AUDIO_AS_FT_PCM8) {
if ((fmt.bit_resolution() != 8) || (fmt.subframe_bytes() != 1)) {
LOG(WARN, "Skipping PCM8 format with invalid bit res/subframe size (%u/%u)\n",
fmt.bit_resolution(), fmt.subframe_bytes());
continue;
}
range.sample_formats = static_cast<audio_sample_format_t>(
AUDIO_SAMPLE_FORMAT_8BIT | AUDIO_SAMPLE_FORMAT_FLAG_UNSIGNED);
} else
if (tag == USB_AUDIO_AS_FT_IEEE_FLOAT) {
if ((fmt.bit_resolution() != 32) || (fmt.subframe_bytes() != 4)) {
LOG(WARN, "Skipping IEEE_FLOAT format with invalid bit res/subframe size (%u/%u)\n",
fmt.bit_resolution(), fmt.subframe_bytes());
continue;
}
range.sample_formats = AUDIO_SAMPLE_FORMAT_32BIT_FLOAT;
} else
if (tag == USB_AUDIO_AS_FT_PCM) {
switch (fmt.bit_resolution()) {
case 8:
case 16:
case 32: {
if (fmt.subframe_bytes() != (fmt.bit_resolution() >> 3)) {
LOG(WARN,
"Skipping PCM format. Subframe size (%u bytes) does not "
"match Bit Res (%u bits)\n",
fmt.bit_resolution(),
fmt.subframe_bytes());
continue;
}
switch (fmt.bit_resolution()) {
case 8: range.sample_formats = AUDIO_SAMPLE_FORMAT_8BIT; break;
case 16: range.sample_formats = AUDIO_SAMPLE_FORMAT_16BIT; break;
case 32: range.sample_formats = AUDIO_SAMPLE_FORMAT_32BIT; break;
}
} break;
case 20:
case 24: {
if ((fmt.subframe_bytes() != 3) && (fmt.subframe_bytes() != 4)) {
LOG(WARN,
"Skipping PCM format. %u-bit audio must be packed into a 3 "
"or 4 byte subframe (Subframe size %u)\n",
fmt.bit_resolution(),
fmt.subframe_bytes());
continue;
}
switch (fmt.bit_resolution()) {
case 20:
range.sample_formats = ((fmt.subframe_bytes() == 3)
? AUDIO_SAMPLE_FORMAT_20BIT_PACKED
: AUDIO_SAMPLE_FORMAT_20BIT_IN32);
break;
case 24:
range.sample_formats = ((fmt.subframe_bytes() == 3)
? AUDIO_SAMPLE_FORMAT_24BIT_PACKED
: AUDIO_SAMPLE_FORMAT_24BIT_IN32);
break;
}
} break;
default:
LOG(WARN, "Skipping PCM format with unsupported bit res (%u bits)\n",
fmt.bit_resolution());
continue;
}
} else {
LOG(WARN, "Skipping unsupported format tag (%u)\n", tag);
continue;
}
// Now pack the supported frame rates. A format with a frame rate count of
// 0 is a continuous range of frame rates. Otherwise, we pack each discrete
// frame rate as an individual entry.
//
// TODO(johngro) : Discrete frame rates could be encoded more compactly
// if wanted to do so by extracting all of the 48k and 44.1k rates into
// a bitmask, and then putting together ranges which represented
// continuous runs of frame rates in each of the families.
if (fmt.frame_rate_cnt()) {
for (uint8_t i = 0; i < fmt.frame_rate_cnt(); ++i) {
uint32_t rate = fmt.frame_rate(i);
range.min_frames_per_second = rate;
range.max_frames_per_second = rate;
if (audio::utils::FrameRateIn48kFamily(rate)) {
range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY;
} else
if (audio::utils::FrameRateIn441kFamily(rate)) {
range.flags = ASF_RANGE_FLAG_FPS_44100_FAMILY;
} else {
range.flags = ASF_RANGE_FLAG_FPS_CONTINUOUS;
}
format_map_.push_back({ range, fmt.alt_id(), fmt.ep_addr(), fmt.max_req_size() });
}
} else {
range.min_frames_per_second = fmt.min_cont_frame_rate();
range.max_frames_per_second = fmt.max_cont_frame_rate();
range.flags = ASF_RANGE_FLAG_FPS_CONTINUOUS;
format_map_.push_back({ range, fmt.alt_id(), fmt.ep_addr(), fmt.max_req_size() });
}
}
// If we failed to encode *any* valid format ranges, log a warning and
// return an error. This stream interface is not going to be useful to us.
if (format_map_.is_empty()) {
LOG(WARN, "Failed to find any usable formats for streaming interface (iid %u)\n", iid());
return ZX_ERR_NOT_SUPPORTED;
}
return ZX_OK;
}
zx_status_t UsbAudioStreamInterface::LookupFormat(uint32_t frames_per_second,
uint16_t channels,
audio_sample_format_t sample_format,
size_t* out_format_ndx) {
if (out_format_ndx == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
*out_format_ndx = format_map_.size();
// Search our format map to find the alternate interface setting which
// supports the requested format.
for (size_t i = 0; i < format_map_.size(); ++i) {
if (audio::utils::FormatIsCompatible(frames_per_second,
channels,
sample_format,
format_map_[i].range_)) {
*out_format_ndx = i;
return ZX_OK;
}
}
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t UsbAudioStreamInterface::ActivateFormat(size_t ndx, uint32_t frames_per_second) {
if (ndx >= format_map_.size()) {
return ZX_ERR_INVALID_ARGS;
}
// Select the interface used for this format, then configure the endpoint
// for the requested frame rate. user know what the maximum request size is
// for this interface.
zx_status_t status;
const auto& f = format_map_[ndx];
status = usb_set_interface(&parent_.usb_proto(), iid(), f.alt_id_);
if (status != ZX_OK) {
LOG(ERROR,
"Failed to select interface (id %u, alt %u, ep %u) "
"when configuring format ndx %zu (status %d)\n",
iid(), f.alt_id_, f.ep_addr_, ndx, status);
return status;
}
// Do not attempt to set the sample rate if the endpoint supports
// only one. In theory, devices should ignore this request, but in
// practice, some devices will refuse the command entirely, and we
// will get ZX_ERR_IO_REFUSED back from the bus driver.
//
// Note: This method of determining whether or not an endpoint
// supports only a single rate only works here because we currently
// demand that all of our formats share a single endpoint address.
// If this changes in the future, this heuristic will need to be
// revisited.
bool single_rate = (format_map_.size() == 1) &&
!(format_map_[0].range_.flags & ASF_RANGE_FLAG_FPS_CONTINUOUS);
if (!single_rate) {
// See section 5.2.3.2.3.1 of the USB Audio 1.0 spec.
uint8_t buffer[3];
buffer[0] = static_cast<uint8_t>(frames_per_second);
buffer[1] = static_cast<uint8_t>(frames_per_second >> 8);
buffer[2] = static_cast<uint8_t>(frames_per_second >> 16);
status = usb_control_out(&parent_.usb_proto(),
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT,
USB_AUDIO_SET_CUR,
USB_AUDIO_SAMPLING_FREQ_CONTROL << 8,
f.ep_addr_, ZX_TIME_INFINITE,
&buffer, sizeof(buffer));
if (status != ZX_OK) {
if (status == ZX_ERR_IO_REFUSED || status == ZX_ERR_IO_INVALID) {
// clear the stall/error
usb_reset_endpoint(&parent_.usb_proto(), f.ep_addr_);
}
LOG(ERROR, "Failed to set frame rate %u for ep address %u (status %d)\n",
frames_per_second, f.ep_addr_, status);
return status;
}
}
return ZX_OK;
}
zx_status_t UsbAudioStreamInterface::ActivateIdleFormat() {
if (idle_hdr_ == nullptr) {
return ZX_ERR_NOT_SUPPORTED;
}
ZX_DEBUG_ASSERT(idle_hdr_->bInterfaceNumber == iid());
return usb_set_interface(&parent_.usb_proto(), iid(), idle_hdr_->bAlternateSetting);
}
void UsbAudioStreamInterface::LinkPath(fbl::unique_ptr<AudioPath> path) {
ZX_DEBUG_ASSERT(path != nullptr);
ZX_DEBUG_ASSERT(path_ == nullptr);
ZX_DEBUG_ASSERT(direction() == path->direction());
ZX_DEBUG_ASSERT(term_link() == path->stream_terminal().id());
path_ = std::move(path);
}
zx_status_t UsbAudioStreamInterface::Format::Init(DescriptorListMemory::Iterator* iter) {
ZX_DEBUG_ASSERT(iter != nullptr);
ZX_DEBUG_ASSERT(iter->desc_list() == desc_list_);
// Skip formats tags that we currently do not support or know how to deal
// with. Right now, we only deal with the linear PCM forms of Type I audio
// formats.
switch (class_hdr_->wFormatTag) {
case USB_AUDIO_AS_FT_PCM:
case USB_AUDIO_AS_FT_PCM8:
case USB_AUDIO_AS_FT_IEEE_FLOAT:
break;
default:
LOG(ERROR,
"Unsupported format tag (0x%04hx) in class specific audio stream interface "
"(iid %u, alt_id %u)\n",
class_hdr_->wFormatTag, interface_hdr_->bInterfaceNumber, alt_id());
return ZX_ERR_NOT_SUPPORTED;
}
// Next go looking for the other headers we will need in order to operate.
// In specific, we need to find an audio format descriptor (specifically a
// Type I descriptor), a general USB Endpoint descriptor, and a audio class
// specific endpoint descriptor.
//
// If we encounter something which is not one of these things, then we have
// run out of headers to parse.
//
// If we encounter duplicates of these descriptors, or we encounter
// something clearly incompatible (such as a type II or type III format
// descriptor), then we are confused and this interface should be ignored.
// Be sure to skip headers like this if we return from the middle of the
// do/while loop below.
auto cleanup = fbl::MakeAutoCall([iter]() { iter->Next(); });
do {
auto hdr = iter->hdr();
if (hdr == nullptr) {
break;
}
if (hdr->bDescriptorType == USB_AUDIO_CS_INTERFACE) {
// Stop parsing if this is not an audio format type descriptor
auto ihdr = iter->hdr_as<usb_audio_desc_header>();
if ((ihdr == nullptr) || (ihdr->bDescriptorSubtype != USB_AUDIO_AS_FORMAT_TYPE)) {
break;
}
auto fmt_hdr = iter->hdr_as<usb_audio_as_format_type_hdr>();
if (fmt_hdr == nullptr) {
break;
}
if (fmt_hdr->bFormatType != USB_AUDIO_FORMAT_TYPE_I) {
LOG(ERROR,
"Unsupported format type (%u) in class specific audio stream format type "
"interface (iid %u, alt_id %u)\n",
fmt_hdr->bFormatType, interface_hdr_->bInterfaceNumber, alt_id());
return ZX_ERR_NOT_SUPPORTED;
}
auto fmt_desc = iter->hdr_as<usb_audio_as_format_type_i_desc>();
if ((fmt_desc_ != nullptr) || (fmt_desc == nullptr)) {
LOG(ERROR,
"Malformed or duplicate type 1 format type descriptor in class specific audio "
"interface (iid %u, alt_id %u)\n",
interface_hdr_->bInterfaceNumber, alt_id());
return ZX_ERR_NOT_SUPPORTED;
}
// Stash the pointer, we'll sanity check a bit more once we are finished finding
// headers.
fmt_desc_ = fmt_desc;
} else
if (hdr->bDescriptorType == USB_DT_ENDPOINT) {
auto ep_desc = iter->hdr_as<usb_endpoint_descriptor_t>();
if (ep_desc == nullptr) {
LOG(ERROR,
"Malformed standard endpoint descriptor in class specific audio interface "
"(iid %u, alt_id %u)\n",
interface_hdr_->bInterfaceNumber, alt_id());
return ZX_ERR_NOT_SUPPORTED;
}
// TODO(johngro): Come back and fix this. There are devices with
// multiple isochronous endpoints per format interface. Device
// which use an isochronous output endpoint with an Asynchronous
// sync type seem to have an isochronous input endpoint as well
// which is probably used for clock recovery. Instead of
// skipping/ignoring this endpoint, we really should be using it to
// recover the device clock.
if (ep_desc_ != nullptr) {
LOG(WARN,
"Skipping duplicate standard endpoint descriptor in class specific audio "
"interface (iid %u, alt_id %u, ep_addr %u)\n",
interface_hdr_->bInterfaceNumber, alt_id(), ep_desc->bEndpointAddress);
} else {
if ((usb_ep_type(ep_desc) != USB_ENDPOINT_ISOCHRONOUS) ||
(usb_ep_sync_type(ep_desc) == USB_ENDPOINT_NO_SYNCHRONIZATION)) {
LOG(WARN,
"Skipping endpoint descriptor with unsupported attributes "
"interface (iid %u, alt_id %u, ep_attr 0x%02x)\n",
interface_hdr_->bInterfaceNumber, alt_id(), ep_desc->bmAttributes);
} else {
ep_desc_ = ep_desc;
}
}
} else
if (hdr->bDescriptorType == USB_AUDIO_CS_ENDPOINT) {
// Stop parsing if this is not a class specific AS isochronous endpoint descriptor
auto ihdr = iter->hdr_as<usb_audio_desc_header>();
if ((ihdr == nullptr) || (ihdr->bDescriptorSubtype != USB_AUDIO_EP_GENERAL)) {
break;
}
auto class_ep_desc = iter->hdr_as<usb_audio_as_isoch_ep_desc>();
if (class_ep_desc == nullptr) {
LOG(ERROR,
"Malformed or class specific endpoint descriptor in class specific audio "
"interface (iid %u, alt_id %u)\n",
interface_hdr_->bInterfaceNumber, alt_id());
return ZX_ERR_NOT_SUPPORTED;
}
if (class_ep_desc_ != nullptr) {
LOG(WARN,
"Skipping duplicate class specific endpoint descriptor in class specific "
"audio interface (iid %u, alt_id %u\n",
interface_hdr_->bInterfaceNumber, alt_id());
} else {
class_ep_desc_ = class_ep_desc;
}
} else {
// We don't recognize this descriptor, so we have run out of
// descriptors that we beleive belong to this format. Move on to
// sanity checks.
break;
}
} while (iter->Next());
cleanup.cancel();
// Sanity check what we have found so far. Right now, we need to have found...
//
// 1) A Type I audio format type descriptor (PCM)
// 2) A standard Isochronous USB endpoint descriptor.
// 3) An audio class specific endpoint descriptor.
//
// In addition, we need to make sure that the range of frame rates present
// in the Type I descriptor makes sense. If the range is continuous, the
// array must contain *exactly* 2 entries. If the range is discrete, then
// the array must contain an integer number of entries, and must contain at
// least one entry.
if ((fmt_desc_ == nullptr) || (ep_desc_ == nullptr) || (class_ep_desc_ == nullptr)) {
LOG(ERROR,
"Missing one or more required descriptors in audio interface (iid %u, alt_id %u); "
"Missing%s%s%s\n",
interface_hdr_->bInterfaceNumber, alt_id(),
(fmt_desc_ == nullptr) ? " [Type I Format Type Descriptor]" : "",
(ep_desc_ == nullptr) ? " [Standard Endpoint Descriptor]" : "",
(class_ep_desc_ == nullptr) ? " [Class Endpoint Descriptor]" : "");
return ZX_ERR_NOT_SUPPORTED;
}
// hdr_as<> should have already verified this for us.
ZX_DEBUG_ASSERT(fmt_desc_->bLength >= sizeof(*fmt_desc_));
// Sanity check the size of the frame rate table.
size_t expected_bytes = (frame_rate_cnt() ? frame_rate_cnt() : 2)
* sizeof(usb_audio_as_samp_freq);
size_t extra_bytes = fmt_desc_->bLength - sizeof(*fmt_desc_);
if (expected_bytes != extra_bytes) {
LOG(ERROR,
"Bad frame rate table size in type 1 audio format type descriptor in audio interface "
"(iid %u, alt_id %u). Expected %zu, Got %zu\n",
interface_hdr_->bInterfaceNumber, alt_id(), expected_bytes, extra_bytes);
return ZX_ERR_INTERNAL;
}
// If this is a continuous range of frame rates, then the min/max order needs to be correct.
if ((frame_rate_cnt() == 0) && (min_cont_frame_rate() > max_cont_frame_rate())) {
LOG(ERROR,
"Invalid continuous frame rate range [%u, %u] type 1 audio format type descriptor in "
"audio interface (iid %u, alt_id %u).\n",
min_cont_frame_rate(), max_cont_frame_rate(),
interface_hdr_->bInterfaceNumber, alt_id());
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
} // namespace usb
} // namespace audio