| // 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 <fbl/algorithm.h> |
| #include <limits> |
| #include <math.h> |
| #include <utility> |
| |
| #include "debug-logging.h" |
| #include "usb-audio-units.h" |
| |
| namespace audio { |
| namespace usb { |
| |
| // a small internal helper methods which handles a bunch of ugly casting for us. |
| template <typename T, typename U> |
| static inline const T* offset_ptr(const U* p, size_t offset) { |
| return ((offset + sizeof(T)) <= p->bLength) |
| ? reinterpret_cast<const T*>(reinterpret_cast<uintptr_t>(p) + offset) |
| : nullptr; |
| } |
| |
| const char* AudioUnit::type_name() const { |
| switch (type()) { |
| case Type::InputTerminal: return "InputTerminal"; |
| case Type::OutputTerminal: return "OutputTerminal"; |
| case Type::MixerUnit: return "MixerUnit"; |
| case Type::SelectorUnit: return "SelectorUnit"; |
| case Type::FeatureUnit: return "FeatureUnit"; |
| case Type::ProcessingUnit: return "ProcessingUnit"; |
| case Type::ExtensionUnit: return "ExtensionUnit"; |
| default: return "<Unknown>"; |
| } |
| } |
| |
| fbl::RefPtr<AudioUnit> AudioUnit::Create(const DescriptorListMemory::Iterator& iter, uint8_t iid) { |
| auto hdr = iter.hdr_as<usb_audio_desc_header>(); |
| |
| // This should already have been verified by the code calling us. |
| ZX_DEBUG_ASSERT(hdr != nullptr); |
| |
| switch (hdr->bDescriptorSubtype) { |
| case USB_AUDIO_AC_INPUT_TERMINAL: return InputTerminal::Create(iter, iid); |
| case USB_AUDIO_AC_OUTPUT_TERMINAL: return OutputTerminal::Create(iter, iid); |
| case USB_AUDIO_AC_MIXER_UNIT: return MixerUnit::Create(iter, iid); |
| case USB_AUDIO_AC_SELECTOR_UNIT: return SelectorUnit::Create(iter, iid); |
| case USB_AUDIO_AC_FEATURE_UNIT: return FeatureUnit::Create(iter, iid); |
| case USB_AUDIO_AC_PROCESSING_UNIT: return ProcessingUnit::Create(iter, iid); |
| case USB_AUDIO_AC_EXTENSION_UNIT: return ExtensionUnit::Create(iter, iid); |
| default: |
| GLOBAL_LOG(WARN, "Unrecognized audio control descriptor (type %u) @ offset %zu\n", |
| hdr->bDescriptorSubtype, iter.offset()); |
| return nullptr; |
| } |
| } |
| |
| zx_status_t AudioUnit::CtrlReq(const usb_protocol_t& proto, |
| uint8_t code, uint16_t val, uint16_t len, void* data) { |
| if (!len || (data == nullptr)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // For audio class specific control codes, get control codes all have their MSB set. |
| uint8_t req_type = (code & 0x80) |
| ? USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE |
| : USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE; |
| |
| // TODO(johngro) : See about fixing the use of const in the C API for the |
| // USB bus protocol. There is no good reason why a usb_protocol structure |
| // should need to be mutable when performing operations such as usb_control. |
| auto proto_ptr = const_cast<usb_protocol_t*>(&proto); |
| |
| // TODO(johngro) : Do better than this if we can. |
| // |
| // None of these control transactions should every take any |
| // significant amount of time, and if the turn out to do so, then we really |
| // need to find a way to use the USB bus driver in an asynchronous fashion. |
| // Even 500 mSec is just *way* too long to ever block a driver thread, for |
| // pretty much any reason. Right now, this timeout is here only for safety |
| // reasons; it would be better to timeout after a half of a second then to |
| // block the entire USB device forever. |
| // |
| // It is tempting to simply kill the driver/process if we ever timeout on |
| // one of these operations, but at time this code was written, that would |
| // kill the entire USB bus driver. So, for now, we eat the timeout and rely |
| // on the code above us taking some action to shut this device down. |
| constexpr uint64_t kRelativeTimeout = ZX_MSEC(500); |
| size_t done = 0; |
| zx_status_t status; |
| if ((req_type & USB_DIR_MASK) == USB_DIR_OUT) { |
| status = usb_control_out(proto_ptr, req_type, code, val, index(), |
| zx_deadline_after(kRelativeTimeout), data, len); |
| done = len; |
| } else { |
| status = usb_control_in(proto_ptr, req_type, code, val, index(), |
| zx_deadline_after(kRelativeTimeout), data, len, &done); |
| } |
| if ((status == ZX_OK) && (done != len)) { |
| status = ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| |
| if (status != ZX_OK) { |
| GLOBAL_LOG(WARN, |
| "WARNING: Audio control request failed! Unit (%s:id %u), " |
| "code 0x%02x val 0x%04hx, ndx 0x%04x [bytes expected %u, got %zu] (status %d)\n", |
| type_name(), id(), code, val, index(), len, done, status); |
| } |
| |
| return status; |
| } |
| |
| fbl::RefPtr<InputTerminal> InputTerminal::Create(const DescriptorListMemory::Iterator& iter, |
| uint8_t iid) { |
| auto hdr = iter.hdr_as<usb_audio_ac_input_terminal_desc>(); |
| |
| if (hdr == nullptr) { |
| GLOBAL_LOG(WARN, "InputTerminal header appears invalid @ offset %zu\n", iter.offset()); |
| return nullptr; |
| } |
| |
| // TODO(johngro): additional sanity checking and pre-processing goes here. |
| |
| fbl::AllocChecker ac; |
| auto ret = fbl::AdoptRef(new (&ac) InputTerminal(iter.desc_list(), hdr, iid)); |
| return ac.check() ? ret : nullptr; |
| } |
| |
| fbl::RefPtr<OutputTerminal> OutputTerminal::Create(const DescriptorListMemory::Iterator& iter, |
| uint8_t iid) { |
| auto hdr = iter.hdr_as<usb_audio_ac_output_terminal_desc>(); |
| |
| if (hdr == nullptr) { |
| GLOBAL_LOG(WARN, "OutputTerminal header appears invalid @ offset %zu\n", iter.offset()); |
| return nullptr; |
| } |
| |
| // TODO(johngro): additional sanity checking and pre-processing goes here. |
| |
| fbl::AllocChecker ac; |
| auto ret = fbl::AdoptRef(new (&ac) OutputTerminal(iter.desc_list(), hdr, iid)); |
| return ac.check() ? ret : nullptr; |
| } |
| |
| fbl::RefPtr<MixerUnit> MixerUnit::Create(const DescriptorListMemory::Iterator& iter, uint8_t iid) { |
| // Find the size of each of the inlined variable length arrays in this |
| // structure, finding the locations of the constant headers in the process. |
| // If anything does not look right, complain and move on. |
| auto hdr0 = iter.hdr_as<usb_audio_ac_mixer_unit_desc_0>(); |
| if (hdr0 != nullptr) { |
| size_t off = sizeof(*hdr0) + hdr0->bNrInPins; |
| auto hdr1 = offset_ptr<usb_audio_ac_mixer_unit_desc_1>(hdr0, off); |
| if (hdr1 != nullptr) { |
| // Determining the size of bmControls is a bit of a pain. To do so, |
| // we need to know 'n', which is the sum the number of channels |
| // across all of the input pins, and 'm' (which should be |
| // hdr1->bNrChannels). At this stage of parsing our unit/terminal |
| // graph, we may not have access to all of the sources which might |
| // feed into the calculation of 'n'. Because of this, for now, just |
| // assume that the size of bmControls (in bytes) is equal to the |
| // space remaining in the descriptor, demanding that this be at |
| // least equal to a single byte (if it was zero, it means that we |
| // either have no input or no output channels, neither of which |
| // makes sense). |
| if (sizeof(usb_audio_ac_mixer_unit_desc_2) < hdr0->bLength) { |
| size_t off2 = hdr0->bLength - sizeof(usb_audio_ac_mixer_unit_desc_2); |
| if (off2 > off) { |
| auto hdr2 = offset_ptr<usb_audio_ac_mixer_unit_desc_2>(hdr0, off2); |
| ZX_DEBUG_ASSERT(hdr2 != nullptr); |
| |
| // TODO(johngro): additional sanity checking and pre-processing goes here. |
| fbl::AllocChecker ac; |
| auto ret = fbl::AdoptRef( |
| new (&ac) MixerUnit(iter.desc_list(), hdr0, hdr1, hdr2, iid)); |
| return ac.check() ? ret : nullptr; |
| } |
| } |
| } |
| } |
| |
| GLOBAL_LOG(WARN, "MixerUnit header appears invalid @ offset %zu\n", iter.offset()); |
| return nullptr; |
| } |
| |
| fbl::RefPtr<SelectorUnit> SelectorUnit::Create(const DescriptorListMemory::Iterator& iter, |
| uint8_t iid) { |
| // Find the size of each of the inlined variable length arrays in this |
| // structure, finding the locations of the constant headers in the process. |
| // If anything does not look right, complain and move on. |
| auto hdr0 = iter.hdr_as<usb_audio_ac_selector_unit_desc_0>(); |
| if (hdr0 != nullptr) { |
| size_t off = sizeof(*hdr0) + hdr0->bNrInPins; |
| auto hdr1 = offset_ptr<usb_audio_ac_selector_unit_desc_1>(hdr0, off); |
| if (hdr1 != nullptr) { |
| // TODO(johngro): additional sanity checking and pre-processing goes here. |
| fbl::AllocChecker ac; |
| auto ret = fbl::AdoptRef(new (&ac) SelectorUnit(iter.desc_list(), hdr0, hdr1, iid)); |
| return ac.check() ? ret : nullptr; |
| } |
| } |
| |
| GLOBAL_LOG(WARN, "SelectorUnit header appears invalid @ offset %zu\n", iter.offset()); |
| return nullptr; |
| } |
| |
| zx_status_t SelectorUnit::Select(const usb_protocol_t& proto, uint8_t upstream_id) { |
| // Section 5.2.2.3.3. defines the selector index as being 1s indexed, so |
| // zero is an easy to use "invalid" value. |
| uint8_t ndx = 0; |
| |
| // Find the appropriate index or return an error trying. |
| uint32_t cnt = source_count(); |
| for (uint32_t i = 0; i < cnt; ++i) { |
| if (upstream_id == source_id(i)) { |
| ndx = static_cast<uint8_t>(i + 1); |
| break; |
| } |
| } |
| |
| if (!ndx) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| // Now go ahead and set the value; |
| return CtrlReq(proto, USB_AUDIO_SET_CUR, 0, &ndx); |
| } |
| |
| fbl::RefPtr<FeatureUnit> FeatureUnit::Create(const DescriptorListMemory::Iterator& iter, |
| uint8_t iid) { |
| // Find the size of each of the inlined variable length arrays in this |
| // structure, finding the locations of the constant headers in the process. |
| // If anything does not look right, complain and move on. |
| auto hdr0 = iter.hdr_as<usb_audio_ac_feature_unit_desc_0>(); |
| if (hdr0 != nullptr) { |
| // The exact expected size of the Controls bitmap depends on the number |
| // of channels feeding this feature unit. This information is not |
| // contained in the feature unit itself, but instead exists upstream of |
| // the unit in first unit/terminal which contains a channel cluster |
| // element. At this point in parsing, we have not discovered all of the |
| // units present in the audio control interface yet, so we cannot trace |
| // upstream to sanity check the size of this field. |
| // |
| // For now, we perform the most basic check we can by assuming that the |
| // size of the Controls bitmap must be... |
| // |
| // 1) Non-zero, and... |
| // 2) Divisible by bControlSize, which must also be non-zero. |
| // |
| // In the future, more stringent checks can be applied during Probe. |
| constexpr size_t kHdrOverhead = sizeof(*hdr0) + sizeof(usb_audio_ac_feature_unit_desc_1); |
| size_t ctrl_array_bytes = hdr0->bLength - kHdrOverhead; |
| if ((kHdrOverhead < hdr0->bLength) && |
| (hdr0->bControlSize > 0) && |
| (!(ctrl_array_bytes % hdr0->bControlSize))) { |
| // Allocate memory for our Features capability array. |
| fbl::AllocChecker ac; |
| size_t feat_len = ctrl_array_bytes / hdr0->bControlSize; |
| auto feat_mem = fbl::unique_ptr<Features[]>(new (&ac) Features[feat_len]); |
| |
| if (ac.check()) { |
| // We just made sure that this fits, there should be no way for us |
| // to have run out of data. |
| size_t off = hdr0->bLength - sizeof(usb_audio_ac_feature_unit_desc_1); |
| auto hdr1 = offset_ptr<usb_audio_ac_feature_unit_desc_1>(hdr0, off); |
| ZX_DEBUG_ASSERT(hdr1 != nullptr); |
| |
| auto ret = fbl::AdoptRef(new (&ac) FeatureUnit(iter.desc_list(), |
| hdr0, hdr1, |
| std::move(feat_mem), feat_len, |
| iid)); |
| if (ac.check()) { |
| return ret; |
| } |
| } |
| |
| GLOBAL_LOG(WARN, "Out of memory attempting to allocate FeatureUnit @ offset %zu\n", |
| iter.offset()); |
| return nullptr; |
| } |
| } |
| |
| GLOBAL_LOG(WARN, "FeatureUnit header appears invalid @ offset %zu\n", iter.offset()); |
| return nullptr; |
| } |
| |
| zx_status_t FeatureUnit::Probe(const usb_protocol_t& proto) { |
| zx_status_t res; |
| |
| // Start by going over our channel feature bitmap and extracting the actual |
| // feature bits for each channel. Right now, we demand that the size of |
| // each entry be (at most) a 32 bit integer. The USB Audio 1.0 Spec only |
| // defines bits up to bit 9, so we really only understand how to handle up |
| // to there. If we cannot fit each of the bitmap entries in a 32-bit |
| // integer, then the USB audio spec has come a long way and someone should |
| // come back here and update this driver. |
| ZX_DEBUG_ASSERT(feature_desc()->bControlSize != 0); // Create should have checked this already |
| if (feature_desc()->bControlSize > sizeof(uint32_t)) { |
| GLOBAL_LOG(WARN, "FeatureUnit id %u has unsupported bControlSize > %zu (%u)\n", |
| id(), sizeof(uint32_t), feature_desc()->bControlSize); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| for (size_t i = 0; i < features_.size(); ++i) { |
| auto& f = features_[i]; |
| f.supported_ = 0; |
| for (uint8_t j = 0; j <feature_desc()->bControlSize; ++j) { |
| uint32_t bits = feature_desc()->bmaControls[(i * feature_desc()->bControlSize) + j]; |
| f.supported_ |= bits << (8 * j); |
| } |
| } |
| |
| // Now, go over our array of features and compute both the union and the |
| // intersection of the features for all of the individual channels. |
| uint32_t ch_feat_union = 0; |
| uint32_t ch_feat_intersection = 0; |
| if (features_.size() > 1) { |
| ch_feat_union = features_[1].supported_; |
| ch_feat_intersection = features_[1].supported_; |
| for (size_t i = 2; i < features_.size(); ++i) { |
| ch_feat_union |= features_[i].supported_; |
| ch_feat_intersection &= features_[i].supported_; |
| |
| } |
| } |
| |
| // Next check for a set of uniformity requirements. In particular, there |
| // are three types of controls (mute, AGC, and volume/gain) that we want to |
| // enforce these guarantees for. Specifically, |
| // |
| // 1) We can handle these controls at the master level, or the individual |
| // channel level, but we don't really know what to do if the controls |
| // exist at both levels. |
| // 2) If we are controlling these things at the individual control level, we |
| // are doing so in a way which mimics a master control knob only. So, if |
| // we have these controls at the per-channel level, it is important that |
| // they be they be identical for each of the individual channels. |
| constexpr uint32_t kUniformControls = USB_AUDIO_FU_BMA_MUTE |
| | USB_AUDIO_FU_BMA_VOLUME |
| | USB_AUDIO_FU_BMA_AUTOMATIC_GAIN; |
| ZX_DEBUG_ASSERT(features_.size() > 0); // Create should have checked this already |
| if (((features_[0].supported_ & ch_feat_union & kUniformControls) != 0) || // Check #1 |
| ((ch_feat_union ^ ch_feat_intersection) & kUniformControls)) { // Check #2 |
| GLOBAL_LOG(WARN, |
| "FeatureUnit id %u has unsupported non-uniform gain controls. " |
| "Master 0x%08x, Channel Union 0x%08x, Channel Intersection 0x%08x.\n", |
| id(), features_[0].supported_, ch_feat_union, ch_feat_intersection); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // Stash bitmaps of controls we care about for later. |
| master_feat_ = features_[0].supported_ & kUniformControls; |
| ch_feat_ = ch_feat_intersection & kUniformControls; |
| |
| // If this feature unit has volume control, fetch and sanity check the |
| // min/max/res of all of the channels. |
| if (has_vol()) { |
| // Go over each of the volume controls and cache the min/max/res values. |
| for (size_t i = 0; i < features_.size(); ++i) { |
| auto& f = features_[i]; |
| |
| if (!f.has_vol()) { |
| continue; |
| } |
| |
| uint8_t ch = static_cast<uint8_t>(i); |
| |
| res = FeatCtrlReq(proto, USB_AUDIO_GET_MIN, USB_AUDIO_VOLUME_CONTROL, ch, &f.vol_min_); |
| if (res != ZX_OK) { |
| return res; |
| } |
| |
| res = FeatCtrlReq(proto, USB_AUDIO_GET_MAX, USB_AUDIO_VOLUME_CONTROL, ch, &f.vol_max_); |
| if (res != ZX_OK) { |
| return res; |
| } |
| |
| res = FeatCtrlReq(proto, USB_AUDIO_GET_RES, USB_AUDIO_VOLUME_CONTROL, ch, &f.vol_res_); |
| if (res != ZX_OK) { |
| return res; |
| } |
| } |
| |
| // If volume control is done at the per-channel level, make sure that all of |
| // the channels support the same range. Otherwise, our volume control range |
| // is equal to the master channel's range. |
| if (features_[0].has_vol()) { |
| vol_min_ = features_[0].vol_min_; |
| vol_max_ = features_[0].vol_max_; |
| vol_res_ = features_[0].vol_res_; |
| } else { |
| vol_min_ = features_[1].vol_min_; |
| vol_max_ = features_[1].vol_max_; |
| vol_res_ = features_[1].vol_res_; |
| for (size_t i = 2; i < features_.size(); ++i) { |
| if ((vol_min_ != features_[i].vol_min_) || |
| (vol_max_ != features_[i].vol_max_) || |
| (vol_res_ != features_[i].vol_res_)) { |
| GLOBAL_LOG(WARN, |
| "FeatureUnit id %u has unsupported non-uniform gain controls. " |
| "Channel %zu's gain range [%hd, %hd, %hd] does not match Channel 1's " |
| "range [%hd, %hd, %hd]\n", |
| id(), i, |
| vol_min_, vol_max_, vol_res_, |
| features_[i].vol_min_, features_[i].vol_max_, features_[i].vol_res_); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| } |
| |
| if (vol_min_ > vol_max_) { |
| GLOBAL_LOG(WARN, "FeatureUnit id %u has invalid volume range [%hd, %hd]\n", |
| id(), vol_min_, vol_max_); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| if (!vol_res_) { |
| GLOBAL_LOG(WARN, "FeatureUnit id %u has invalid volume res %hd\n", id(), vol_res_); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // Fetch the current volume setting from the appropriate source, then |
| // make certain that all channels are set to the same if there is no |
| // master control knob. |
| bool master_control = (master_feat_ & USB_AUDIO_FU_BMA_VOLUME); |
| uint8_t ch = master_control ? 0 : 1; |
| res = FeatCtrlReq(proto, USB_AUDIO_GET_CUR, USB_AUDIO_VOLUME_CONTROL, ch, &vol_cur_); |
| if (res != ZX_OK) { |
| return res; |
| } |
| |
| if (!master_control) { |
| SetFeature(proto, USB_AUDIO_VOLUME_CONTROL, vol_cur_); |
| } |
| } |
| |
| // If we have mute controls, figure out the current setting. |
| if (has_mute()) { |
| res = FeatCtrlReq(proto, USB_AUDIO_GET_CUR, USB_AUDIO_MUTE_CONTROL, 0, &mute_cur_); |
| if (res != ZX_OK) { |
| return res; |
| } |
| } |
| |
| // If we have agc controls, figure out the current setting. |
| if (has_agc()) { |
| res = FeatCtrlReq(proto, USB_AUDIO_GET_CUR, USB_AUDIO_AUTOMATIC_GAIN_CONTROL, 0, &agc_cur_); |
| if (res != ZX_OK) { |
| return res; |
| } |
| } |
| |
| // Dump some diags info if TRACE level logging is enabled. |
| if (has_vol()) { |
| GLOBAL_LOG(TRACE, |
| "FeatureUnit id %u: can%s mute, can%s AGC, gain [%.3f, %.3f: step %.3f] dB\n", |
| id(), |
| has_mute() ? "" : "not", |
| has_agc() ? "" : "not", |
| vol_min_db(), vol_max_db(), vol_res_db()); |
| } else { |
| GLOBAL_LOG(TRACE, "FeatureUnit id %u: can%s mute, can%s AGC, and has fixed gain\n", |
| id(), has_mute() ? "" : "not", has_agc() ? "" : "not"); |
| } |
| |
| // All done! Declare success and get out. |
| return ZX_OK; |
| }; |
| |
| float FeatureUnit::SetVol(const usb_protocol_t& proto, float db) { |
| // If we have no volume control, then our gain is fixed at 0.0 dB no matter |
| // what the user asks for. |
| if (!has_vol()) { |
| return 0.0; |
| } |
| |
| // Convert to our target value. Start by converting to ticks. |
| float ticks_float = db / kDbPerTick; |
| |
| // Now snap to the closest allowed tick based on our resolution. |
| ticks_float = roundf(ticks_float / vol_res_) * vol_res_; |
| |
| // Now clamp to the acceptable min/max range and convert to integer ticks. |
| vol_cur_ = static_cast<int16_t>(fbl::clamp<float>(ticks_float, vol_min_, vol_max_)); |
| |
| // Finally apply the setting. If we have no explicit mute control, and we |
| // are currently supposed to be muted, skip this step. We are using the |
| // volume control to simulate mute to the best of our abilities; we will |
| // restore vol_cur_ when the unit finally becomes un-muted. |
| if (!(mute_cur_ && !has_mute())) { |
| SetFeature(proto, USB_AUDIO_VOLUME_CONTROL, vol_cur_); |
| } |
| |
| return vol_cur_ * kDbPerTick; |
| } |
| |
| bool FeatureUnit::SetMute(const usb_protocol_t& proto, bool mute) { |
| mute_cur_ = mute; |
| |
| // If we have an explicit mute control, use that. Otherwise, do the best we |
| // can using the volume control (if present). |
| if (has_mute()) { |
| SetFeature(proto, USB_AUDIO_MUTE_CONTROL, mute_cur_); |
| } else { |
| // Section 5.2.2.4.3.2 of the USB Audio 1.0 spec defines int16::min as |
| // -inf dB for the purpose of setting gain. |
| int16_t tgt = mute ? std::numeric_limits<int16_t>::min() : vol_cur_; |
| SetFeature(proto, USB_AUDIO_VOLUME_CONTROL, tgt); |
| } |
| |
| return !!mute_cur_; |
| } |
| |
| bool FeatureUnit::SetAgc(const usb_protocol_t& proto, bool agc) { |
| if (has_agc()) { |
| agc_cur_ = agc; |
| SetFeature(proto, USB_AUDIO_AUTOMATIC_GAIN_CONTROL, static_cast<uint8_t>(agc)); |
| } |
| return !!agc_cur_; |
| } |
| |
| fbl::RefPtr<ProcessingUnit> ProcessingUnit::Create(const DescriptorListMemory::Iterator& iter, |
| uint8_t iid) { |
| // Find the size of each of the inlined variable length arrays in this |
| // structure, finding the locations of the constant headers in the process. |
| // If anything does not look right, complain and move on. |
| auto hdr0 = iter.hdr_as<usb_audio_ac_processing_unit_desc_0>(); |
| if (hdr0 != nullptr) { |
| size_t off = sizeof(*hdr0) + hdr0->bNrInPins; |
| auto hdr1 = offset_ptr<usb_audio_ac_processing_unit_desc_1>(hdr0, off); |
| if (hdr1 != nullptr) { |
| off += sizeof(*hdr1) + hdr1->bControlSize; |
| auto hdr2 = offset_ptr<usb_audio_ac_processing_unit_desc_2>(hdr0, off); |
| |
| // TODO(johngro): additional sanity checking and pre-processing goes here. |
| // |
| // Note: Processing units actually come in their own pre-defined |
| // sub-flavors (determined by hdr0->wProcessType). Instead of |
| // lumping them all together into one ProcessingUnit class, we |
| // should probably take the time to break them down into the various |
| // sub-flavors, at which point in time, the big validation switch |
| // statement would go somewhere in here. |
| // |
| // For now, however, we do not expect to have any need to control |
| // processing units. If we ever encounter one, we really only want |
| // to understand the size of the baSourceID array so that we can |
| // successfully walk the graph when attempting to build input/output |
| // stream paths. |
| fbl::AllocChecker ac; |
| auto ret = fbl::AdoptRef( |
| new (&ac) ProcessingUnit(iter.desc_list(), hdr0, hdr1, hdr2, iid)); |
| return ac.check() ? ret : nullptr; |
| } |
| } |
| |
| GLOBAL_LOG(WARN, "ProcessingUnit header appears invalid @ offset %zu\n", iter.offset()); |
| return nullptr; |
| } |
| |
| fbl::RefPtr<ExtensionUnit> ExtensionUnit::Create(const DescriptorListMemory::Iterator& iter, |
| uint8_t iid) { |
| // Find the size of each of the inlined variable length arrays in this |
| // structure, finding the locations of the constant headers in the process. |
| // If anything does not look right, complain and move on. |
| auto hdr0 = iter.hdr_as<usb_audio_ac_extension_unit_desc_0>(); |
| if (hdr0 != nullptr) { |
| size_t off = sizeof(*hdr0) + hdr0->bNrInPins; |
| auto hdr1 = offset_ptr<usb_audio_ac_extension_unit_desc_1>(hdr0, off); |
| if (hdr1 != nullptr) { |
| off += sizeof(*hdr1) + hdr1->bControlSize; |
| auto hdr2 = offset_ptr<usb_audio_ac_extension_unit_desc_2>(hdr0, off); |
| |
| // TODO(johngro): additional sanity checking and pre-processing goes here. |
| fbl::AllocChecker ac; |
| auto ret = fbl::AdoptRef( |
| new (&ac) ExtensionUnit(iter.desc_list(), hdr0, hdr1, hdr2, iid)); |
| return ac.check() ? ret : nullptr; |
| } |
| } |
| |
| GLOBAL_LOG(WARN, "ExtensionUnit header appears invalid @ offset %zu\n", iter.offset()); |
| return nullptr; |
| } |
| |
| } // namespace usb |
| } // namespace audio |