blob: d8fc48e134c8fcbab4b214b5b91f1260b3fd1b7a [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <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_OLD, &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);
}
} // namespace usb
} // namespace audio
extern "C" {
zx_status_t usb_audio_device_bind(void* ctx, zx_device_t* device) {
return audio::usb::UsbAudioDevice::DriverBind(device);
}
void usb_audio_driver_release(void*) {
dispatcher::ThreadPool::ShutdownAll();
}
} // extern "C"