| // 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 <ddk/binding.h> |
| #include <dispatcher-pool/dispatcher-thread-pool.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/intrusive_double_list.h> |
| #include <string.h> |
| |
| #include <utility> |
| |
| #include "usb-audio.h" |
| #include "usb-audio-device.h" |
| #include "usb-audio-stream.h" |
| #include "usb-audio-stream-interface.h" |
| |
| namespace audio { |
| namespace usb { |
| |
| zx_status_t UsbAudioDevice::DriverBind(zx_device_t* parent) { |
| fbl::AllocChecker ac; |
| auto usb_device = fbl::AdoptRef(new (&ac) audio::usb::UsbAudioDevice(parent)); |
| if (!ac.check()) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx_status_t status = usb_device->Bind(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // We have transferred our fbl::RefPtr reference to the C ddk. We will |
| // recover it (someday) when the release hook is called. Until then, we |
| // need to deliberately leak our reference so that we do not destruct as we |
| // exit this function. |
| __UNUSED UsbAudioDevice* leaked_ref; |
| leaked_ref = usb_device.leak_ref(); |
| return status; |
| } |
| |
| UsbAudioDevice::UsbAudioDevice(zx_device_t* parent) : UsbAudioDeviceBase(parent) { |
| ::memset(&usb_proto_, 0, sizeof(usb_proto_)); |
| ::memset(&usb_dev_desc_, 0, sizeof(usb_dev_desc_)); |
| snprintf(log_prefix_, sizeof(log_prefix_), "UsbAud Unknown"); |
| } |
| |
| void UsbAudioDevice::RemoveAudioStream(const fbl::RefPtr<UsbAudioStream>& stream) { |
| fbl::AutoLock lock(&lock_); |
| ZX_DEBUG_ASSERT(stream != nullptr); |
| if (stream->InContainer()) { |
| streams_.erase(*stream); |
| } |
| } |
| |
| zx_status_t UsbAudioDevice::Bind() { |
| zx_status_t status; |
| |
| // Fetch our protocol. We will need it to do pretty much anything with this |
| // device. |
| status = device_get_protocol(parent(), ZX_PROTOCOL_USB, &usb_proto_); |
| if (status != ZX_OK) { |
| LOG(ERROR, "Failed to get USB protocol thunks (status %d)\n", status); |
| return status; |
| } |
| |
| parent_req_size_ = usb_get_request_size(&usb_proto_); |
| ZX_DEBUG_ASSERT(parent_req_size_ != 0); |
| |
| usb_composite_protocol_t usb_composite_proto; |
| status = device_get_protocol(parent(), ZX_PROTOCOL_USB_COMPOSITE, &usb_composite_proto); |
| if (status != ZX_OK) { |
| LOG(ERROR, "Failed to get USB composite protocol thunks (status %d)\n", status); |
| return status; |
| } |
| |
| // Fetch our top level device descriptor, so we know stuff like the values |
| // of our VID/PID. |
| usb_get_device_descriptor(&usb_proto_, &usb_dev_desc_); |
| snprintf(log_prefix_, sizeof(log_prefix_), "UsbAud %04x:%04x", vid(), pid()); |
| |
| // Attempt to cache the string descriptors for our manufacturer name, |
| // product name, and serial number. |
| if (usb_dev_desc_.iManufacturer) { |
| mfr_name_ = FetchStringDescriptor(usb_proto_, usb_dev_desc_.iManufacturer); |
| } |
| |
| if (usb_dev_desc_.iProduct) { |
| prod_name_ = FetchStringDescriptor(usb_proto_, usb_dev_desc_.iProduct); |
| } |
| |
| if (usb_dev_desc_.iSerialNumber) { |
| serial_num_ = FetchStringDescriptor(usb_proto_, usb_dev_desc_.iSerialNumber); |
| } |
| |
| // Our top level binding script has only claimed audio interfaces with a |
| // subclass of control. Go ahead and claim anything which has a top level |
| // class of of "audio"; this is where we will find our Audio and MIDI |
| // streaming interfaces. |
| status = usb_claim_additional_interfaces( |
| &usb_composite_proto, |
| [](usb_interface_descriptor_t* intf, void* arg) -> bool { |
| return (intf->bInterfaceClass == USB_CLASS_AUDIO && |
| intf->bInterfaceSubClass != USB_SUBCLASS_AUDIO_CONTROL); |
| }, |
| NULL); |
| if (status != ZX_OK) { |
| LOG(ERROR, "Failed to claim additional audio interfaces (status %d)\n", status); |
| return status; |
| } |
| |
| // Allocate and read in our descriptor list. |
| desc_list_ = DescriptorListMemory::Create(&usb_proto_); |
| if (desc_list_ == nullptr) { |
| LOG(ERROR, "Failed to fetch descriptor list\n"); |
| return status; |
| } |
| |
| // Publish our device. |
| status = DdkAdd("usb-audio-ctrl"); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| Probe(); |
| |
| return ZX_OK; |
| } |
| |
| void UsbAudioDevice::Probe() { |
| // A reference to the audio control interface along with the set of audio |
| // stream interfaces that we discover during probing. We will need at least |
| // one control interface and one or more usable streaming audio interface if |
| // we want to publish *any* audio streams. |
| fbl::unique_ptr<UsbAudioControlInterface> control_ifc; |
| fbl::DoublyLinkedList<fbl::unique_ptr<UsbAudioStreamInterface>> aud_stream_ifcs; |
| |
| // Go over our descriptor list. Right now, we are looking for only three |
| // things; The Audio Control interface, and the various Audio/MIDI Streaming |
| // interfaces. |
| DescriptorListMemory::Iterator iter(desc_list_); |
| while (iter.valid()) { |
| // Advance to the next descriptor if we don't find and parse an |
| // interface we understand. |
| auto cleanup = fbl::MakeAutoCall([&iter] { iter.Next(); }); |
| auto hdr = iter.hdr(); |
| |
| // We are only prepared to find interface descriptors at this point. |
| if (hdr->bDescriptorType != USB_DT_INTERFACE) { |
| LOG(WARN, "Skipping unexpected descriptor (len = %u, type = %u)\n", |
| hdr->bLength, hdr->bDescriptorType); |
| continue; |
| } |
| |
| auto ihdr = iter.hdr_as<usb_interface_descriptor_t>(); |
| if (ihdr == nullptr) { |
| LOG(WARN, "Skipping bad interface descriptor header @ offset %zu/%zu\n", |
| iter.offset(), iter.desc_list()->size()); |
| continue; |
| } |
| |
| if ((ihdr->bInterfaceClass != USB_CLASS_AUDIO) || |
| ((ihdr->bInterfaceSubClass != USB_SUBCLASS_AUDIO_CONTROL) && |
| (ihdr->bInterfaceSubClass != USB_SUBCLASS_AUDIO_STREAMING) && |
| (ihdr->bInterfaceSubClass != USB_SUBCLASS_MIDI_STREAMING))) { |
| LOG(WARN, "Skipping unknown interface (class %u, subclass %u)\n", |
| ihdr->bInterfaceClass, ihdr->bInterfaceSubClass); |
| continue; |
| } |
| |
| switch (ihdr->bInterfaceSubClass) { |
| case USB_SUBCLASS_AUDIO_CONTROL: { |
| if (control_ifc != nullptr) { |
| LOG(WARN, "More than one audio control interface detected, skipping.\n"); |
| break; |
| } |
| |
| auto control = UsbAudioControlInterface::Create(this); |
| if (control == nullptr) { |
| LOG(WARN, "Failed to allocate audio control interface\n"); |
| break; |
| } |
| |
| // Give the control interface a chance to parse it's contents. |
| // Success or failure, when we are finished, the iterator should |
| // have been advanced to the next descriptor which does not make |
| // sense to the control interface parser. Cancel the cleanup |
| // task so that it does not skip over this header. |
| zx_status_t res = control->Initialize(&iter); |
| cleanup.cancel(); |
| if (res == ZX_OK) { |
| // No need to log in case of failure, the interface class |
| // should already have done so. |
| control_ifc = std::move(control); |
| } |
| break; |
| } |
| |
| case USB_SUBCLASS_AUDIO_STREAMING: { |
| // We recognize this header and are going to consume it (whether or |
| // not we successfully create or add to an existing audio stream |
| // interface). Cancel the cleanup lambda so that it does not skip |
| // the next header as well. |
| cleanup.cancel(); |
| |
| // Check to see if this is a new interface, or an alternate |
| // interface description for an existing stream interface. |
| uint8_t iid = ihdr->bInterfaceNumber; |
| auto ifc_iter = aud_stream_ifcs.find_if( |
| [iid](const UsbAudioStreamInterface& ifc) -> bool { return ifc.iid() == iid; }); |
| |
| if (ifc_iter.IsValid()) { |
| zx_status_t res = ifc_iter->AddInterface(&iter); |
| if (res != ZX_OK) { |
| LOG(WARN, "Failed to add audio stream interface (id %u) @ offset %zu/%zu\n", |
| iid, iter.offset(), iter.desc_list()->size()); |
| } |
| } else { |
| auto ifc = UsbAudioStreamInterface::Create(this, &iter); |
| if (ifc == nullptr) { |
| LOG(WARN, |
| "Failed to create audio stream interface (id %u) @ offset %zu/%zu\n", |
| iid, iter.offset(), iter.desc_list()->size()); |
| } else { |
| LOG(TRACE, "Discovered new audio streaming interface (id %u)\n", iid); |
| aud_stream_ifcs.push_back(std::move(ifc)); |
| } |
| } |
| break; |
| } |
| |
| // TODO(johngro): Do better than this for MIDI streaming interfaces. |
| // We should probably mirror the pattern used for the audio |
| // streaming interfaces where we create a class to hold all of the |
| // interfaces along with their descriptors and alternate interface |
| // variants, then pass that class on to a driver class assuming that |
| // everything checks out. |
| // |
| // Right now, we just look for a top level interface descriptor |
| // along with a single endpoint descriptor, and skip pretty much |
| // everything else. |
| case USB_SUBCLASS_MIDI_STREAMING: { |
| // We recognize this header and are going to consume it (whether or |
| // not we successfully create or add to an existing audio stream |
| // interface). Cancel the cleanup lambda so that it does not skip |
| // the next header as well. |
| cleanup.cancel(); |
| |
| // Go looking for the endpoint descriptor which goes with this |
| // streaming descriptor. If we find one, attempt to publish a device. |
| struct MidiStreamingInfo info(ihdr); |
| ParseMidiStreamingIfc(&iter, &info); |
| |
| if (info.out_ep != nullptr) { |
| LOG(TRACE, "Adding MIDI sink (iid %u, ep 0x%02x)\n", |
| info.ifc->bInterfaceNumber, info.out_ep->bEndpointAddress); |
| usb_midi_sink_create(zxdev(), |
| &usb_proto_, |
| midi_sink_index_++, |
| info.ifc, |
| info.out_ep, |
| parent_req_size_); |
| } |
| |
| if (info.in_ep != nullptr) { |
| LOG(TRACE, "Adding MIDI source (iid %u, ep 0x%02x)\n", |
| info.ifc->bInterfaceNumber, info.in_ep->bEndpointAddress); |
| usb_midi_source_create(zxdev(), |
| &usb_proto_, |
| midi_source_index_++, |
| info.ifc, |
| info.in_ep, |
| parent_req_size_); |
| } |
| |
| break; |
| } // case |
| } // switch |
| } |
| |
| if ((control_ifc == nullptr) && !aud_stream_ifcs.is_empty()) { |
| LOG(WARN, "No control interface discovered. Discarding all audio streaming interfaces\n"); |
| aud_stream_ifcs.clear(); |
| } |
| |
| // Now that we are done parsing all of our descriptors, go over our list of |
| // audio streaming interfaces and pair each up with the appropriate audio |
| // path as we go. Create an actual Fuchsia audio stream for each valid |
| // streaming interface with a valid audio path. |
| while (!aud_stream_ifcs.is_empty()) { |
| // Build the format map for this stream interface. If we cannot find |
| // any usable formats for this streaming interface, simply discard it. |
| auto stream_ifc = aud_stream_ifcs.pop_front(); |
| zx_status_t status = stream_ifc->BuildFormatMap(); |
| if (status != ZX_OK) { |
| LOG(ERROR, "Failed to build format map for streaming interface id %u (status %d)\n", |
| stream_ifc->iid(), status); |
| continue; |
| } |
| |
| // Find the path which goes with this interface. |
| auto path = control_ifc->ExtractPath(stream_ifc->term_link(), stream_ifc->direction()); |
| if (path == nullptr) { |
| LOG(WARN, |
| "Discarding audio streaming interface (id %u) as we could not find a path to match " |
| "its terminal link ID (%u) and direction (%u)\n", |
| stream_ifc->iid(), |
| stream_ifc->term_link(), |
| static_cast<uint32_t>(stream_ifc->direction())); |
| continue; |
| } |
| |
| // Link the path to the stream interface. |
| LOG(TRACE, "Linking streaming interface id %u to audio path terminal %u\n", |
| stream_ifc->iid(), path->stream_terminal().id()); |
| stream_ifc->LinkPath(std::move(path)); |
| |
| // Log a warning if we are about to build an audio path which operates |
| // in separate clock domain. We still need to add support for this |
| // case, see ZX-2044 for details. |
| if (stream_ifc->ep_sync_type() == EndpointSyncType::Async) { |
| LOG(WARN, |
| "Warning: Creating USB audio %s with operating in Asynchronous Isochronous mode. " |
| "See ZX-2044\n", |
| stream_ifc->direction() == Direction::Input ? "input" : "output"); |
| } |
| |
| // Create a new audio stream, handing the stream interface over to it. |
| auto stream = UsbAudioStream::Create(this, std::move(stream_ifc)); |
| if (stream == nullptr) { |
| // No need to log an error, the Create method has already done so. |
| continue; |
| } |
| |
| // Make sure that the stream is being tracked in our streams_ collection |
| // before attempting to publish its device node. |
| { |
| fbl::AutoLock lock(&lock_); |
| streams_.push_back(stream); |
| } |
| |
| // Publish the new stream. If something goes wrong, take it out of the |
| // streams_ collection. |
| status = stream->Bind(); |
| if (status != ZX_OK) { |
| // Again, no need to log. Bind will have already logged any error. |
| RemoveAudioStream(stream); |
| } |
| } |
| } |
| |
| void UsbAudioDevice::ParseMidiStreamingIfc(DescriptorListMemory::Iterator* iter, |
| MidiStreamingInfo* inout_info) { |
| MidiStreamingInfo& info = *inout_info; |
| |
| ZX_DEBUG_ASSERT(iter != nullptr); |
| ZX_DEBUG_ASSERT(inout_info != nullptr); |
| ZX_DEBUG_ASSERT(inout_info->ifc != nullptr); |
| |
| // Go looking for the endpoint descriptor which goes with this |
| // streaming descriptor. Try to consume all of the descriptors |
| // which go with this MIDI streaming descriptor as we go. |
| while (iter->Next()) { |
| auto hdr = iter->hdr(); |
| |
| switch (hdr->bDescriptorType) { |
| // Generic interface |
| case USB_DT_INTERFACE: { |
| auto ihdr = iter->hdr_as<usb_interface_descriptor_t>(); |
| if (ihdr == nullptr) { |
| return; |
| } |
| |
| // If this is not a midi streaming interface, or it is a midi |
| // streaming interface with a different interface id, than the ones |
| // we have been seeing, then we are done. |
| if ((ihdr->bInterfaceSubClass != USB_SUBCLASS_MIDI_STREAMING) || |
| (ihdr->bInterfaceNumber != info.ifc->bInterfaceNumber)) { |
| return; |
| } |
| |
| // If we have already found an endpoint which goes with an |
| // interface, then this is another alternate setting (either |
| // with an endpoint or an idle alternate settings). In a |
| // more complicated world, we should handle this, but for |
| // now we just log a warning and skip it. |
| if ((info.out_ep != nullptr) || (info.in_ep != nullptr)) { |
| LOG(WARN, |
| "Multiple alternate settings found for MIDI streaming interface " |
| "(iid %u, alt %u)\n", |
| ihdr->bInterfaceNumber, |
| ihdr->bAlternateSetting); |
| continue; |
| } |
| |
| // Stash this as the most recent MIDI streaming interface we have |
| // discovered, and keep parsing looking for the associated |
| // endpoint(s). |
| info.ifc = ihdr; |
| } break; |
| |
| // Class specific interface |
| case USB_AUDIO_CS_INTERFACE: { |
| auto aud_hdr = iter->hdr_as<usb_audio_desc_header>(); |
| if (aud_hdr == nullptr) { |
| return; |
| } |
| |
| // Silently skip the class specific MIDI headers which go |
| // along with this streaming interface descriptor. |
| if ((aud_hdr->bDescriptorSubtype == USB_MIDI_MS_HEADER) || |
| (aud_hdr->bDescriptorSubtype == USB_MIDI_IN_JACK) || |
| (aud_hdr->bDescriptorSubtype == USB_MIDI_OUT_JACK) || |
| (aud_hdr->bDescriptorSubtype == USB_MIDI_ELEMENT)) { |
| LOG(SPEW, "Skipping class specific MIDI interface subtype = %u\n", |
| aud_hdr->bDescriptorSubtype); |
| continue; |
| } |
| |
| // We don't recognize this class specific interface header. Stop |
| // parsing. |
| return; |
| } break; |
| |
| // Generic Endpoint |
| case USB_DT_ENDPOINT: { |
| auto ep_desc = iter->hdr_as<usb_endpoint_descriptor_t>(); |
| if (ep_desc == nullptr) { |
| return; |
| } |
| |
| // If this is not a bulk transfer endpoint, then we are not quite sure what to do with |
| // it. Log a warning and skip it. |
| if (usb_ep_type(ep_desc) != USB_ENDPOINT_BULK) { |
| LOG(WARN, |
| "Skipping Non-bulk transfer endpoint (%u) found for MIDI streaming interface " |
| "(iid %u, alt %u)\n", |
| usb_ep_type(ep_desc), |
| info.ifc->bInterfaceNumber, |
| info.ifc->bAlternateSetting); |
| continue; |
| } |
| |
| auto& ep_tgt = (usb_ep_direction(ep_desc) == USB_ENDPOINT_OUT) |
| ? info.out_ep |
| : info.in_ep; |
| const char* log_tag = (usb_ep_direction(ep_desc) == USB_ENDPOINT_OUT) |
| ? "output" : "input"; |
| |
| // If we have already found an endpoint for this interface, log a |
| // warning and skip this one. |
| if (ep_tgt != nullptr) { |
| LOG(WARN, |
| "Multiple %s endpoints found found for MIDI streaming interface " |
| "(iid %u, alt %u, exiting ep_addr 0x%02x, new ep_addr 0x%02x)\n", |
| log_tag, |
| info.ifc->bInterfaceNumber, |
| info.ifc->bAlternateSetting, |
| ep_tgt->bEndpointAddress, |
| ep_desc->bEndpointAddress); |
| continue; |
| } |
| |
| // Stash this endpoint as the found endpoint and keep parsing to |
| // consume the rest of the descriptors associated with this |
| // interface that we plan to ignore. |
| LOG(SPEW, "Found %s MIDI endpoint descriptor (addr 0x%02x, attr 0x%02x)\n", |
| log_tag, ep_desc->bEndpointAddress, ep_desc->bmAttributes); |
| ep_tgt = ep_desc; |
| } break; |
| |
| case USB_AUDIO_CS_ENDPOINT: { |
| auto ep_desc = iter->hdr_as<usb_midi_ms_endpoint_desc>(); |
| if (ep_desc == nullptr) { |
| return; |
| } |
| |
| if (ep_desc->bDescriptorSubtype == USB_MIDI_MS_GENERAL) { |
| LOG(SPEW, "Skipping class specific MIDI endpoint\n"); |
| continue; |
| } |
| |
| return; |
| } break; |
| |
| default: |
| return; |
| } |
| } |
| } |
| |
| void UsbAudioDevice::DdkUnbind() { |
| // Unpublish our device node. |
| DdkRemove(); |
| } |
| |
| void UsbAudioDevice::DdkRelease() { |
| // Recover our reference from the unmanaged C DDK. Then, just let it go out |
| // of scope. |
| auto reference = fbl::internal::MakeRefPtrNoAdopt(this); |
| } |
| |
| static zx_status_t usb_audio_device_bind(void* ctx, zx_device_t* device) { |
| return UsbAudioDevice::DriverBind(device); |
| } |
| |
| static void usb_audio_driver_release(void*) { |
| dispatcher::ThreadPool::ShutdownAll(); |
| } |
| |
| static constexpr zx_driver_ops_t driver_ops = [](){ |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = usb_audio_device_bind; |
| ops.release = usb_audio_driver_release; |
| return ops; |
| }(); |
| |
| } // namespace usb |
| } // namespace audio |
| |
| ZIRCON_DRIVER_BEGIN(usb_audio, audio::usb::driver_ops, "zircon", "0.1", 4) |
| BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_USB), |
| BI_ABORT_IF(NE, BIND_USB_CLASS, USB_CLASS_AUDIO), |
| BI_ABORT_IF(NE, BIND_USB_SUBCLASS, USB_SUBCLASS_AUDIO_CONTROL), |
| BI_MATCH_IF(EQ, BIND_USB_PROTOCOL, 0), |
| ZIRCON_DRIVER_END(usb_audio) |