From 1f93a7dfd1f8d5ff7a5c53246c7534fe2332d6f4 Mon Sep 17 00:00:00 2001 From: hc <hc@nodka.com> Date: Mon, 11 Dec 2023 02:46:07 +0000 Subject: [PATCH] add audio --- kernel/sound/usb/card.c | 407 +++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 files changed, 357 insertions(+), 50 deletions(-) diff --git a/kernel/sound/usb/card.c b/kernel/sound/usb/card.c index 861c041..ee5d28a 100644 --- a/kernel/sound/usb/card.c +++ b/kernel/sound/usb/card.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * (Tentative) USB Audio Driver for ALSA * @@ -8,21 +9,6 @@ * Thomas Sailer (sailer@ife.ee.ethz.ch) * * Audio Class 3.0 support by Ruslan Bilovol <ruslan.bilovol@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * * * NOTES: * @@ -68,6 +54,7 @@ #include "format.h" #include "power.h" #include "stream.h" +#include "media.h" MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>"); MODULE_DESCRIPTION("USB Audio"); @@ -85,8 +72,10 @@ 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."); @@ -107,8 +96,12 @@ 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 @@ -119,15 +112,119 @@ static struct snd_usb_audio *usb_chip[SNDRV_CARDS]; static struct usb_driver usb_audio_driver; -/** - * find_snd_usb_substream - helper API to find usb substream context - * information using card number, pcm device number and direction. - * @card_num: card number - * @pcm_idx: pcm device number - * @direction: SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE - * @uchip: substream context. - * disconnect_cb: callback to use for cleanup on disconnect. - */ +static struct snd_usb_audio_vendor_ops *usb_vendor_ops; + +int snd_vendor_set_ops(struct snd_usb_audio_vendor_ops *ops) +{ + if ((!ops->connect) || + (!ops->disconnect) || + (!ops->set_interface) || + (!ops->set_rate) || + (!ops->set_pcm_buf) || + (!ops->set_pcm_intf) || + (!ops->set_pcm_connection) || + (!ops->set_pcm_binterval) || + (!ops->usb_add_ctls)) + return -EINVAL; + + usb_vendor_ops = ops; + return 0; +} +EXPORT_SYMBOL_GPL(snd_vendor_set_ops); + +struct snd_usb_audio_vendor_ops *snd_vendor_get_ops(void) +{ + return usb_vendor_ops; +} + +static int snd_vendor_connect(struct usb_interface *intf) +{ + struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops(); + + if (ops) + return ops->connect(intf); + return 0; +} + +static void snd_vendor_disconnect(struct usb_interface *intf) +{ + struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops(); + + if (ops) + ops->disconnect(intf); +} + +int snd_vendor_set_interface(struct usb_device *udev, + struct usb_host_interface *intf, + int iface, int alt) +{ + struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops(); + + if (ops) + return ops->set_interface(udev, intf, iface, alt); + return 0; +} + +int snd_vendor_set_rate(struct usb_interface *intf, int iface, int rate, + int alt) +{ + struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops(); + + if (ops) + return ops->set_rate(intf, iface, rate, alt); + return 0; +} + +int snd_vendor_set_pcm_buf(struct usb_device *udev, int iface) +{ + struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops(); + + if (ops) + ops->set_pcm_buf(udev, iface); + return 0; +} + +int snd_vendor_set_pcm_intf(struct usb_interface *intf, int iface, int alt, + int direction) +{ + struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops(); + + if (ops) + return ops->set_pcm_intf(intf, iface, alt, direction); + return 0; +} + +int snd_vendor_set_pcm_connection(struct usb_device *udev, + enum snd_vendor_pcm_open_close onoff, + int direction) +{ + struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops(); + + if (ops) + return ops->set_pcm_connection(udev, onoff, direction); + return 0; +} + +int snd_vendor_set_pcm_binterval(struct audioformat *fp, + struct audioformat *found, + int *cur_attr, int *attr) +{ + struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops(); + + if (ops) + return ops->set_pcm_binterval(fp, found, cur_attr, attr); + return 0; +} + +static int snd_vendor_usb_add_ctls(struct snd_usb_audio *chip) +{ + struct snd_usb_audio_vendor_ops *ops = snd_vendor_get_ops(); + + if (ops) + return ops->usb_add_ctls(chip); + return 0; +} + struct snd_usb_substream *find_snd_usb_substream(unsigned int card_num, unsigned int pcm_idx, unsigned int direction, struct snd_usb_audio **uchip, void (*disconnect_cb)(struct snd_usb_audio *chip)) @@ -143,7 +240,7 @@ * search using chip->card->number */ for (idx = 0; idx < SNDRV_CARDS; idx++) { - if (!usb_chip[idx] || !usb_chip[idx]->card) + if (!usb_chip[idx]) continue; if (usb_chip[idx]->card->number == card_num) { chip = usb_chip[idx]; @@ -152,20 +249,19 @@ } if (!chip || atomic_read(&chip->shutdown)) { - pr_debug("%s: instance of usb card # %d does not exist\n", + pr_debug("%s: instance of usb crad # %d does not exist\n", __func__, card_num); goto err; } if (pcm_idx >= chip->pcm_devs) { - usb_audio_err(chip, "%s: invalid pcm dev number %u > %d\n", - __func__, pcm_idx, chip->pcm_devs); + pr_err("%s: invalid pcm dev number %u > %d\n", __func__, + pcm_idx, chip->pcm_devs); goto err; } if (direction > SNDRV_PCM_STREAM_CAPTURE) { - usb_audio_err(chip, "%s: invalid direction %u\n", __func__, - direction); + pr_err("%s: invalid direction %u\n", __func__, direction); goto err; } @@ -173,9 +269,9 @@ if (as->pcm_index == pcm_idx) { subs = &as->substream[direction]; if (subs->interface < 0 && !subs->data_endpoint && - !subs->sync_endpoint) { - usb_audio_err(chip, "%s: stream disconnected, bail out\n", - __func__); + !subs->sync_endpoint) { + pr_debug("%s: stream disconnected, bail out\n", + __func__); subs = NULL; goto err; } @@ -189,7 +285,7 @@ err: *uchip = chip; if (!subs) - pr_err("%s: substream instance not found\n", __func__); + pr_debug("%s: substream instance not found\n", __func__); mutex_unlock(®ister_mutex); return subs; } @@ -313,7 +409,7 @@ dev_warn(&dev->dev, "unknown interface protocol %#02x, assuming v1\n", protocol); - /* fall through */ + fallthrough; case UAC_VERSION_1: { struct uac1_ac_header_descriptor *h1; @@ -416,6 +512,121 @@ } /* + * Profile name preset table + */ +struct usb_audio_device_name { + u32 id; + const char *vendor_name; + const char *product_name; + const char *profile_name; /* override card->longname */ +}; + +#define PROFILE_NAME(vid, pid, vendor, product, profile) \ + { .id = USB_ID(vid, pid), .vendor_name = (vendor), \ + .product_name = (product), .profile_name = (profile) } +#define DEVICE_NAME(vid, pid, vendor, product) \ + PROFILE_NAME(vid, pid, vendor, product, NULL) + +/* vendor/product and profile name presets, sorted in device id order */ +static const struct usb_audio_device_name usb_audio_names[] = { + /* HP Thunderbolt Dock Audio Headset */ + PROFILE_NAME(0x03f0, 0x0269, "HP", "Thunderbolt Dock Audio Headset", + "HP-Thunderbolt-Dock-Audio-Headset"), + /* HP Thunderbolt Dock Audio Module */ + PROFILE_NAME(0x03f0, 0x0567, "HP", "Thunderbolt Dock Audio Module", + "HP-Thunderbolt-Dock-Audio-Module"), + + /* Two entries for Gigabyte TRX40 Aorus Master: + * TRX40 Aorus Master has two USB-audio devices, one for the front + * headphone with ESS SABRE9218 DAC chip, while another for the rest + * I/O (the rear panel and the front mic) with Realtek ALC1220-VB. + * Here we provide two distinct names for making UCM profiles easier. + */ + PROFILE_NAME(0x0414, 0xa000, "Gigabyte", "Aorus Master Front Headphone", + "Gigabyte-Aorus-Master-Front-Headphone"), + PROFILE_NAME(0x0414, 0xa001, "Gigabyte", "Aorus Master Main Audio", + "Gigabyte-Aorus-Master-Main-Audio"), + + /* Gigabyte TRX40 Aorus Pro WiFi */ + PROFILE_NAME(0x0414, 0xa002, + "Realtek", "ALC1220-VB-DT", "Realtek-ALC1220-VB-Desktop"), + + /* Creative/E-Mu devices */ + DEVICE_NAME(0x041e, 0x3010, "Creative Labs", "Sound Blaster MP3+"), + /* Creative/Toshiba Multimedia Center SB-0500 */ + DEVICE_NAME(0x041e, 0x3048, "Toshiba", "SB-0500"), + + DEVICE_NAME(0x046d, 0x0990, "Logitech, Inc.", "QuickCam Pro 9000"), + + /* ASUS ROG Zenith II: this machine has also two devices, one for + * the front headphone and another for the rest + */ + PROFILE_NAME(0x0b05, 0x1915, "ASUS", "Zenith II Front Headphone", + "Zenith-II-Front-Headphone"), + PROFILE_NAME(0x0b05, 0x1916, "ASUS", "Zenith II Main Audio", + "Zenith-II-Main-Audio"), + + /* ASUS ROG Strix */ + PROFILE_NAME(0x0b05, 0x1917, + "Realtek", "ALC1220-VB-DT", "Realtek-ALC1220-VB-Desktop"), + /* ASUS PRIME TRX40 PRO-S */ + PROFILE_NAME(0x0b05, 0x1918, + "Realtek", "ALC1220-VB-DT", "Realtek-ALC1220-VB-Desktop"), + + /* Dell WD15 Dock */ + PROFILE_NAME(0x0bda, 0x4014, "Dell", "WD15 Dock", "Dell-WD15-Dock"), + /* Dell WD19 Dock */ + PROFILE_NAME(0x0bda, 0x402e, "Dell", "WD19 Dock", "Dell-WD15-Dock"), + + DEVICE_NAME(0x0ccd, 0x0028, "TerraTec", "Aureon5.1MkII"), + + /* + * The original product_name is "USB Sound Device", however this name + * is also used by the CM106 based cards, so make it unique. + */ + DEVICE_NAME(0x0d8c, 0x0102, NULL, "ICUSBAUDIO7D"), + DEVICE_NAME(0x0d8c, 0x0103, NULL, "Audio Advantage MicroII"), + + /* MSI TRX40 Creator */ + PROFILE_NAME(0x0db0, 0x0d64, + "Realtek", "ALC1220-VB-DT", "Realtek-ALC1220-VB-Desktop"), + /* MSI TRX40 */ + PROFILE_NAME(0x0db0, 0x543d, + "Realtek", "ALC1220-VB-DT", "Realtek-ALC1220-VB-Desktop"), + + /* Stanton/N2IT Final Scratch v1 device ('Scratchamp') */ + DEVICE_NAME(0x103d, 0x0100, "Stanton", "ScratchAmp"), + DEVICE_NAME(0x103d, 0x0101, "Stanton", "ScratchAmp"), + + /* aka. Serato Scratch Live DJ Box */ + DEVICE_NAME(0x13e5, 0x0001, "Rane", "SL-1"), + + /* Lenovo ThinkStation P620 Rear Line-in, Line-out and Microphone */ + PROFILE_NAME(0x17aa, 0x1046, "Lenovo", "ThinkStation P620 Rear", + "Lenovo-ThinkStation-P620-Rear"), + /* Lenovo ThinkStation P620 Internal Speaker + Front Headset */ + PROFILE_NAME(0x17aa, 0x104d, "Lenovo", "ThinkStation P620 Main", + "Lenovo-ThinkStation-P620-Main"), + + /* Asrock TRX40 Creator */ + PROFILE_NAME(0x26ce, 0x0a01, + "Realtek", "ALC1220-VB-DT", "Realtek-ALC1220-VB-Desktop"), + + { } /* terminator */ +}; + +static const struct usb_audio_device_name * +lookup_device_name(u32 id) +{ + static const struct usb_audio_device_name *p; + + for (p = usb_audio_names; p->id; p++) + if (p->id == id) + return p; + return NULL; +} + +/* * free the chip instance * * here we have to do not much, since pcm and controls are already freed @@ -441,10 +652,16 @@ const struct snd_usb_audio_quirk *quirk) { struct snd_card *card = chip->card; + const struct usb_audio_device_name *preset; + const char *s = NULL; - if (quirk && quirk->product_name && *quirk->product_name) { - strlcpy(card->shortname, quirk->product_name, - sizeof(card->shortname)); + preset = lookup_device_name(chip->usb_id); + if (preset && preset->product_name) + s = preset->product_name; + else if (quirk && quirk->product_name) + s = quirk->product_name; + if (s && *s) { + strlcpy(card->shortname, s, sizeof(card->shortname)); return; } @@ -466,17 +683,26 @@ const struct snd_usb_audio_quirk *quirk) { struct snd_card *card = chip->card; + const struct usb_audio_device_name *preset; + const char *s = NULL; int len; + preset = lookup_device_name(chip->usb_id); + /* 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)); + if (preset && preset->profile_name) + s = preset->profile_name; + if (s && *s) { + strlcpy(card->longname, s, sizeof(card->longname)); return; } - if (quirk && quirk->vendor_name && *quirk->vendor_name) { - len = strlcpy(card->longname, quirk->vendor_name, sizeof(card->longname)); + if (preset && preset->vendor_name) + s = preset->vendor_name; + else if (quirk && quirk->vendor_name) + s = quirk->vendor_name; + if (s && *s) { + len = strlcpy(card->longname, s, sizeof(card->longname)); } else { /* retrieve the vendor and device strings as longname */ if (dev->descriptor.iManufacturer) @@ -613,6 +839,21 @@ 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 iface < inum; + } + + return false; +} + static const struct usb_device_id usb_audio_ids[]; /* defined below */ /* look for the corresponding quirk */ @@ -668,6 +909,10 @@ if (err < 0) return err; + err = snd_vendor_connect(intf); + if (err) + return err; + /* * found a config. now register to ALSA */ @@ -688,6 +933,10 @@ } } 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 */ @@ -700,7 +949,6 @@ id, &chip); if (err < 0) goto __error; - chip->pm_intf = intf; break; } else if (vid[i] != -1 || pid[i] != -1) { dev_info(&dev->dev, @@ -717,7 +965,16 @@ goto __error; } } + + if (chip->num_interfaces >= MAX_CARD_INTERFACES) { + dev_info(&dev->dev, "Too many interfaces assigned to the single USB-audio card\n"); + err = -EINVAL; + goto __error; + } + dev_set_drvdata(&dev->dev, chip); + + snd_vendor_usb_add_ctls(chip); /* * For devices with more than one control interface, we assume the @@ -746,16 +1003,33 @@ 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)) { + 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); + } + + if (quirk) + chip->quirk_type = quirk->type; + usb_chip[chip->index] = chip; + chip->intf[chip->num_interfaces] = intf; chip->num_interfaces++; usb_set_intfdata(intf, chip); atomic_dec(&chip->active); @@ -790,6 +1064,11 @@ card = chip->card; + if (chip->disconnect_cb) + chip->disconnect_cb(chip); + + snd_vendor_disconnect(intf); + mutex_lock(®ister_mutex); if (atomic_inc_return(&chip->shutdown) == 1) { struct snd_usb_stream *as; @@ -814,11 +1093,22 @@ 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); } } + + if (chip->quirk_type == QUIRK_SETUP_DISABLE_AUTOSUSPEND) + usb_enable_autosuspend(interface_to_usbdev(intf)); chip->num_interfaces--; if (chip->num_interfaces <= 0) { @@ -863,19 +1153,37 @@ int snd_usb_autoresume(struct snd_usb_audio *chip) { + int i, err; + if (atomic_read(&chip->shutdown)) return -EIO; - if (atomic_inc_return(&chip->active) == 1) - return usb_autopm_get_interface(chip->pm_intf); + if (atomic_inc_return(&chip->active) != 1) + return 0; + + for (i = 0; i < chip->num_interfaces; i++) { + err = usb_autopm_get_interface(chip->intf[i]); + if (err < 0) { + /* rollback */ + while (--i >= 0) + usb_autopm_put_interface(chip->intf[i]); + atomic_dec(&chip->active); + return err; + } + } return 0; } void snd_usb_autosuspend(struct snd_usb_audio *chip) { + int i; + if (atomic_read(&chip->shutdown)) return; - if (atomic_dec_and_test(&chip->active)) - usb_autopm_put_interface(chip->pm_intf); + if (!atomic_dec_and_test(&chip->active)) + return; + + for (i = 0; i < chip->num_interfaces; i++) + usb_autopm_put_interface(chip->intf[i]); } static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) @@ -890,7 +1198,6 @@ if (!chip->num_suspended_intf++) { list_for_each_entry(as, &chip->pcm_list, list) { - snd_pcm_suspend_all(as->pcm); snd_usb_pcm_suspend(as); as->substream[0].need_setup_ep = as->substream[1].need_setup_ep = true; -- Gitblit v1.6.2