| // Copyright 2017 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 <ddk/device.h> |
| #include <zircon/device/audio.h> |
| #include <zircon/device/usb.h> |
| #include <zircon/hw/usb-audio.h> |
| #include <zircon/process.h> |
| #include <zircon/types.h> |
| #include <zx/vmar.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/limits.h> |
| #include <string.h> |
| |
| #include <dispatcher-pool/dispatcher-thread-pool.h> |
| |
| #include "debug-logging.h" |
| #include "usb-audio.h" |
| #include "usb-audio-stream.h" |
| |
| namespace audio { |
| namespace usb { |
| |
| static constexpr uint32_t MAX_OUTSTANDING_REQ = 8; |
| |
| static constexpr uint32_t ExtractSampleRate(const usb_audio_ac_samp_freq& sr) { |
| return static_cast<uint32_t>(sr.freq[0]) |
| | (static_cast<uint32_t>(sr.freq[1]) << 8) |
| | (static_cast<uint32_t>(sr.freq[2]) << 16); |
| } |
| |
| UsbAudioStream::~UsbAudioStream() { |
| // We destructing. All of our requests should be sitting in the free list. |
| ZX_DEBUG_ASSERT(allocated_req_cnt_ == free_req_cnt_); |
| |
| while (!list_is_empty(&free_req_)) { |
| usb_request_release(list_remove_head_type(&free_req_, usb_request_t, node)); |
| } |
| } |
| |
| // static |
| zx_status_t UsbAudioStream::Create(bool is_input, |
| zx_device_t* parent, |
| usb_protocol_t* usb, |
| int index, |
| usb_interface_descriptor_t* usb_interface, |
| usb_endpoint_descriptor_t* usb_endpoint, |
| usb_audio_ac_format_type_i_desc* format_desc) { |
| auto domain = dispatcher::ExecutionDomain::Create(); |
| if (domain == nullptr) { return ZX_ERR_NO_MEMORY; } |
| |
| auto stream = fbl::AdoptRef( |
| new UsbAudioStream(parent, usb, is_input, index, fbl::move(domain))); |
| char name[64]; |
| snprintf(name, sizeof(name), "usb-audio-%s-%03d", is_input ? "input" : "output", index); |
| |
| zx_status_t res = stream->Bind(name, usb_interface, usb_endpoint, format_desc); |
| if (res == ZX_OK) { |
| // If bind/setup has succeeded, then the devmgr now controls our |
| // lifecycle and will release us when finished with us. Let go of our |
| // local reference. |
| // |
| // TODO(johngro) : outright leaking this reference feels wrong. We |
| // should bind this to the devmgr cookie somehow instead. |
| __UNUSED auto leak = stream.leak_ref(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| void UsbAudioStream::PrintDebugPrefix() const { |
| printf("usb-audio-%s-%03d: ", is_input() ? "input" : "output", usb_index_); |
| } |
| |
| zx_status_t UsbAudioStream::Bind(const char* devname, |
| usb_interface_descriptor_t* usb_interface, |
| usb_endpoint_descriptor_t* usb_endpoint, |
| usb_audio_ac_format_type_i_desc* format_desc) { |
| // TODO(johngro) : parse all of the supported formats and widgets present in |
| // this audio device. Support things like aynch plug notification, format |
| // selection, gain control, sidetone, etc... |
| if (!usb_interface || !usb_endpoint || !format_desc) |
| return ZX_ERR_INVALID_ARGS; |
| |
| ZX_DEBUG_ASSERT(!supported_formats_.size()); |
| zx_status_t res = AddFormats(*format_desc, &supported_formats_); |
| if (res != ZX_OK) { |
| LOG("Failed to parse format descriptor (res %d)\n", res); |
| return res; |
| } |
| |
| // TODO(johngro): Do this differently when we have the ability to queue io |
| // transactions to a USB isochronous endpoint and can have the bus driver |
| // DMA directly from the ring buffer we have set up with our user. |
| { |
| fbl::AutoLock req_lock(&req_lock_); |
| |
| list_initialize(&free_req_); |
| free_req_cnt_ = 0; |
| allocated_req_cnt_ = 0; |
| max_req_size_ = usb_ep_max_packet(usb_endpoint); |
| |
| for (uint32_t i = 0; i < MAX_OUTSTANDING_REQ; ++i) { |
| usb_request_t* req; |
| zx_status_t status = usb_request_alloc(&req, max_req_size_, |
| usb_endpoint->bEndpointAddress); |
| if (status != ZX_OK) { |
| LOG("Failed to allocate usb request %u/%u (size %u): %d\n", |
| i + 1, MAX_OUTSTANDING_REQ, max_req_size_, status); |
| return status; |
| } |
| |
| req->cookie = this; |
| req->complete_cb = [](usb_request_t* req, void* cookie) -> void { |
| ZX_DEBUG_ASSERT(cookie != nullptr); |
| reinterpret_cast<UsbAudioStream*>(cookie)->RequestComplete(req); |
| }; |
| |
| list_add_head(&free_req_, &req->node); |
| ++free_req_cnt_; |
| ++allocated_req_cnt_; |
| } |
| } |
| |
| iface_num_ = usb_interface->bInterfaceNumber; |
| alt_setting_ = usb_interface->bAlternateSetting; |
| usb_ep_addr_ = usb_endpoint->bEndpointAddress; |
| |
| return UsbAudioStreamBase::DdkAdd(devname); |
| } |
| |
| void UsbAudioStream::ReleaseRingBufferLocked() { |
| if (ring_buffer_virt_ != nullptr) { |
| ZX_DEBUG_ASSERT(ring_buffer_size_ != 0); |
| zx::vmar::root_self().unmap(reinterpret_cast<uintptr_t>(ring_buffer_virt_), |
| ring_buffer_size_); |
| ring_buffer_virt_ = nullptr; |
| ring_buffer_size_ = 0; |
| } |
| ring_buffer_vmo_.reset(); |
| } |
| |
| zx_status_t UsbAudioStream::AddFormats( |
| const usb_audio_ac_format_type_i_desc& format_desc, |
| fbl::Vector<audio_stream_format_range_t>* supported_formats) { |
| if (!supported_formats) |
| return ZX_ERR_INVALID_ARGS; |
| |
| // Record the min/max number of channels. |
| audio_stream_format_range_t range; |
| range.min_channels = format_desc.bNrChannels; |
| range.max_channels = format_desc.bNrChannels; |
| |
| // Encode the bit resolution and subframe size from the audio descriptor as |
| // an audio device driver audio_sample_format_t |
| // |
| // TODO(johngro) : figure out how format descriptors are used to indicate |
| // 32-bit floating point, uLaw/aLaw compression, or 8 bit unsigned. In |
| // theory, there should be a wFormatTag field somewhere in the structure |
| // which indicates this, but their does not appear to be one (currently). |
| // If it follows the pattern of a Type II MPEG audio format, it may be that |
| // bDescriptorSubtype is supposed to be USB_AUDIO_AS_FORMAT_SPECIFIC which |
| // will then be followed by a 2 byte wFormatTag instead of a single byte |
| // bFormatType. |
| switch (format_desc.bBitResolution) { |
| case 8: |
| case 16: |
| case 32: { |
| if (format_desc.bSubFrameSize != (format_desc.bBitResolution >> 3)) { |
| LOG("Unsupported format. Subframe size (%u bytes) does not " |
| "match Bit Res (%u bits)\n", |
| format_desc.bSubFrameSize, |
| format_desc.bBitResolution); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| switch (format_desc.bBitResolution) { |
| 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 ((format_desc.bSubFrameSize != 3) && (format_desc.bSubFrameSize != 4)) { |
| LOG("Unsupported format. %u-bit audio must be packed into a 3 " |
| "or 4 byte subframe (Subframe size %u)\n", |
| format_desc.bBitResolution, |
| format_desc.bSubFrameSize); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| switch (format_desc.bBitResolution) { |
| case 20: range.sample_formats = (format_desc.bSubFrameSize == 3) |
| ? AUDIO_SAMPLE_FORMAT_20BIT_PACKED |
| : AUDIO_SAMPLE_FORMAT_20BIT_IN32; |
| case 24: range.sample_formats = (format_desc.bSubFrameSize == 3) |
| ? AUDIO_SAMPLE_FORMAT_24BIT_PACKED |
| : AUDIO_SAMPLE_FORMAT_24BIT_IN32; |
| } |
| } break; |
| |
| default: |
| LOG("Unsupported format. Bad Bit Res (%u bits)\n", format_desc.bBitResolution); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // If bSamFreqType is 0, it means that we have a continuous range of |
| // sampling frequencies available. Otherwise, we have a discrete number and |
| // bSamFreqType specifies how many. |
| // |
| // See Universal Serial Bus Device Class Definition for Audio Data Formats |
| // Release 1.0 Tables 2-2 and 2-3. |
| if (format_desc.bSamFreqType) { |
| fbl::AllocChecker ac; |
| supported_formats->reserve(format_desc.bSamFreqType, &ac); |
| if (!ac.check()) { |
| LOG("Out of memory attempting to reserve %u format ranges\n", |
| format_desc.bSamFreqType); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| // TODO(johngro) : This 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. |
| for (uint32_t i = 0; i < format_desc.bSamFreqType; ++i) { |
| uint32_t rate = ExtractSampleRate(format_desc.tSamFreq[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; |
| } |
| |
| supported_formats->push_back(range); |
| } |
| fixed_sample_rate_ = supported_formats->size() <= 1; |
| } else { |
| fbl::AllocChecker ac; |
| supported_formats->reserve(1, &ac); |
| if (!ac.check()) { |
| LOG("Out of memory attempting to reserve 1 format range\n"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| range.min_frames_per_second = ExtractSampleRate(format_desc.tSamFreq[0]); |
| range.max_frames_per_second = ExtractSampleRate(format_desc.tSamFreq[1]); |
| range.flags = ASF_RANGE_FLAG_FPS_CONTINUOUS; |
| |
| supported_formats->push_back(range); |
| fixed_sample_rate_ = false; |
| } |
| |
| return ZX_OK; |
| } |
| |
| void UsbAudioStream::DdkUnbind() { |
| // Close all of our client event sources if we have not already. |
| default_domain_->Deactivate(); |
| |
| // Unpublish our device node. |
| DdkRemove(); |
| } |
| |
| void UsbAudioStream::DdkRelease() { |
| // Reclaim our reference from the driver framework and let it go out of |
| // scope. If this is our last reference (it should be), we will destruct |
| // immediately afterwards. |
| auto thiz = fbl::internal::MakeRefPtrNoAdopt(this); |
| } |
| |
| zx_status_t UsbAudioStream::DdkIoctl(uint32_t op, |
| const void* in_buf, size_t in_len, |
| void* out_buf, size_t out_len, size_t* out_actual) { |
| // The only IOCTL we support is get channel. |
| if (op != AUDIO_IOCTL_GET_CHANNEL) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| if ((out_buf == nullptr) || |
| (out_actual == nullptr) || |
| (out_len != sizeof(zx_handle_t))) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| fbl::AutoLock lock(&lock_); |
| |
| // Attempt to allocate a new driver channel and bind it to us. If we don't |
| // already have an stream_channel_, flag this channel is the privileged |
| // connection (The connection which is allowed to do things like change |
| // formats). |
| bool privileged = (stream_channel_ == nullptr); |
| auto channel = dispatcher::Channel::Create(); |
| if (channel == nullptr) |
| return ZX_ERR_NO_MEMORY; |
| |
| dispatcher::Channel::ProcessHandler phandler( |
| [stream = fbl::WrapRefPtr(this), privileged](dispatcher::Channel* channel) -> zx_status_t { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->default_domain_); |
| return stream->ProcessStreamChannel(channel, privileged); |
| }); |
| |
| dispatcher::Channel::ChannelClosedHandler chandler; |
| if (privileged) { |
| chandler = dispatcher::Channel::ChannelClosedHandler( |
| [stream = fbl::WrapRefPtr(this)](const dispatcher::Channel* channel) -> void { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->default_domain_); |
| stream->DeactivateStreamChannel(channel); |
| }); |
| } |
| |
| zx::channel client_endpoint; |
| zx_status_t res = channel->Activate(&client_endpoint, |
| default_domain_, |
| fbl::move(phandler), |
| fbl::move(chandler)); |
| if (res == ZX_OK) { |
| if (privileged) { |
| ZX_DEBUG_ASSERT(stream_channel_ == nullptr); |
| stream_channel_ = channel; |
| } |
| |
| *(reinterpret_cast<zx_handle_t*>(out_buf)) = client_endpoint.release(); |
| *out_actual = sizeof(zx_handle_t); |
| } |
| |
| return res; |
| } |
| |
| #define HREQ(_cmd, _payload, _handler, _allow_noack, ...) \ |
| case _cmd: \ |
| if (req_size != sizeof(req._payload)) { \ |
| DEBUG_LOG("Bad " #_cmd \ |
| " response length (%u != %zu)\n", \ |
| req_size, sizeof(req._payload)); \ |
| return ZX_ERR_INVALID_ARGS; \ |
| } \ |
| if (!_allow_noack && (req.hdr.cmd & AUDIO_FLAG_NO_ACK)) { \ |
| DEBUG_LOG("NO_ACK flag not allowed for " #_cmd "\n"); \ |
| return ZX_ERR_INVALID_ARGS; \ |
| } \ |
| return _handler(channel, req._payload, ##__VA_ARGS__); |
| zx_status_t UsbAudioStream::ProcessStreamChannel(dispatcher::Channel* channel, bool privileged) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| fbl::AutoLock lock(&lock_); |
| |
| // TODO(johngro) : Factor all of this behavior around accepting channels and |
| // dispatching audio driver requests into some form of utility class so it |
| // can be shared with the IntelHDA codec implementations as well. |
| union { |
| audio_proto::CmdHdr hdr; |
| audio_proto::StreamGetFmtsReq get_formats; |
| audio_proto::StreamSetFmtReq set_format; |
| audio_proto::GetGainReq get_gain; |
| audio_proto::SetGainReq set_gain; |
| audio_proto::PlugDetectReq plug_detect; |
| // TODO(johngro) : add more commands here |
| } req; |
| |
| static_assert(sizeof(req) <= 256, |
| "Request buffer is getting to be too large to hold on the stack!"); |
| |
| uint32_t req_size; |
| zx_status_t res = channel->Read(&req, sizeof(req), &req_size); |
| if (res != ZX_OK) |
| return res; |
| |
| if ((req_size < sizeof(req.hdr) || |
| (req.hdr.transaction_id == AUDIO_INVALID_TRANSACTION_ID))) |
| return ZX_ERR_INVALID_ARGS; |
| |
| // Strip the NO_ACK flag from the request before selecting the dispatch target. |
| auto cmd = static_cast<audio_proto::Cmd>(req.hdr.cmd & ~AUDIO_FLAG_NO_ACK); |
| switch (cmd) { |
| HREQ(AUDIO_STREAM_CMD_GET_FORMATS, get_formats, OnGetStreamFormatsLocked, false); |
| HREQ(AUDIO_STREAM_CMD_SET_FORMAT, set_format, OnSetStreamFormatLocked, false, privileged); |
| HREQ(AUDIO_STREAM_CMD_GET_GAIN, get_gain, OnGetGainLocked, false); |
| HREQ(AUDIO_STREAM_CMD_SET_GAIN, set_gain, OnSetGainLocked, true); |
| HREQ(AUDIO_STREAM_CMD_PLUG_DETECT, plug_detect, OnPlugDetectLocked, true); |
| default: |
| DEBUG_LOG("Unrecognized stream command 0x%04x\n", req.hdr.cmd); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| zx_status_t UsbAudioStream::ProcessRingBufferChannel(dispatcher::Channel* channel) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| fbl::AutoLock lock(&lock_); |
| |
| union { |
| audio_proto::CmdHdr hdr; |
| audio_proto::RingBufGetFifoDepthReq get_fifo_depth; |
| audio_proto::RingBufGetBufferReq get_buffer; |
| audio_proto::RingBufStartReq rb_start; |
| audio_proto::RingBufStopReq rb_stop; |
| // TODO(johngro) : add more commands here |
| } req; |
| |
| static_assert(sizeof(req) <= 256, |
| "Request buffer is getting to be too large to hold on the stack!"); |
| |
| uint32_t req_size; |
| zx_status_t res = channel->Read(&req, sizeof(req), &req_size); |
| if (res != ZX_OK) |
| return res; |
| |
| if ((req_size < sizeof(req.hdr) || |
| (req.hdr.transaction_id == AUDIO_INVALID_TRANSACTION_ID))) |
| return ZX_ERR_INVALID_ARGS; |
| |
| // Strip the NO_ACK flag from the request before selecting the dispatch target. |
| auto cmd = static_cast<audio_proto::Cmd>(req.hdr.cmd & ~AUDIO_FLAG_NO_ACK); |
| switch (cmd) { |
| HREQ(AUDIO_RB_CMD_GET_FIFO_DEPTH, get_fifo_depth, OnGetFifoDepthLocked, false); |
| HREQ(AUDIO_RB_CMD_GET_BUFFER, get_buffer, OnGetBufferLocked, false); |
| HREQ(AUDIO_RB_CMD_START, rb_start, OnStartLocked, false); |
| HREQ(AUDIO_RB_CMD_STOP, rb_stop, OnStopLocked, false); |
| default: |
| DEBUG_LOG("Unrecognized ring buffer command 0x%04x\n", req.hdr.cmd); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| #undef HANDLE_REQ |
| |
| zx_status_t UsbAudioStream::OnGetStreamFormatsLocked(dispatcher::Channel* channel, |
| const audio_proto::StreamGetFmtsReq& req) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| size_t formats_sent = 0; |
| audio_proto::StreamGetFmtsResp resp; |
| |
| if (supported_formats_.size() > fbl::numeric_limits<uint16_t>::max()) { |
| LOG("Too many formats (%zu) to send during AUDIO_STREAM_CMD_GET_FORMATS request!\n", |
| supported_formats_.size()); |
| return ZX_ERR_INTERNAL; |
| } |
| |
| resp.hdr = req.hdr; |
| resp.format_range_count = static_cast<uint16_t>(supported_formats_.size()); |
| |
| do { |
| size_t todo, payload_sz, __UNUSED to_send; |
| zx_status_t res; |
| |
| todo = fbl::min<size_t>(supported_formats_.size() - formats_sent, |
| AUDIO_STREAM_CMD_GET_FORMATS_MAX_RANGES_PER_RESPONSE); |
| payload_sz = sizeof(resp.format_ranges[0]) * todo; |
| to_send = offsetof(audio_proto::StreamGetFmtsResp, format_ranges) + payload_sz; |
| |
| resp.first_format_range_ndx = static_cast<uint16_t>(formats_sent); |
| ::memcpy(resp.format_ranges, supported_formats_.get() + formats_sent, payload_sz); |
| |
| res = channel->Write(&resp, sizeof(resp)); |
| if (res != ZX_OK) { |
| DEBUG_LOG("Failed to send get stream formats response (res %d)\n", res); |
| return res; |
| } |
| |
| formats_sent += todo; |
| } while (formats_sent < supported_formats_.size()); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbAudioStream::OnSetStreamFormatLocked(dispatcher::Channel* channel, |
| const audio_proto::StreamSetFmtReq& req, |
| bool privileged) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| |
| zx::channel client_rb_channel; |
| audio_proto::StreamSetFmtResp resp; |
| bool found_one = false; |
| |
| resp.hdr = req.hdr; |
| |
| // Only the privileged stream channel is allowed to change the format. |
| if (!privileged) { |
| ZX_DEBUG_ASSERT(channel == stream_channel_.get()); |
| resp.result = ZX_ERR_ACCESS_DENIED; |
| goto finished; |
| } |
| |
| // Check the format for compatibility |
| for (const auto& fmt : supported_formats_) { |
| if (audio::utils::FormatIsCompatible(req.frames_per_second, |
| req.channels, |
| req.sample_format, |
| fmt)) { |
| found_one = true; |
| break; |
| } |
| } |
| |
| if (!found_one) { |
| resp.result = ZX_ERR_INVALID_ARGS; |
| goto finished; |
| } |
| |
| { |
| // TODO(johngro) : If the ring buffer is running, should we automatically |
| // stop it instead of returning bad state? |
| fbl::AutoLock req_lock(&req_lock_); |
| if (ring_buffer_state_ != RingBufferState::STOPPED) { |
| resp.result = ZX_ERR_BAD_STATE; |
| goto finished; |
| } |
| } |
| |
| // Determine the frame size. |
| frame_size_ = audio::utils::ComputeFrameSize(req.channels, req.sample_format); |
| if (!frame_size_) { |
| LOG("Failed to compute frame size (ch %hu fmt 0x%08x)\n", req.channels, req.sample_format); |
| resp.result = ZX_ERR_INTERNAL; |
| goto finished; |
| } |
| |
| // Compute the size of our short packets, and the constants used to generate |
| // the short/long packet cadence. For now, assume that we will be operating |
| // at a 1mSec isochronous rate. |
| // |
| // Make sure that we can fit our longest payload length into one of our |
| // usb requests. |
| // |
| // TODO(johngro) : Unless/until we can find some way to set the USB bus |
| // driver to perform direct DMA to/from the Ring Buffer VMO without the need |
| // for software intervention, we may want to expose ways to either increase |
| // the isochronous interval (to minimize load) or to use USB 2.0 125uSec |
| // sub-frame timing (to decrease latency) if possible. |
| uint32_t long_payload_len; |
| iso_packet_rate_ = 1000; |
| bytes_per_packet_ = (req.frames_per_second / iso_packet_rate_) * frame_size_; |
| fractional_bpp_inc_ = (req.frames_per_second % iso_packet_rate_); |
| long_payload_len = bytes_per_packet_ + (fractional_bpp_inc_ ? frame_size_ : 0); |
| if (long_payload_len > max_req_size_) { |
| resp.result = ZX_ERR_INVALID_ARGS; |
| goto finished; |
| } |
| |
| // Looks like we are going ahead with this format change. Tear down any |
| // exiting ring buffer interface before proceeding. |
| if (rb_channel_ != nullptr) { |
| rb_channel_->Deactivate(); |
| rb_channel_.reset(); |
| } |
| |
| // We always try to keep two isochronous packets in flight at any point in |
| // time. Based on our cadence generation parameters, determine if it is |
| // possible to have 0, 1 or 2 long packets back to back at any point in time |
| // during the sequence. |
| // |
| // TODO(johngro): This is not the proper way to report the FIFO depth. How |
| // far ahead the USB controller will read ahead into its FIFO is going to be |
| // a property of the controller and the properties of the endpoint. It is |
| // possible that this is negotiable to some extent as well. I need to work |
| // with voydanof@ to determine what we can expose from the USB bus driver in |
| // order to report this accurately. |
| // |
| // Right now, we assume that the controller will never get farther ahead |
| // than two isochronous usb requests, so we report this the worst case fifo_depth. |
| fifo_bytes_ = bytes_per_packet_ << 1; |
| |
| // If we have no fractional portion to accumulate, we always send |
| // short packets. If our fractional portion is <= 1/2 of our |
| // isochronous rate, then we will never send two long packets back |
| // to back. |
| if (fractional_bpp_inc_) { |
| fifo_bytes_ += frame_size_; |
| if (fractional_bpp_inc_ > (iso_packet_rate_ >> 1)) { |
| fifo_bytes_ += frame_size_; |
| } |
| } |
| |
| // Send the commands required to set up the new format. 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. |
| // |
| // TODO(johngro): more work is needed if we are changing sample format or |
| // channel count. Right now, we only support the one format/count provided |
| // to us by the outer layer, but eventually we need to support them all. |
| if (!fixed_sample_rate_) { |
| ZX_DEBUG_ASSERT(parent_ != nullptr); |
| resp.result = usb_audio_set_sample_rate(&usb_, usb_ep_addr_, req.frames_per_second); |
| if (resp.result != ZX_OK) { |
| goto finished; |
| } |
| } |
| |
| // Create a new ring buffer channel which can be used to move bulk data and |
| // bind it to us. |
| rb_channel_ = dispatcher::Channel::Create(); |
| if (rb_channel_ == nullptr) { |
| resp.result = ZX_ERR_NO_MEMORY; |
| } else { |
| dispatcher::Channel::ProcessHandler phandler( |
| [stream = fbl::WrapRefPtr(this)](dispatcher::Channel* channel) -> zx_status_t { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->default_domain_); |
| return stream->ProcessRingBufferChannel(channel); |
| }); |
| |
| dispatcher::Channel::ChannelClosedHandler chandler( |
| [stream = fbl::WrapRefPtr(this)](const dispatcher::Channel* channel) -> void { |
| OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->default_domain_); |
| stream->DeactivateRingBufferChannel(channel); |
| }); |
| |
| resp.result = rb_channel_->Activate(&client_rb_channel, |
| default_domain_, |
| fbl::move(phandler), |
| fbl::move(chandler)); |
| if (resp.result != ZX_OK) { |
| rb_channel_.reset(); |
| } |
| } |
| |
| finished: |
| if (resp.result == ZX_OK) { |
| return channel->Write(&resp, sizeof(resp), fbl::move(client_rb_channel)); |
| } else { |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| } |
| |
| zx_status_t UsbAudioStream::OnGetGainLocked(dispatcher::Channel* channel, |
| const audio_proto::GetGainReq& req) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| audio_proto::GetGainResp resp; |
| |
| resp.hdr = req.hdr; |
| resp.cur_mute = false; |
| resp.cur_gain = 0.0; |
| resp.can_mute = false; |
| resp.min_gain = 0.0; |
| resp.max_gain = 0.0; |
| resp.gain_step = 0.0; |
| |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t UsbAudioStream::OnSetGainLocked(dispatcher::Channel* channel, |
| const audio_proto::SetGainReq& req) { |
| ZX_DEBUG_ASSERT(channel != nullptr); |
| if (req.hdr.cmd & AUDIO_FLAG_NO_ACK) |
| return ZX_OK; |
| |
| audio_proto::SetGainResp resp; |
| resp.hdr = req.hdr; |
| |
| bool illegal_mute = (req.flags & AUDIO_SGF_MUTE_VALID) && (req.flags & AUDIO_SGF_MUTE); |
| bool illegal_gain = (req.flags & AUDIO_SGF_GAIN_VALID) && (req.gain != 0.0f); |
| |
| resp.cur_mute = false; |
| resp.cur_gain = 0.0; |
| resp.result = (illegal_mute || illegal_gain) |
| ? ZX_ERR_INVALID_ARGS |
| : ZX_OK; |
| |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t UsbAudioStream::OnPlugDetectLocked(dispatcher::Channel* channel, |
| const audio_proto::PlugDetectReq& req) { |
| if (req.hdr.cmd & AUDIO_FLAG_NO_ACK) |
| return ZX_OK; |
| |
| audio_proto::PlugDetectResp resp; |
| resp.hdr = req.hdr; |
| resp.flags = static_cast<audio_pd_notify_flags_t>(AUDIO_PDNF_HARDWIRED | |
| AUDIO_PDNF_PLUGGED); |
| resp.plug_state_time = create_time_; |
| |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t UsbAudioStream::OnGetFifoDepthLocked(dispatcher::Channel* channel, |
| const audio_proto::RingBufGetFifoDepthReq& req) { |
| audio_proto::RingBufGetFifoDepthResp resp; |
| |
| resp.hdr = req.hdr; |
| resp.result = ZX_OK; |
| resp.fifo_depth = fifo_bytes_; |
| |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| zx_status_t UsbAudioStream::OnGetBufferLocked(dispatcher::Channel* channel, |
| const audio_proto::RingBufGetBufferReq& req) { |
| audio_proto::RingBufGetBufferResp resp; |
| zx::vmo client_rb_handle; |
| uint32_t map_flags, client_rights; |
| |
| resp.hdr = req.hdr; |
| resp.result = ZX_ERR_INTERNAL; |
| |
| { |
| // We cannot create a new ring buffer if we are not currently stopped. |
| fbl::AutoLock req_lock(&req_lock_); |
| if (ring_buffer_state_ != RingBufferState::STOPPED) { |
| resp.result = ZX_ERR_BAD_STATE; |
| goto finished; |
| } |
| } |
| |
| // Unmap and release any previous ring buffer. |
| ReleaseRingBufferLocked(); |
| |
| // Compute the ring buffer size. It needs to be at least as big |
| // as the virtual fifo depth. |
| ZX_DEBUG_ASSERT(frame_size_ && ((fifo_bytes_ % frame_size_) == 0)); |
| ZX_DEBUG_ASSERT(fifo_bytes_ && ((fifo_bytes_ % fifo_bytes_) == 0)); |
| ring_buffer_size_ = req.min_ring_buffer_frames; |
| ring_buffer_size_ *= frame_size_; |
| if (ring_buffer_size_ < fifo_bytes_) |
| ring_buffer_size_ = fifo_bytes_; |
| |
| // Set up our state for generating notifications. |
| if (req.notifications_per_ring) { |
| bytes_per_notification_ = ring_buffer_size_ / req.notifications_per_ring; |
| } else { |
| bytes_per_notification_ = 0; |
| } |
| |
| // Create the ring buffer vmo we will use to share memory with the client. |
| resp.result = zx::vmo::create(ring_buffer_size_, 0, &ring_buffer_vmo_); |
| if (resp.result != ZX_OK) { |
| LOG("Failed to create ring buffer (size %u, res %d)\n", ring_buffer_size_, resp.result); |
| goto finished; |
| } |
| |
| // Map the VMO into our address space. |
| // |
| // TODO(johngro): skip this step when APIs in the USB bus driver exist to |
| // DMA directly from the VMO. |
| map_flags = ZX_VM_FLAG_PERM_READ; |
| if (is_input()) |
| map_flags |= ZX_VM_FLAG_PERM_WRITE; |
| |
| resp.result = zx::vmar::root_self().map(0, ring_buffer_vmo_, |
| 0, ring_buffer_size_, |
| map_flags, |
| reinterpret_cast<uintptr_t*>(&ring_buffer_virt_)); |
| if (resp.result != ZX_OK) { |
| LOG("Failed to map ring buffer (size %u, res %d)\n", ring_buffer_size_, resp.result); |
| goto finished; |
| } |
| |
| // Create the client's handle to the ring buffer vmo and set it back to them. |
| client_rights = ZX_RIGHT_TRANSFER | ZX_RIGHT_MAP | ZX_RIGHT_READ; |
| if (!is_input()) |
| client_rights |= ZX_RIGHT_WRITE; |
| |
| resp.result = ring_buffer_vmo_.duplicate(client_rights, &client_rb_handle); |
| if (resp.result != ZX_OK) { |
| LOG("Failed to duplicate ring buffer handle (res %d)\n", resp.result); |
| goto finished; |
| } |
| |
| finished: |
| zx_status_t res; |
| if (resp.result == ZX_OK) { |
| ZX_DEBUG_ASSERT(client_rb_handle.is_valid()); |
| res = channel->Write(&resp, sizeof(resp), fbl::move(client_rb_handle)); |
| } else { |
| res = channel->Write(&resp, sizeof(resp)); |
| } |
| |
| if (res != ZX_OK) |
| ReleaseRingBufferLocked(); |
| |
| return res; |
| } |
| |
| zx_status_t UsbAudioStream::OnStartLocked(dispatcher::Channel* channel, |
| const audio_proto::RingBufStartReq& req) { |
| audio_proto::RingBufStartResp resp; |
| resp.hdr = req.hdr; |
| resp.start_ticks = 0; |
| |
| fbl::AutoLock req_lock(&req_lock_); |
| |
| if (ring_buffer_state_ != RingBufferState::STOPPED) { |
| // The ring buffer is running, do not linger in the lock while we send |
| // the error code back to the user. |
| req_lock.release(); |
| resp.result = ZX_ERR_BAD_STATE; |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| // We are idle, all of our usb requests should be sitting in the free list. |
| ZX_DEBUG_ASSERT(allocated_req_cnt_ == free_req_cnt_); |
| |
| // switch to alternate interface if necessary |
| if (alt_setting_ != 0) { |
| usb_set_interface(&usb_, iface_num_, alt_setting_); |
| } |
| |
| // Initialize the counters used to... |
| // 1) generate the short/long packet cadence. |
| // 2) generate notifications. |
| // 3) track the position in the ring buffer. |
| fractional_bpp_acc_ = 0; |
| notification_acc_ = 0; |
| ring_buffer_offset_ = 0; |
| ring_buffer_pos_ = 0; |
| |
| // Schedule the frame number which the first transaction will go out on. |
| // |
| // TODO(johngro): This cannot be the current frame number, that train |
| // has already left the station. It probably should not be the next frame |
| // number either as that train might be just about to leave the station. |
| // |
| // For now, set this to be the current frame number +2 and use the first |
| // transaction complete callback to estimate the DMA start time. Moving |
| // forward, when the USB bus driver can tell us which frame a transaction |
| // went out on, schedule the transaction using the special "on the next USB |
| // isochronous frame" sentinel value and figure out which frame that was |
| // during the callback. |
| size_t read_amt; |
| resp.result = device_ioctl(parent_, IOCTL_USB_GET_CURRENT_FRAME, |
| NULL, 0, |
| &usb_frame_num_, sizeof(usb_frame_num_), |
| &read_amt); |
| if ((resp.result != ZX_OK) || (read_amt != sizeof(usb_frame_num_))) { |
| LOG("Failed to fetch USB frame number! (res %d, amt %zu)\n", resp.result, read_amt); |
| if (alt_setting_ != 0) { |
| usb_set_interface(&usb_, iface_num_, 0); |
| } |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| usb_frame_num_ += 2; |
| |
| // Flag ourselves as being in the starting state, then queue up all of our |
| // transactions. |
| ring_buffer_state_ = RingBufferState::STARTING; |
| while (!list_is_empty(&free_req_)) |
| QueueRequestLocked(); |
| |
| // Record the transaction ID we will send back to our client when we have |
| // successfully started, then get out. |
| pending_job_resp_.start = resp; |
| return ZX_OK; |
| } |
| |
| zx_status_t UsbAudioStream::OnStopLocked(dispatcher::Channel* channel, |
| const audio_proto::RingBufStopReq& req) { |
| fbl::AutoLock req_lock(&req_lock_); |
| |
| // TODO(johngro): We currently cannot cancel USB transactions once queued. |
| // When we can, we can come back and simply cancel the in-flight |
| // transactions instead of having an intermediate STOPPING state we use to |
| // wait for the transactions in flight to finish via RequestComplete. |
| if (ring_buffer_state_ != RingBufferState::STARTED) { |
| audio_proto::RingBufStopResp resp; |
| |
| req_lock.release(); |
| resp.hdr = req.hdr; |
| resp.result = ZX_ERR_BAD_STATE; |
| |
| return channel->Write(&resp, sizeof(resp)); |
| } |
| |
| ring_buffer_state_ = RingBufferState::STOPPING; |
| pending_job_resp_.stop.hdr = req.hdr; |
| |
| return ZX_OK; |
| } |
| |
| void UsbAudioStream::RequestComplete(usb_request_t* req) { |
| enum class Action { |
| NONE, |
| SIGNAL_STARTED, |
| SIGNAL_STOPPED, |
| NOTIFY_POSITION, |
| HANDLE_UNPLUG, |
| }; |
| |
| union { |
| audio_proto::RingBufStopResp stop; |
| audio_proto::RingBufStartResp start; |
| audio_proto::RingBufPositionNotify notify_pos; |
| } resp; |
| |
| uint64_t complete_time = zx_ticks_get(); |
| Action when_finished = Action::NONE; |
| |
| // TODO(johngro) : See MG-940. Eliminate this as soon as we have a more |
| // official way of meeting real-time latency requirements. Also, the fact |
| // that this boosting gets done after the first transaction completes |
| // degrades the quality of the startup time estimate (if the system is under |
| // high load when the system starts up). As a general issue, there are |
| // better ways of refining this estimate than bumping the thread prio before |
| // the first transaction gets queued. Therefor, we just have a poor |
| // estimate for now and will need to live with the consequences. |
| if (!req_complete_prio_bumped_) { |
| zx_thread_set_priority(24 /* HIGH_PRIORITY in LK */); |
| req_complete_prio_bumped_ = true; |
| } |
| |
| { |
| fbl::AutoLock req_lock(&req_lock_); |
| |
| // Cache the status and length of this usb request. |
| zx_status_t req_status = req->response.status; |
| uint32_t req_length = static_cast<uint32_t>(req->header.length); |
| |
| // Complete the usb request. This will return the transaction to the free |
| // list and (in the case of an input stream) copy the payload to the |
| // ring buffer, and update the ring buffer position. |
| // |
| // TODO(johngro): copying the payload out of the ring buffer is an |
| // operation which goes away when we get to the zero copy world. |
| CompleteRequestLocked(req); |
| |
| // Did the transaction fail because the device was unplugged? If so, |
| // enter the stopping state and close the connections to our clients. |
| if (req_status == ZX_ERR_IO_NOT_PRESENT) { |
| ring_buffer_state_ = RingBufferState::STOPPING_AFTER_UNPLUG; |
| } else { |
| // If we are supposed to be delivering notifications, check to see |
| // if it is time to do so. |
| if (bytes_per_notification_) { |
| notification_acc_ += req_length; |
| |
| if ((ring_buffer_state_ == RingBufferState::STARTED) && |
| (notification_acc_ >= bytes_per_notification_)) { |
| when_finished = Action::NOTIFY_POSITION; |
| notification_acc_ = (notification_acc_ % bytes_per_notification_); |
| resp.notify_pos.ring_buffer_pos = ring_buffer_pos_; |
| } |
| } |
| } |
| |
| switch (ring_buffer_state_) { |
| case RingBufferState::STOPPING: |
| if (free_req_cnt_ == allocated_req_cnt_) { |
| resp.stop = pending_job_resp_.stop; |
| when_finished = Action::SIGNAL_STOPPED; |
| } |
| break; |
| |
| case RingBufferState::STOPPING_AFTER_UNPLUG: |
| if (free_req_cnt_ == allocated_req_cnt_) { |
| resp.stop = pending_job_resp_.stop; |
| when_finished = Action::HANDLE_UNPLUG; |
| } |
| break; |
| |
| case RingBufferState::STARTING: |
| resp.start = pending_job_resp_.start; |
| when_finished = Action::SIGNAL_STARTED; |
| break; |
| |
| case RingBufferState::STARTED: |
| QueueRequestLocked(); |
| break; |
| |
| case RingBufferState::STOPPED: |
| default: |
| LOG("Invalid state (%u) in %s\n", |
| static_cast<uint32_t>(ring_buffer_state_), __PRETTY_FUNCTION__); |
| ZX_DEBUG_ASSERT(false); |
| break; |
| } |
| } |
| |
| if (when_finished != Action::NONE) { |
| fbl::AutoLock lock(&lock_); |
| switch (when_finished) { |
| case Action::SIGNAL_STARTED: |
| if (rb_channel_ != nullptr) { |
| // TODO(johngro) : this start time estimate is not as good as it |
| // could be. We really need to have the USB bus driver report |
| // the relationship between the USB frame counter and the system |
| // tick counter (and track the relationship in the case that the |
| // USB oscillator is not derived from the system oscillator). |
| // Then we can accurately report the start time as the time of |
| // the tick on which we scheduled the first transaction. |
| resp.start.result = ZX_OK; |
| resp.start.start_ticks = complete_time - ticks_per_msec_; |
| rb_channel_->Write(&resp.start, sizeof(resp.start)); |
| } |
| { |
| fbl::AutoLock req_lock(&req_lock_); |
| ring_buffer_state_ = RingBufferState::STARTED; |
| } |
| break; |
| |
| case Action::HANDLE_UNPLUG: |
| if (rb_channel_ != nullptr) { |
| rb_channel_->Deactivate(); |
| rb_channel_.reset(); |
| } |
| |
| if (stream_channel_ != nullptr) { |
| stream_channel_->Deactivate(); |
| stream_channel_.reset(); |
| } |
| |
| { |
| fbl::AutoLock req_lock(&req_lock_); |
| ring_buffer_state_ = RingBufferState::STOPPED; |
| } |
| break; |
| |
| case Action::SIGNAL_STOPPED: |
| if (rb_channel_ != nullptr) { |
| resp.stop.result = ZX_OK; |
| rb_channel_->Write(&resp.stop, sizeof(resp.stop)); |
| } |
| { |
| fbl::AutoLock req_lock(&req_lock_); |
| ring_buffer_state_ = RingBufferState::STOPPED; |
| } |
| break; |
| |
| case Action::NOTIFY_POSITION: |
| resp.notify_pos.hdr.cmd = AUDIO_RB_POSITION_NOTIFY; |
| resp.notify_pos.hdr.transaction_id = AUDIO_INVALID_TRANSACTION_ID; |
| rb_channel_->Write(&resp.notify_pos, sizeof(resp.notify_pos)); |
| break; |
| |
| default: |
| ZX_DEBUG_ASSERT(false); |
| break; |
| } |
| } |
| } |
| |
| void UsbAudioStream::QueueRequestLocked() { |
| ZX_DEBUG_ASSERT((ring_buffer_state_ == RingBufferState::STARTING) || |
| (ring_buffer_state_ == RingBufferState::STARTED)); |
| ZX_DEBUG_ASSERT(!list_is_empty(&free_req_)); |
| |
| // Figure out how much we want to send or receive this time (short or long |
| // packet) |
| uint32_t todo = bytes_per_packet_; |
| fractional_bpp_acc_ += fractional_bpp_inc_; |
| if (fractional_bpp_acc_ >= iso_packet_rate_) { |
| fractional_bpp_acc_ -= iso_packet_rate_; |
| todo += frame_size_; |
| ZX_DEBUG_ASSERT(fractional_bpp_acc_ < iso_packet_rate_); |
| } |
| |
| // Grab a free usb request. |
| auto req = list_remove_head_type(&free_req_, usb_request_t, node); |
| ZX_DEBUG_ASSERT(req != nullptr); |
| ZX_DEBUG_ASSERT(free_req_cnt_ > 0); |
| --free_req_cnt_; |
| |
| // If this is an output stream, copy our data into the usb request. |
| // TODO(johngro): eliminate this when we can get to a zero-copy world. |
| if (!is_input()) { |
| uint32_t avail = ring_buffer_size_ - ring_buffer_offset_; |
| ZX_DEBUG_ASSERT(ring_buffer_offset_ < ring_buffer_size_); |
| ZX_DEBUG_ASSERT((avail % frame_size_) == 0); |
| uint32_t amt = fbl::min(avail, todo); |
| |
| const uint8_t* src = reinterpret_cast<uint8_t*>(ring_buffer_virt_) + ring_buffer_offset_; |
| usb_request_copyto(req, src, amt, 0); |
| if (amt == avail) { |
| ring_buffer_offset_ = todo - amt; |
| if (ring_buffer_offset_ > 0) { |
| usb_request_copyto(req, ring_buffer_virt_, ring_buffer_offset_, amt); |
| } |
| } else { |
| ring_buffer_offset_ += amt; |
| } |
| } |
| |
| req->header.frame = usb_frame_num_++; |
| req->header.length = todo; |
| usb_request_queue(&usb_, req); |
| } |
| |
| void UsbAudioStream::CompleteRequestLocked(usb_request_t* req) { |
| ZX_DEBUG_ASSERT(req); |
| |
| // If we are an input stream, copy the payload into the ring buffer. |
| if (is_input()) { |
| uint32_t todo = static_cast<uint32_t>(req->header.length); |
| |
| uint32_t avail = ring_buffer_size_ - ring_buffer_offset_; |
| ZX_DEBUG_ASSERT(ring_buffer_offset_ < ring_buffer_size_); |
| ZX_DEBUG_ASSERT((avail % frame_size_) == 0); |
| |
| uint32_t amt = fbl::min(avail, todo); |
| uint8_t* dst = reinterpret_cast<uint8_t*>(ring_buffer_virt_) + ring_buffer_offset_; |
| |
| if (req->response.status == ZX_OK) { |
| usb_request_copyfrom(req, dst, amt, 0); |
| if (amt < todo) { |
| usb_request_copyfrom(req, ring_buffer_virt_, todo - amt, amt); |
| } |
| } else { |
| // TODO(johngro): filling with zeros is only the proper thing to do |
| // for signed formats. USB does support unsigned 8-bit audio; if |
| // that is our format, we should fill with 0x80 instead in order to |
| // fill with silence. |
| memset(dst, 0, amt); |
| if (amt < todo) { |
| memset(ring_buffer_virt_, 0, todo - amt); |
| } |
| } |
| } |
| |
| // Update the ring buffer position. |
| ring_buffer_pos_ += static_cast<uint32_t>(req->header.length); |
| if (ring_buffer_pos_ >= ring_buffer_size_) { |
| ring_buffer_pos_ -= ring_buffer_size_; |
| ZX_DEBUG_ASSERT(ring_buffer_pos_ < ring_buffer_size_); |
| } |
| |
| // If this is an input stream, the ring buffer offset should always be equal |
| // to the stream position. |
| if (is_input()) { |
| ring_buffer_offset_ = ring_buffer_pos_; |
| } |
| |
| // Return the transaction to the free list. |
| list_add_head(&free_req_, &req->node); |
| ++free_req_cnt_; |
| ZX_DEBUG_ASSERT(free_req_cnt_ <= allocated_req_cnt_); |
| } |
| |
| void UsbAudioStream::DeactivateStreamChannel(const dispatcher::Channel* channel) { |
| fbl::AutoLock lock(&lock_); |
| |
| ZX_DEBUG_ASSERT(stream_channel_.get() == channel); |
| ZX_DEBUG_ASSERT(rb_channel_.get() != channel); |
| stream_channel_.reset(); |
| } |
| |
| void UsbAudioStream::DeactivateRingBufferChannel(const dispatcher::Channel* channel) { |
| fbl::AutoLock lock(&lock_); |
| |
| ZX_DEBUG_ASSERT(stream_channel_.get() != channel); |
| ZX_DEBUG_ASSERT(rb_channel_.get() == channel); |
| |
| { |
| fbl::AutoLock req_lock(&req_lock_); |
| if (ring_buffer_state_ != RingBufferState::STOPPED) { |
| ring_buffer_state_ = RingBufferState::STOPPING; |
| } |
| } |
| |
| rb_channel_.reset(); |
| } |
| |
| } // namespace usb |
| } // namespace audio |
| |
| extern "C" |
| zx_status_t usb_audio_sink_create(zx_device_t* device, usb_protocol_t* usb, int index, |
| usb_interface_descriptor_t* intf, |
| usb_endpoint_descriptor_t* ep, |
| usb_audio_ac_format_type_i_desc* format_desc) { |
| return audio::usb::UsbAudioStream::Create(false, device, usb, index, intf, ep, format_desc); |
| } |
| |
| extern "C" |
| zx_status_t usb_audio_source_create(zx_device_t* device, usb_protocol_t* usb, int index, |
| usb_interface_descriptor_t* intf, |
| usb_endpoint_descriptor_t* ep, |
| usb_audio_ac_format_type_i_desc* format_desc) { |
| return audio::usb::UsbAudioStream::Create(true, device, usb, index, intf, ep, format_desc); |
| } |
| |
| extern "C" |
| void usb_audio_driver_release(void*) { |
| audio::dispatcher::ThreadPool::ShutdownAll(); |
| } |