mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-18 12:16:49 +07:00
a4aad5636c
The USB-audio driver may call snd_card_register() multiple times as its probe function is per USB interface while some USB-audio devices may provide multiple interfaces to assign different streams although they belong to the same device. This works in most cases but the registration is racy, hence it may miss the device recognition, e.g. PA doesn't see certain devices when hotplugged. The recent addition of the delayed registration quirk allows to sync the registration at the last known interface, and the previous commit added a new module option to allow the dynamic setup for that purpose. Now, this patch tries to find out and notifies for such devices that require the delayed registration. It shows a message like: Found post-registration device assignment: 1234abcd:02 If you hit this message, you can pass delayed_register module option like: snd_usb_audio.delayed_register=1234abcd:02 by just copying the last shown entry. If this works, it can be added statically in the quirk list, registration_quirks[] found at the end of sound/usb/quirks.c. Link: https://lore.kernel.org/r/20200325103322.2508-4-tiwai@suse.de Signed-off-by: Takashi Iwai <tiwai@suse.de>
948 lines
25 KiB
C
948 lines
25 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* (Tentative) USB Audio Driver for ALSA
|
|
*
|
|
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
|
|
*
|
|
* Many codes borrowed from audio.c by
|
|
* Alan Cox (alan@lxorguk.ukuu.org.uk)
|
|
* Thomas Sailer (sailer@ife.ee.ethz.ch)
|
|
*
|
|
* Audio Class 3.0 support by Ruslan Bilovol <ruslan.bilovol@gmail.com>
|
|
*
|
|
* NOTES:
|
|
*
|
|
* - the linked URBs would be preferred but not used so far because of
|
|
* the instability of unlinking.
|
|
* - type II is not supported properly. there is no device which supports
|
|
* this type *correctly*. SB extigy looks as if it supports, but it's
|
|
* indeed an AC3 stream packed in SPDIF frames (i.e. no real AC3 stream).
|
|
*/
|
|
|
|
|
|
#include <linux/bitops.h>
|
|
#include <linux/init.h>
|
|
#include <linux/list.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/string.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/usb/audio.h>
|
|
#include <linux/usb/audio-v2.h>
|
|
#include <linux/usb/audio-v3.h>
|
|
#include <linux/module.h>
|
|
|
|
#include <sound/control.h>
|
|
#include <sound/core.h>
|
|
#include <sound/info.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/initval.h>
|
|
|
|
#include "usbaudio.h"
|
|
#include "card.h"
|
|
#include "midi.h"
|
|
#include "mixer.h"
|
|
#include "proc.h"
|
|
#include "quirks.h"
|
|
#include "endpoint.h"
|
|
#include "helper.h"
|
|
#include "debug.h"
|
|
#include "pcm.h"
|
|
#include "format.h"
|
|
#include "power.h"
|
|
#include "stream.h"
|
|
#include "media.h"
|
|
|
|
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
|
|
MODULE_DESCRIPTION("USB Audio");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_SUPPORTED_DEVICE("{{Generic,USB Audio}}");
|
|
|
|
|
|
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
|
|
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
|
|
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;/* Enable this card */
|
|
/* Vendor/product IDs for this card */
|
|
static int vid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 };
|
|
static int pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 };
|
|
static int device_setup[SNDRV_CARDS]; /* device parameter for this card */
|
|
static bool ignore_ctl_error;
|
|
static bool autoclock = true;
|
|
static char *quirk_alias[SNDRV_CARDS];
|
|
static char *delayed_register[SNDRV_CARDS];
|
|
|
|
bool snd_usb_use_vmalloc = true;
|
|
bool snd_usb_skip_validation;
|
|
|
|
module_param_array(index, int, NULL, 0444);
|
|
MODULE_PARM_DESC(index, "Index value for the USB audio adapter.");
|
|
module_param_array(id, charp, NULL, 0444);
|
|
MODULE_PARM_DESC(id, "ID string for the USB audio adapter.");
|
|
module_param_array(enable, bool, NULL, 0444);
|
|
MODULE_PARM_DESC(enable, "Enable USB audio adapter.");
|
|
module_param_array(vid, int, NULL, 0444);
|
|
MODULE_PARM_DESC(vid, "Vendor ID for the USB audio device.");
|
|
module_param_array(pid, int, NULL, 0444);
|
|
MODULE_PARM_DESC(pid, "Product ID for the USB audio device.");
|
|
module_param_array(device_setup, int, NULL, 0444);
|
|
MODULE_PARM_DESC(device_setup, "Specific device setup (if needed).");
|
|
module_param(ignore_ctl_error, bool, 0444);
|
|
MODULE_PARM_DESC(ignore_ctl_error,
|
|
"Ignore errors from USB controller for mixer interfaces.");
|
|
module_param(autoclock, bool, 0444);
|
|
MODULE_PARM_DESC(autoclock, "Enable auto-clock selection for UAC2 devices (default: yes).");
|
|
module_param_array(quirk_alias, charp, NULL, 0444);
|
|
MODULE_PARM_DESC(quirk_alias, "Quirk aliases, e.g. 0123abcd:5678beef.");
|
|
module_param_array(delayed_register, charp, NULL, 0444);
|
|
MODULE_PARM_DESC(delayed_register, "Quirk for delayed registration, given by id:iface, e.g. 0123abcd:4.");
|
|
module_param_named(use_vmalloc, snd_usb_use_vmalloc, bool, 0444);
|
|
MODULE_PARM_DESC(use_vmalloc, "Use vmalloc for PCM intermediate buffers (default: yes).");
|
|
module_param_named(skip_validation, snd_usb_skip_validation, bool, 0444);
|
|
MODULE_PARM_DESC(skip_validation, "Skip unit descriptor validation (default: no).");
|
|
|
|
/*
|
|
* we keep the snd_usb_audio_t instances by ourselves for merging
|
|
* the all interfaces on the same card as one sound device.
|
|
*/
|
|
|
|
static DEFINE_MUTEX(register_mutex);
|
|
static struct snd_usb_audio *usb_chip[SNDRV_CARDS];
|
|
static struct usb_driver usb_audio_driver;
|
|
|
|
/*
|
|
* disconnect streams
|
|
* called from usb_audio_disconnect()
|
|
*/
|
|
static void snd_usb_stream_disconnect(struct snd_usb_stream *as)
|
|
{
|
|
int idx;
|
|
struct snd_usb_substream *subs;
|
|
|
|
for (idx = 0; idx < 2; idx++) {
|
|
subs = &as->substream[idx];
|
|
if (!subs->num_formats)
|
|
continue;
|
|
subs->interface = -1;
|
|
subs->data_endpoint = NULL;
|
|
subs->sync_endpoint = NULL;
|
|
}
|
|
}
|
|
|
|
static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int interface)
|
|
{
|
|
struct usb_device *dev = chip->dev;
|
|
struct usb_host_interface *alts;
|
|
struct usb_interface_descriptor *altsd;
|
|
struct usb_interface *iface = usb_ifnum_to_if(dev, interface);
|
|
|
|
if (!iface) {
|
|
dev_err(&dev->dev, "%u:%d : does not exist\n",
|
|
ctrlif, interface);
|
|
return -EINVAL;
|
|
}
|
|
|
|
alts = &iface->altsetting[0];
|
|
altsd = get_iface_desc(alts);
|
|
|
|
/*
|
|
* Android with both accessory and audio interfaces enabled gets the
|
|
* interface numbers wrong.
|
|
*/
|
|
if ((chip->usb_id == USB_ID(0x18d1, 0x2d04) ||
|
|
chip->usb_id == USB_ID(0x18d1, 0x2d05)) &&
|
|
interface == 0 &&
|
|
altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC &&
|
|
altsd->bInterfaceSubClass == USB_SUBCLASS_VENDOR_SPEC) {
|
|
interface = 2;
|
|
iface = usb_ifnum_to_if(dev, interface);
|
|
if (!iface)
|
|
return -EINVAL;
|
|
alts = &iface->altsetting[0];
|
|
altsd = get_iface_desc(alts);
|
|
}
|
|
|
|
if (usb_interface_claimed(iface)) {
|
|
dev_dbg(&dev->dev, "%d:%d: skipping, already claimed\n",
|
|
ctrlif, interface);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if ((altsd->bInterfaceClass == USB_CLASS_AUDIO ||
|
|
altsd->bInterfaceClass == USB_CLASS_VENDOR_SPEC) &&
|
|
altsd->bInterfaceSubClass == USB_SUBCLASS_MIDISTREAMING) {
|
|
int err = __snd_usbmidi_create(chip->card, iface,
|
|
&chip->midi_list, NULL,
|
|
chip->usb_id);
|
|
if (err < 0) {
|
|
dev_err(&dev->dev,
|
|
"%u:%d: cannot create sequencer device\n",
|
|
ctrlif, interface);
|
|
return -EINVAL;
|
|
}
|
|
usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if ((altsd->bInterfaceClass != USB_CLASS_AUDIO &&
|
|
altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
|
|
altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING) {
|
|
dev_dbg(&dev->dev,
|
|
"%u:%d: skipping non-supported interface %d\n",
|
|
ctrlif, interface, altsd->bInterfaceClass);
|
|
/* skip non-supported classes */
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (snd_usb_get_speed(dev) == USB_SPEED_LOW) {
|
|
dev_err(&dev->dev, "low speed audio streaming not supported\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (! snd_usb_parse_audio_interface(chip, interface)) {
|
|
usb_set_interface(dev, interface, 0); /* reset the current interface */
|
|
usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1L);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* parse audio control descriptor and create pcm/midi streams
|
|
*/
|
|
static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
|
|
{
|
|
struct usb_device *dev = chip->dev;
|
|
struct usb_host_interface *host_iface;
|
|
struct usb_interface_descriptor *altsd;
|
|
int i, protocol;
|
|
|
|
/* find audiocontrol interface */
|
|
host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0];
|
|
altsd = get_iface_desc(host_iface);
|
|
protocol = altsd->bInterfaceProtocol;
|
|
|
|
switch (protocol) {
|
|
default:
|
|
dev_warn(&dev->dev,
|
|
"unknown interface protocol %#02x, assuming v1\n",
|
|
protocol);
|
|
/* fall through */
|
|
|
|
case UAC_VERSION_1: {
|
|
struct uac1_ac_header_descriptor *h1;
|
|
int rest_bytes;
|
|
|
|
h1 = snd_usb_find_csint_desc(host_iface->extra,
|
|
host_iface->extralen,
|
|
NULL, UAC_HEADER);
|
|
if (!h1 || h1->bLength < sizeof(*h1)) {
|
|
dev_err(&dev->dev, "cannot find UAC_HEADER\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rest_bytes = (void *)(host_iface->extra +
|
|
host_iface->extralen) - (void *)h1;
|
|
|
|
/* just to be sure -- this shouldn't hit at all */
|
|
if (rest_bytes <= 0) {
|
|
dev_err(&dev->dev, "invalid control header\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (rest_bytes < sizeof(*h1)) {
|
|
dev_err(&dev->dev, "too short v1 buffer descriptor\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!h1->bInCollection) {
|
|
dev_info(&dev->dev, "skipping empty audio interface (v1)\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (rest_bytes < h1->bLength) {
|
|
dev_err(&dev->dev, "invalid buffer length (v1)\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (h1->bLength < sizeof(*h1) + h1->bInCollection) {
|
|
dev_err(&dev->dev, "invalid UAC_HEADER (v1)\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < h1->bInCollection; i++)
|
|
snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]);
|
|
|
|
break;
|
|
}
|
|
|
|
case UAC_VERSION_2:
|
|
case UAC_VERSION_3: {
|
|
struct usb_interface_assoc_descriptor *assoc =
|
|
usb_ifnum_to_if(dev, ctrlif)->intf_assoc;
|
|
|
|
if (!assoc) {
|
|
/*
|
|
* Firmware writers cannot count to three. So to find
|
|
* the IAD on the NuForce UDH-100, also check the next
|
|
* interface.
|
|
*/
|
|
struct usb_interface *iface =
|
|
usb_ifnum_to_if(dev, ctrlif + 1);
|
|
if (iface &&
|
|
iface->intf_assoc &&
|
|
iface->intf_assoc->bFunctionClass == USB_CLASS_AUDIO &&
|
|
iface->intf_assoc->bFunctionProtocol == UAC_VERSION_2)
|
|
assoc = iface->intf_assoc;
|
|
}
|
|
|
|
if (!assoc) {
|
|
dev_err(&dev->dev, "Audio class v2/v3 interfaces need an interface association\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (protocol == UAC_VERSION_3) {
|
|
int badd = assoc->bFunctionSubClass;
|
|
|
|
if (badd != UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0 &&
|
|
(badd < UAC3_FUNCTION_SUBCLASS_GENERIC_IO ||
|
|
badd > UAC3_FUNCTION_SUBCLASS_SPEAKERPHONE)) {
|
|
dev_err(&dev->dev,
|
|
"Unsupported UAC3 BADD profile\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
chip->badd_profile = badd;
|
|
}
|
|
|
|
for (i = 0; i < assoc->bInterfaceCount; i++) {
|
|
int intf = assoc->bFirstInterface + i;
|
|
|
|
if (intf != ctrlif)
|
|
snd_usb_create_stream(chip, ctrlif, intf);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* free the chip instance
|
|
*
|
|
* here we have to do not much, since pcm and controls are already freed
|
|
*
|
|
*/
|
|
|
|
static void snd_usb_audio_free(struct snd_card *card)
|
|
{
|
|
struct snd_usb_audio *chip = card->private_data;
|
|
struct snd_usb_endpoint *ep, *n;
|
|
|
|
list_for_each_entry_safe(ep, n, &chip->ep_list, list)
|
|
snd_usb_endpoint_free(ep);
|
|
|
|
mutex_destroy(&chip->mutex);
|
|
if (!atomic_read(&chip->shutdown))
|
|
dev_set_drvdata(&chip->dev->dev, NULL);
|
|
}
|
|
|
|
static void usb_audio_make_shortname(struct usb_device *dev,
|
|
struct snd_usb_audio *chip,
|
|
const struct snd_usb_audio_quirk *quirk)
|
|
{
|
|
struct snd_card *card = chip->card;
|
|
|
|
if (quirk && quirk->product_name && *quirk->product_name) {
|
|
strlcpy(card->shortname, quirk->product_name,
|
|
sizeof(card->shortname));
|
|
return;
|
|
}
|
|
|
|
/* retrieve the device string as shortname */
|
|
if (!dev->descriptor.iProduct ||
|
|
usb_string(dev, dev->descriptor.iProduct,
|
|
card->shortname, sizeof(card->shortname)) <= 0) {
|
|
/* no name available from anywhere, so use ID */
|
|
sprintf(card->shortname, "USB Device %#04x:%#04x",
|
|
USB_ID_VENDOR(chip->usb_id),
|
|
USB_ID_PRODUCT(chip->usb_id));
|
|
}
|
|
|
|
strim(card->shortname);
|
|
}
|
|
|
|
static void usb_audio_make_longname(struct usb_device *dev,
|
|
struct snd_usb_audio *chip,
|
|
const struct snd_usb_audio_quirk *quirk)
|
|
{
|
|
struct snd_card *card = chip->card;
|
|
int len;
|
|
|
|
/* shortcut - if any pre-defined string is given, use it */
|
|
if (quirk && quirk->profile_name && *quirk->profile_name) {
|
|
strlcpy(card->longname, quirk->profile_name,
|
|
sizeof(card->longname));
|
|
return;
|
|
}
|
|
|
|
if (quirk && quirk->vendor_name && *quirk->vendor_name) {
|
|
len = strlcpy(card->longname, quirk->vendor_name, sizeof(card->longname));
|
|
} else {
|
|
/* retrieve the vendor and device strings as longname */
|
|
if (dev->descriptor.iManufacturer)
|
|
len = usb_string(dev, dev->descriptor.iManufacturer,
|
|
card->longname, sizeof(card->longname));
|
|
else
|
|
len = 0;
|
|
/* we don't really care if there isn't any vendor string */
|
|
}
|
|
if (len > 0) {
|
|
strim(card->longname);
|
|
if (*card->longname)
|
|
strlcat(card->longname, " ", sizeof(card->longname));
|
|
}
|
|
|
|
strlcat(card->longname, card->shortname, sizeof(card->longname));
|
|
|
|
len = strlcat(card->longname, " at ", sizeof(card->longname));
|
|
|
|
if (len < sizeof(card->longname))
|
|
usb_make_path(dev, card->longname + len, sizeof(card->longname) - len);
|
|
|
|
switch (snd_usb_get_speed(dev)) {
|
|
case USB_SPEED_LOW:
|
|
strlcat(card->longname, ", low speed", sizeof(card->longname));
|
|
break;
|
|
case USB_SPEED_FULL:
|
|
strlcat(card->longname, ", full speed", sizeof(card->longname));
|
|
break;
|
|
case USB_SPEED_HIGH:
|
|
strlcat(card->longname, ", high speed", sizeof(card->longname));
|
|
break;
|
|
case USB_SPEED_SUPER:
|
|
strlcat(card->longname, ", super speed", sizeof(card->longname));
|
|
break;
|
|
case USB_SPEED_SUPER_PLUS:
|
|
strlcat(card->longname, ", super speed plus", sizeof(card->longname));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* create a chip instance and set its names.
|
|
*/
|
|
static int snd_usb_audio_create(struct usb_interface *intf,
|
|
struct usb_device *dev, int idx,
|
|
const struct snd_usb_audio_quirk *quirk,
|
|
unsigned int usb_id,
|
|
struct snd_usb_audio **rchip)
|
|
{
|
|
struct snd_card *card;
|
|
struct snd_usb_audio *chip;
|
|
int err;
|
|
char component[14];
|
|
|
|
*rchip = NULL;
|
|
|
|
switch (snd_usb_get_speed(dev)) {
|
|
case USB_SPEED_LOW:
|
|
case USB_SPEED_FULL:
|
|
case USB_SPEED_HIGH:
|
|
case USB_SPEED_WIRELESS:
|
|
case USB_SPEED_SUPER:
|
|
case USB_SPEED_SUPER_PLUS:
|
|
break;
|
|
default:
|
|
dev_err(&dev->dev, "unknown device speed %d\n", snd_usb_get_speed(dev));
|
|
return -ENXIO;
|
|
}
|
|
|
|
err = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE,
|
|
sizeof(*chip), &card);
|
|
if (err < 0) {
|
|
dev_err(&dev->dev, "cannot create card instance %d\n", idx);
|
|
return err;
|
|
}
|
|
|
|
chip = card->private_data;
|
|
mutex_init(&chip->mutex);
|
|
init_waitqueue_head(&chip->shutdown_wait);
|
|
chip->index = idx;
|
|
chip->dev = dev;
|
|
chip->card = card;
|
|
chip->setup = device_setup[idx];
|
|
chip->autoclock = autoclock;
|
|
atomic_set(&chip->active, 1); /* avoid autopm during probing */
|
|
atomic_set(&chip->usage_count, 0);
|
|
atomic_set(&chip->shutdown, 0);
|
|
|
|
chip->usb_id = usb_id;
|
|
INIT_LIST_HEAD(&chip->pcm_list);
|
|
INIT_LIST_HEAD(&chip->ep_list);
|
|
INIT_LIST_HEAD(&chip->midi_list);
|
|
INIT_LIST_HEAD(&chip->mixer_list);
|
|
|
|
card->private_free = snd_usb_audio_free;
|
|
|
|
strcpy(card->driver, "USB-Audio");
|
|
sprintf(component, "USB%04x:%04x",
|
|
USB_ID_VENDOR(chip->usb_id), USB_ID_PRODUCT(chip->usb_id));
|
|
snd_component_add(card, component);
|
|
|
|
usb_audio_make_shortname(dev, chip, quirk);
|
|
usb_audio_make_longname(dev, chip, quirk);
|
|
|
|
snd_usb_audio_create_proc(chip);
|
|
|
|
*rchip = chip;
|
|
return 0;
|
|
}
|
|
|
|
/* look for a matching quirk alias id */
|
|
static bool get_alias_id(struct usb_device *dev, unsigned int *id)
|
|
{
|
|
int i;
|
|
unsigned int src, dst;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(quirk_alias); i++) {
|
|
if (!quirk_alias[i] ||
|
|
sscanf(quirk_alias[i], "%x:%x", &src, &dst) != 2 ||
|
|
src != *id)
|
|
continue;
|
|
dev_info(&dev->dev,
|
|
"device (%04x:%04x): applying quirk alias %04x:%04x\n",
|
|
USB_ID_VENDOR(*id), USB_ID_PRODUCT(*id),
|
|
USB_ID_VENDOR(dst), USB_ID_PRODUCT(dst));
|
|
*id = dst;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool check_delayed_register_option(struct snd_usb_audio *chip, int iface)
|
|
{
|
|
int i;
|
|
unsigned int id, inum;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(delayed_register); i++) {
|
|
if (delayed_register[i] &&
|
|
sscanf(delayed_register[i], "%x:%x", &id, &inum) == 2 &&
|
|
id == chip->usb_id)
|
|
return inum != iface;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static const struct usb_device_id usb_audio_ids[]; /* defined below */
|
|
|
|
/* look for the corresponding quirk */
|
|
static const struct snd_usb_audio_quirk *
|
|
get_alias_quirk(struct usb_device *dev, unsigned int id)
|
|
{
|
|
const struct usb_device_id *p;
|
|
|
|
for (p = usb_audio_ids; p->match_flags; p++) {
|
|
/* FIXME: this checks only vendor:product pair in the list */
|
|
if ((p->match_flags & USB_DEVICE_ID_MATCH_DEVICE) ==
|
|
USB_DEVICE_ID_MATCH_DEVICE &&
|
|
p->idVendor == USB_ID_VENDOR(id) &&
|
|
p->idProduct == USB_ID_PRODUCT(id))
|
|
return (const struct snd_usb_audio_quirk *)p->driver_info;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* probe the active usb device
|
|
*
|
|
* note that this can be called multiple times per a device, when it
|
|
* includes multiple audio control interfaces.
|
|
*
|
|
* thus we check the usb device pointer and creates the card instance
|
|
* only at the first time. the successive calls of this function will
|
|
* append the pcm interface to the corresponding card.
|
|
*/
|
|
static int usb_audio_probe(struct usb_interface *intf,
|
|
const struct usb_device_id *usb_id)
|
|
{
|
|
struct usb_device *dev = interface_to_usbdev(intf);
|
|
const struct snd_usb_audio_quirk *quirk =
|
|
(const struct snd_usb_audio_quirk *)usb_id->driver_info;
|
|
struct snd_usb_audio *chip;
|
|
int i, err;
|
|
struct usb_host_interface *alts;
|
|
int ifnum;
|
|
u32 id;
|
|
|
|
alts = &intf->altsetting[0];
|
|
ifnum = get_iface_desc(alts)->bInterfaceNumber;
|
|
id = USB_ID(le16_to_cpu(dev->descriptor.idVendor),
|
|
le16_to_cpu(dev->descriptor.idProduct));
|
|
if (get_alias_id(dev, &id))
|
|
quirk = get_alias_quirk(dev, id);
|
|
if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum)
|
|
return -ENXIO;
|
|
|
|
err = snd_usb_apply_boot_quirk(dev, intf, quirk, id);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
/*
|
|
* found a config. now register to ALSA
|
|
*/
|
|
|
|
/* check whether it's already registered */
|
|
chip = NULL;
|
|
mutex_lock(®ister_mutex);
|
|
for (i = 0; i < SNDRV_CARDS; i++) {
|
|
if (usb_chip[i] && usb_chip[i]->dev == dev) {
|
|
if (atomic_read(&usb_chip[i]->shutdown)) {
|
|
dev_err(&dev->dev, "USB device is in the shutdown state, cannot create a card instance\n");
|
|
err = -EIO;
|
|
goto __error;
|
|
}
|
|
chip = usb_chip[i];
|
|
atomic_inc(&chip->active); /* avoid autopm */
|
|
break;
|
|
}
|
|
}
|
|
if (! chip) {
|
|
err = snd_usb_apply_boot_quirk_once(dev, intf, quirk, id);
|
|
if (err < 0)
|
|
goto __error;
|
|
|
|
/* it's a fresh one.
|
|
* now look for an empty slot and create a new card instance
|
|
*/
|
|
for (i = 0; i < SNDRV_CARDS; i++)
|
|
if (!usb_chip[i] &&
|
|
(vid[i] == -1 || vid[i] == USB_ID_VENDOR(id)) &&
|
|
(pid[i] == -1 || pid[i] == USB_ID_PRODUCT(id))) {
|
|
if (enable[i]) {
|
|
err = snd_usb_audio_create(intf, dev, i, quirk,
|
|
id, &chip);
|
|
if (err < 0)
|
|
goto __error;
|
|
chip->pm_intf = intf;
|
|
break;
|
|
} else if (vid[i] != -1 || pid[i] != -1) {
|
|
dev_info(&dev->dev,
|
|
"device (%04x:%04x) is disabled\n",
|
|
USB_ID_VENDOR(id),
|
|
USB_ID_PRODUCT(id));
|
|
err = -ENOENT;
|
|
goto __error;
|
|
}
|
|
}
|
|
if (!chip) {
|
|
dev_err(&dev->dev, "no available usb audio device\n");
|
|
err = -ENODEV;
|
|
goto __error;
|
|
}
|
|
}
|
|
dev_set_drvdata(&dev->dev, chip);
|
|
|
|
/*
|
|
* For devices with more than one control interface, we assume the
|
|
* first contains the audio controls. We might need a more specific
|
|
* check here in the future.
|
|
*/
|
|
if (!chip->ctrl_intf)
|
|
chip->ctrl_intf = alts;
|
|
|
|
chip->txfr_quirk = 0;
|
|
err = 1; /* continue */
|
|
if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) {
|
|
/* need some special handlings */
|
|
err = snd_usb_create_quirk(chip, intf, &usb_audio_driver, quirk);
|
|
if (err < 0)
|
|
goto __error;
|
|
}
|
|
|
|
if (err > 0) {
|
|
/* create normal USB audio interfaces */
|
|
err = snd_usb_create_streams(chip, ifnum);
|
|
if (err < 0)
|
|
goto __error;
|
|
err = snd_usb_create_mixer(chip, ifnum, ignore_ctl_error);
|
|
if (err < 0)
|
|
goto __error;
|
|
}
|
|
|
|
if (chip->need_delayed_register) {
|
|
dev_info(&dev->dev,
|
|
"Found post-registration device assignment: %08x:%02x\n",
|
|
chip->usb_id, ifnum);
|
|
chip->need_delayed_register = false; /* clear again */
|
|
}
|
|
|
|
/* we are allowed to call snd_card_register() many times, but first
|
|
* check to see if a device needs to skip it or do anything special
|
|
*/
|
|
if (!snd_usb_registration_quirk(chip, ifnum) &&
|
|
!check_delayed_register_option(chip, ifnum)) {
|
|
err = snd_card_register(chip->card);
|
|
if (err < 0)
|
|
goto __error;
|
|
}
|
|
|
|
if (quirk && quirk->shares_media_device) {
|
|
/* don't want to fail when snd_media_device_create() fails */
|
|
snd_media_device_create(chip, intf);
|
|
}
|
|
|
|
usb_chip[chip->index] = chip;
|
|
chip->num_interfaces++;
|
|
usb_set_intfdata(intf, chip);
|
|
atomic_dec(&chip->active);
|
|
mutex_unlock(®ister_mutex);
|
|
return 0;
|
|
|
|
__error:
|
|
if (chip) {
|
|
/* chip->active is inside the chip->card object,
|
|
* decrement before memory is possibly returned.
|
|
*/
|
|
atomic_dec(&chip->active);
|
|
if (!chip->num_interfaces)
|
|
snd_card_free(chip->card);
|
|
}
|
|
mutex_unlock(®ister_mutex);
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* we need to take care of counter, since disconnection can be called also
|
|
* many times as well as usb_audio_probe().
|
|
*/
|
|
static void usb_audio_disconnect(struct usb_interface *intf)
|
|
{
|
|
struct snd_usb_audio *chip = usb_get_intfdata(intf);
|
|
struct snd_card *card;
|
|
struct list_head *p;
|
|
|
|
if (chip == (void *)-1L)
|
|
return;
|
|
|
|
card = chip->card;
|
|
|
|
mutex_lock(®ister_mutex);
|
|
if (atomic_inc_return(&chip->shutdown) == 1) {
|
|
struct snd_usb_stream *as;
|
|
struct snd_usb_endpoint *ep;
|
|
struct usb_mixer_interface *mixer;
|
|
|
|
/* wait until all pending tasks done;
|
|
* they are protected by snd_usb_lock_shutdown()
|
|
*/
|
|
wait_event(chip->shutdown_wait,
|
|
!atomic_read(&chip->usage_count));
|
|
snd_card_disconnect(card);
|
|
/* release the pcm resources */
|
|
list_for_each_entry(as, &chip->pcm_list, list) {
|
|
snd_usb_stream_disconnect(as);
|
|
}
|
|
/* release the endpoint resources */
|
|
list_for_each_entry(ep, &chip->ep_list, list) {
|
|
snd_usb_endpoint_release(ep);
|
|
}
|
|
/* release the midi resources */
|
|
list_for_each(p, &chip->midi_list) {
|
|
snd_usbmidi_disconnect(p);
|
|
}
|
|
/*
|
|
* Nice to check quirk && quirk->shares_media_device and
|
|
* then call the snd_media_device_delete(). Don't have
|
|
* access to the quirk here. snd_media_device_delete()
|
|
* accesses mixer_list
|
|
*/
|
|
snd_media_device_delete(chip);
|
|
|
|
/* release mixer resources */
|
|
list_for_each_entry(mixer, &chip->mixer_list, list) {
|
|
snd_usb_mixer_disconnect(mixer);
|
|
}
|
|
}
|
|
|
|
chip->num_interfaces--;
|
|
if (chip->num_interfaces <= 0) {
|
|
usb_chip[chip->index] = NULL;
|
|
mutex_unlock(®ister_mutex);
|
|
snd_card_free_when_closed(card);
|
|
} else {
|
|
mutex_unlock(®ister_mutex);
|
|
}
|
|
}
|
|
|
|
/* lock the shutdown (disconnect) task and autoresume */
|
|
int snd_usb_lock_shutdown(struct snd_usb_audio *chip)
|
|
{
|
|
int err;
|
|
|
|
atomic_inc(&chip->usage_count);
|
|
if (atomic_read(&chip->shutdown)) {
|
|
err = -EIO;
|
|
goto error;
|
|
}
|
|
err = snd_usb_autoresume(chip);
|
|
if (err < 0)
|
|
goto error;
|
|
return 0;
|
|
|
|
error:
|
|
if (atomic_dec_and_test(&chip->usage_count))
|
|
wake_up(&chip->shutdown_wait);
|
|
return err;
|
|
}
|
|
|
|
/* autosuspend and unlock the shutdown */
|
|
void snd_usb_unlock_shutdown(struct snd_usb_audio *chip)
|
|
{
|
|
snd_usb_autosuspend(chip);
|
|
if (atomic_dec_and_test(&chip->usage_count))
|
|
wake_up(&chip->shutdown_wait);
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
int snd_usb_autoresume(struct snd_usb_audio *chip)
|
|
{
|
|
if (atomic_read(&chip->shutdown))
|
|
return -EIO;
|
|
if (atomic_inc_return(&chip->active) == 1)
|
|
return usb_autopm_get_interface(chip->pm_intf);
|
|
return 0;
|
|
}
|
|
|
|
void snd_usb_autosuspend(struct snd_usb_audio *chip)
|
|
{
|
|
if (atomic_read(&chip->shutdown))
|
|
return;
|
|
if (atomic_dec_and_test(&chip->active))
|
|
usb_autopm_put_interface(chip->pm_intf);
|
|
}
|
|
|
|
static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message)
|
|
{
|
|
struct snd_usb_audio *chip = usb_get_intfdata(intf);
|
|
struct snd_usb_stream *as;
|
|
struct usb_mixer_interface *mixer;
|
|
struct list_head *p;
|
|
|
|
if (chip == (void *)-1L)
|
|
return 0;
|
|
|
|
chip->autosuspended = !!PMSG_IS_AUTO(message);
|
|
if (!chip->autosuspended)
|
|
snd_power_change_state(chip->card, SNDRV_CTL_POWER_D3hot);
|
|
if (!chip->num_suspended_intf++) {
|
|
list_for_each_entry(as, &chip->pcm_list, list) {
|
|
snd_usb_pcm_suspend(as);
|
|
as->substream[0].need_setup_ep =
|
|
as->substream[1].need_setup_ep = true;
|
|
}
|
|
list_for_each(p, &chip->midi_list)
|
|
snd_usbmidi_suspend(p);
|
|
list_for_each_entry(mixer, &chip->mixer_list, list)
|
|
snd_usb_mixer_suspend(mixer);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __usb_audio_resume(struct usb_interface *intf, bool reset_resume)
|
|
{
|
|
struct snd_usb_audio *chip = usb_get_intfdata(intf);
|
|
struct snd_usb_stream *as;
|
|
struct usb_mixer_interface *mixer;
|
|
struct list_head *p;
|
|
int err = 0;
|
|
|
|
if (chip == (void *)-1L)
|
|
return 0;
|
|
if (--chip->num_suspended_intf)
|
|
return 0;
|
|
|
|
atomic_inc(&chip->active); /* avoid autopm */
|
|
|
|
list_for_each_entry(as, &chip->pcm_list, list) {
|
|
err = snd_usb_pcm_resume(as);
|
|
if (err < 0)
|
|
goto err_out;
|
|
}
|
|
|
|
/*
|
|
* ALSA leaves material resumption to user space
|
|
* we just notify and restart the mixers
|
|
*/
|
|
list_for_each_entry(mixer, &chip->mixer_list, list) {
|
|
err = snd_usb_mixer_resume(mixer, reset_resume);
|
|
if (err < 0)
|
|
goto err_out;
|
|
}
|
|
|
|
list_for_each(p, &chip->midi_list) {
|
|
snd_usbmidi_resume(p);
|
|
}
|
|
|
|
if (!chip->autosuspended)
|
|
snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0);
|
|
chip->autosuspended = 0;
|
|
|
|
err_out:
|
|
atomic_dec(&chip->active); /* allow autopm after this point */
|
|
return err;
|
|
}
|
|
|
|
static int usb_audio_resume(struct usb_interface *intf)
|
|
{
|
|
return __usb_audio_resume(intf, false);
|
|
}
|
|
|
|
static int usb_audio_reset_resume(struct usb_interface *intf)
|
|
{
|
|
return __usb_audio_resume(intf, true);
|
|
}
|
|
#else
|
|
#define usb_audio_suspend NULL
|
|
#define usb_audio_resume NULL
|
|
#define usb_audio_reset_resume NULL
|
|
#endif /* CONFIG_PM */
|
|
|
|
static const struct usb_device_id usb_audio_ids [] = {
|
|
#include "quirks-table.h"
|
|
{ .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS),
|
|
.bInterfaceClass = USB_CLASS_AUDIO,
|
|
.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL },
|
|
{ } /* Terminating entry */
|
|
};
|
|
MODULE_DEVICE_TABLE(usb, usb_audio_ids);
|
|
|
|
/*
|
|
* entry point for linux usb interface
|
|
*/
|
|
|
|
static struct usb_driver usb_audio_driver = {
|
|
.name = "snd-usb-audio",
|
|
.probe = usb_audio_probe,
|
|
.disconnect = usb_audio_disconnect,
|
|
.suspend = usb_audio_suspend,
|
|
.resume = usb_audio_resume,
|
|
.reset_resume = usb_audio_reset_resume,
|
|
.id_table = usb_audio_ids,
|
|
.supports_autosuspend = 1,
|
|
};
|
|
|
|
module_usb_driver(usb_audio_driver);
|