// 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 #include #include #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::Create( UsbAudioDevice* parent, DescriptorListMemory::Iterator* iter) { ZX_DEBUG_ASSERT(parent != nullptr); ZX_DEBUG_ASSERT(iter != nullptr); auto ihdr = iter->hdr_as(); ZX_DEBUG_ASSERT(ihdr); // The caller should have already verified this. uint8_t iid = ihdr->bInterfaceNumber; fbl::AllocChecker ac; fbl::unique_ptr 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(); 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(); if ((next_hdr != nullptr) && (next_hdr->bDescriptorType == USB_AUDIO_CS_INTERFACE) && (next_hdr->bDescriptorSubtype == USB_AUDIO_AS_GENERAL)) { auto aud_hdr = iter->hdr_as(); 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(&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(fbl::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_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(frames_per_second); buffer[1] = static_cast(frames_per_second >> 8); buffer[2] = static_cast(frames_per_second >> 16); status = usb_control(&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_, &buffer, sizeof(buffer), ZX_TIME_INFINITE, NULL); 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 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_ = fbl::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(); if ((ihdr == nullptr) || (ihdr->bDescriptorSubtype != USB_AUDIO_AS_FORMAT_TYPE)) { break; } auto fmt_hdr = iter->hdr_as(); 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(); 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(); 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(); if ((ihdr == nullptr) || (ihdr->bDescriptorSubtype != USB_AUDIO_EP_GENERAL)) { break; } auto class_ep_desc = iter->hdr_as(); 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