Linux Sound subsystem development
 help / color / mirror / Atom feed
From: Cezary Rojewski <cezary.rojewski@intel.com>
To: broonie@kernel.org, tiwai@suse.com, perex@perex.cz
Cc: amadeuszx.slawinski@linux.intel.com, linux-sound@vger.kernel.org,
	gregkh@linuxfoundation.org, quic_wcheng@quicinc.com,
	mathias.nyman@linux.intel.com,
	Cezary Rojewski <cezary.rojewski@intel.com>
Subject: [RFC 06/15] ALSA: usb: Implement two-stage chip probing mechanism
Date: Wed,  9 Apr 2025 13:07:21 +0200	[thread overview]
Message-ID: <20250409110731.3752332-7-cezary.rojewski@intel.com> (raw)
In-Reply-To: <20250409110731.3752332-1-cezary.rojewski@intel.com>

The USB Audio-Class (AC) device driver on ASoC side needs to know the
number of available PCM streams when creating list of DAI and DAI Links
before these objects are actually probed. To achieve that split existing
USB stream enumeration procedure from one-stage to two-stage process:

1) parse the descriptors and obtain information about PCMs but do not
   create them yet
2) create all PCMs based on the previously obtained information and
   follow that up with MIDIs, mixers and Media resources

As number of changes is large, the process is not part of a single
patch. The switch is done in follow ups.

Existing usb_audio_probe() is a non-atomic function that performs a
large number of operations. These can be represented with:

	snd_usb_request_chip()
		snd_usb_find_chip()
		snd_usb_alloc_slot()
		snd_usb_new_chip()
		snd_usb_chip_init()
	snd_usb_chip_release()
	snd_usb_sanity_check()
	snd_usb_create_card()

All the steps done before stream creation are performed in first-stage
probing function - snd_usb_probe_unlocked(). As ASoC is responsible for
calling snd_card_new(), the component driver has to bind the card to USB
device manually. Helper snd_usb_bind_card() addresses that need.

Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
 include/linux/usb.h     |  15 +++
 include/linux/usb/ch9.h |  11 ++
 include/sound/usb.h     |   2 +
 sound/usb/card.c        | 289 +++++++++++++++++++++++++++++++++++++++-
 4 files changed, 316 insertions(+), 1 deletion(-)

diff --git a/include/linux/usb.h b/include/linux/usb.h
index cfa8005e24f9..8e2f9eb93823 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -749,6 +749,21 @@ static inline const struct usb_device *__intf_to_usbdev_const(const struct usb_i
 		 const struct usb_interface *: __intf_to_usbdev_const,	\
 		 struct usb_interface *: __intf_to_usbdev)(intf)
 
+static inline u16 usb_device_vid(struct usb_device *udev)
+{
+	return le16_to_cpu(udev->descriptor.idVendor);
+}
+
+static inline u16 usb_device_pid(struct usb_device *udev)
+{
+	return le16_to_cpu(udev->descriptor.idProduct);
+}
+
+static inline u32 usb_device_vpid(struct usb_device *udev)
+{
+	return ((u32)usb_device_vid(udev) << 16) | usb_device_pid(udev);
+}
+
 extern struct usb_device *usb_get_dev(struct usb_device *dev);
 extern void usb_put_dev(struct usb_device *dev);
 extern struct usb_device *usb_hub_find_child(struct usb_device *hdev,
diff --git a/include/linux/usb/ch9.h b/include/linux/usb/ch9.h
index c93b410b314a..e0f4d9bbc8fa 100644
--- a/include/linux/usb/ch9.h
+++ b/include/linux/usb/ch9.h
@@ -57,4 +57,15 @@ extern const char *usb_decode_ctrl(char *str, size_t size, __u8 bRequestType,
 				   __u16 wLength);
 #endif
 
+/**
+ * usb_epaddr_num - extract endpoint's number from its address
+ * @epaddr: bEndpointAddress of the USB endpoint
+ *
+ * Returns endpoint number: 0 to 15.
+ */
+static inline int usb_epaddr_num(unsigned char epaddr)
+{
+	return epaddr & USB_ENDPOINT_NUMBER_MASK;
+}
+
 #endif /* __LINUX_USB_CH9_H */
diff --git a/include/sound/usb.h b/include/sound/usb.h
index c50fc61f357c..f30a8a96c49e 100644
--- a/include/sound/usb.h
+++ b/include/sound/usb.h
@@ -90,6 +90,8 @@ struct snd_usb_audio {
 	struct snd_intf_to_ctrl intf_to_ctrl[MAX_CARD_INTERFACES];
 };
 
+int snd_usb_bind_card(struct snd_usb_audio *chip, struct snd_card *card,
+		      struct usb_driver *driver);
 int snd_usb_bind_pcm(struct list_head *stream_entry, struct snd_pcm *pcm);
 
 #define USB_AUDIO_IFACE_UNUSED	((void *)-1L)
diff --git a/sound/usb/card.c b/sound/usb/card.c
index 4121ad03acd1..a9e37fe0d29e 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -841,7 +841,6 @@ get_alias_quirk(struct usb_device *dev, unsigned int id)
 	return NULL;
 }
 
-__maybe_unused
 static int snd_usb_parse_pcms(struct snd_usb_audio *chip, struct usb_interface *iface,
 			      struct usb_driver *driver)
 {
@@ -918,6 +917,294 @@ static void snd_usb_chip_free(struct snd_usb_audio *chip)
 	kfree(chip);
 }
 
+/* Must be called under register_mutex due to usb_chip[] manipulation. */
+static void snd_usb_chip_release(struct snd_usb_audio *chip, struct usb_interface *iface)
+{
+	chip->num_interfaces--;
+	chip->intf[chip->num_interfaces] = NULL;
+	usb_set_intfdata(iface, NULL);
+
+	if (!chip->num_interfaces) {
+		usb_chip[chip->index] = NULL;
+		if (chip->card)
+			snd_card_free(chip->card);
+		snd_usb_chip_free(chip);
+	}
+}
+
+/*
+ * Search usb_chip[] for a chip instance matching the specified @udev.
+ * If found, increase chip->active before returning to block autosuspend.
+ */
+static struct snd_usb_audio *snd_usb_find_chip(struct usb_device *udev)
+{
+	struct snd_usb_audio *chip;
+	int i;
+
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		chip = usb_chip[i];
+
+		if (!chip || chip->dev != udev)
+			continue;
+		if (atomic_read(&chip->shutdown)) {
+			dev_err(&udev->dev, "USB device is in the shutdown state, cannot create a card instance\n");
+			return ERR_PTR(-EIO);
+		}
+
+		/* Avoid autosuspend during probe. */
+		atomic_inc(&chip->active);
+		return chip;
+	}
+
+	return NULL;
+}
+
+static int snd_usb_alloc_slot(struct usb_device *udev)
+{
+	u16 product = usb_device_pid(udev);
+	u16 vendor = usb_device_vid(udev);
+	int i;
+
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (usb_chip[i])
+			continue;
+		if ((vid[i] == -1 || vid[i] == vendor) &&
+		    (pid[i] == -1 || pid[i] == product)) {
+			if (enable[i])
+				return i;
+			if (vid[i] == vendor || pid[i] == product) {
+				dev_info(&udev->dev, "device (%04x:%04x) is disabled\n",
+					 vendor, product);
+				return -ENOENT;
+			}
+		}
+	}
+
+	return -EBUSY;
+}
+
+static void snd_usb_chip_init(struct snd_usb_audio *chip, struct usb_interface *iface, int idx,
+			      const struct snd_usb_audio_quirk *quirk, u32 id)
+{
+	struct usb_device *udev = interface_to_usbdev(iface);
+
+	chip->index = idx;
+	chip->dev = udev;
+	chip->setup = device_setup[idx];
+	chip->generic_implicit_fb = implicit_fb[idx];
+	chip->autoclock = autoclock;
+	chip->lowlatency = lowlatency;
+	chip->usb_id = id;
+	mutex_init(&chip->mutex);
+	init_waitqueue_head(&chip->shutdown_wait);
+
+	/* Avoid autosuspend during probe. */
+	atomic_set(&chip->active, 1);
+	atomic_set(&chip->usage_count, 0);
+	atomic_set(&chip->shutdown, 0);
+	INIT_LIST_HEAD(&chip->pcm_list);
+	INIT_LIST_HEAD(&chip->ep_list);
+	INIT_LIST_HEAD(&chip->iface_ref_list);
+	INIT_LIST_HEAD(&chip->clock_ref_list);
+	INIT_LIST_HEAD(&chip->midi_list);
+	INIT_LIST_HEAD(&chip->midi_v2_list);
+	INIT_LIST_HEAD(&chip->mixer_list);
+
+	/* Assign quirk-related information. */
+	chip->quirk = quirk;
+	if (quirk)
+		chip->quirk_type = quirk->type;
+	if (quirk_flags[idx])
+		chip->quirk_flags = quirk_flags[idx];
+	else
+		snd_usb_init_quirk_flags(chip);
+	if (ignore_ctl_error)
+		chip->quirk_flags |= QUIRK_FLAG_IGNORE_CTL_ERROR;
+
+	find_last_interface(chip);
+}
+
+/* Leaves chip->active at 1. */
+static struct snd_usb_audio *snd_usb_new_chip(struct usb_interface *iface, int idx,
+					      const struct snd_usb_audio_quirk *quirk, u32 id)
+{
+	struct snd_usb_audio *chip;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return NULL;
+
+	snd_usb_chip_init(chip, iface, idx, quirk, id);
+	return chip;
+}
+
+/*
+ * Must be called under register_mutex due to usb_chip[] manipulation.
+ *
+ * Regardless if existing chip is found or new one is created, chip->active
+ * is updated to avoid autosuspend.
+ */
+static struct snd_usb_audio *snd_usb_request_chip(struct usb_interface *iface,
+						  const struct snd_usb_audio_quirk *quirk, u32 id)
+{
+	struct usb_device *udev = interface_to_usbdev(iface);
+	struct snd_usb_audio *chip;
+	int slot, ret;
+
+	chip = snd_usb_find_chip(udev);
+	if (IS_ERR(chip))
+		return chip;
+
+	/* If not registered yet, create new chip. */
+	if (!chip) {
+		ret = snd_usb_apply_boot_quirk_once(udev, iface, quirk, id);
+		if (ret < 0)
+			return ERR_PTR(ret);
+
+		slot = snd_usb_alloc_slot(udev);
+		if (slot < 0) {
+			dev_err(&udev->dev, "no available usb audio device\n");
+			return ERR_PTR(slot);
+		}
+
+		chip = snd_usb_new_chip(iface, slot, quirk, id);
+		if (!chip)
+			return ERR_PTR(-ENOMEM);
+
+		usb_chip[slot] = chip;
+		dev_set_drvdata(&udev->dev, chip);
+	} else if (chip->num_interfaces >= MAX_CARD_INTERFACES) {
+		dev_info(&udev->dev, "Too many interfaces assigned to the single USB-audio card\n");
+
+		atomic_dec(&chip->active);
+		return ERR_PTR(-EINVAL);
+	}
+
+	chip->intf[chip->num_interfaces] = iface;
+	chip->num_interfaces++;
+	usb_set_intfdata(iface, chip);
+
+	return chip;
+}
+
+static int snd_usb_sanity_check(struct usb_interface *iface,
+				const struct snd_usb_audio_quirk *quirk)
+{
+	struct usb_host_interface *alts;
+	struct usb_device *udev;
+	int ifnum;
+
+	udev = interface_to_usbdev(iface);
+	alts = &iface->altsetting[0];
+	ifnum = get_iface_desc(alts)->bInterfaceNumber;
+
+	if (quirk && quirk->ifnum >= 0 && ifnum != quirk->ifnum)
+		return -ENXIO;
+	if (quirk && quirk->ifnum == QUIRK_NODEV_INTERFACE)
+		return -ENODEV;
+
+	switch (snd_usb_get_speed(udev)) {
+	case USB_SPEED_LOW:
+	case USB_SPEED_FULL:
+	case USB_SPEED_HIGH:
+	case USB_SPEED_SUPER:
+	case USB_SPEED_SUPER_PLUS:
+		break;
+	default:
+		dev_err(&udev->dev, "unknown device speed %d\n", snd_usb_get_speed(udev));
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+__maybe_unused
+static int snd_usb_probe_unlocked(struct usb_interface *iface, const struct usb_device_id *usb_id,
+				  struct usb_driver *driver)
+{
+	const struct snd_usb_audio_quirk *quirk;
+	struct usb_host_interface *alts;
+	struct snd_usb_audio *chip;
+	struct usb_device *udev;
+	int ret;
+	u32 id;
+
+	quirk = (const struct snd_usb_audio_quirk *)usb_id->driver_info;
+	alts = &iface->altsetting[0];
+	udev = interface_to_usbdev(iface);
+	id = usb_device_vpid(udev);
+	if (get_alias_id(udev, &id))
+		quirk = get_alias_quirk(udev, id);
+
+	ret = snd_usb_sanity_check(iface, quirk);
+	if (ret)
+		return ret;
+
+	ret = snd_usb_apply_boot_quirk(udev, iface, quirk, id);
+	if (ret < 0)
+		return ret;
+
+	chip = snd_usb_request_chip(iface, quirk, id);
+	if (IS_ERR(chip))
+		return PTR_ERR(chip);
+
+	if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND)
+		usb_disable_autosuspend(interface_to_usbdev(iface));
+
+	/*
+	 * 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;
+
+	ret = snd_usb_parse_pcms(chip, iface, driver);
+	if (ret)
+		goto err;
+	return 0;
+err:
+	snd_usb_chip_release(chip, iface);
+	return ret;
+}
+
+int snd_usb_bind_card(struct snd_usb_audio *chip, struct snd_card *card,
+		      struct usb_driver *driver)
+{
+	chip->card = card;
+	snd_usb_audio_create_proc(chip);
+
+	atomic_sub(chip->num_interfaces, &chip->active);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_usb_bind_card);
+
+__maybe_unused
+static int snd_usb_create_card(struct snd_usb_audio *chip, struct usb_interface *iface)
+{
+	struct usb_device *udev = chip->dev;
+	struct snd_card *card;
+	int idx = chip->index;
+	char component[14];
+	int ret;
+
+	ret = snd_card_new(&iface->dev, index[idx], id[idx], THIS_MODULE, 0, &card);
+	if (ret) {
+		dev_err(&udev->dev, "cannot create card instance %d\n", idx);
+		return ret;
+	}
+
+	chip->card = card;
+	strscpy(card->driver, "USB-Audio", sizeof(card->driver));
+	usb_audio_make_shortname(udev, chip, chip->quirk);
+	usb_audio_make_longname(udev, chip, chip->quirk);
+	sprintf(component, "USB%04x:%04x", usb_device_vid(udev), usb_device_pid(udev));
+	snd_component_add(card, component);
+	snd_usb_audio_create_proc(chip);
+
+	return 0;
+}
+
 /* register card if we reach to the last interface or to the specified
  * one given via option
  */
-- 
2.25.1


  parent reply	other threads:[~2025-04-09 10:51 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
2025-04-09 11:07 ` [RFC 01/15] ALSA: usb: Move media-filters to the media code Cezary Rojewski
2025-04-09 11:07 ` [RFC 02/15] ALSA: usb: Drop private_free() usage for card and pcms Cezary Rojewski
2025-04-09 11:07 ` [RFC 03/15] ALSA: usb: Relocate the usbaudio header file Cezary Rojewski
2025-04-09 11:07 ` [RFC 04/15] ALSA: usb: Implement two-stage quirk applying mechanism Cezary Rojewski
2025-04-09 11:07 ` [RFC 05/15] ALSA: usb: Implement two-stage stream creation mechanism Cezary Rojewski
2025-04-09 11:07 ` Cezary Rojewski [this message]
2025-04-09 11:07 ` [RFC 07/15] ALSA: usb: Switch to the two-stage chip probing Cezary Rojewski
2025-04-09 11:07 ` [RFC 08/15] ALSA: usb: Switch to the two-stage stream creation Cezary Rojewski
2025-04-09 11:07 ` [RFC 09/15] ALSA: usb: Switch to the two-stage quirk applying Cezary Rojewski
2025-04-09 11:07 ` [RFC 10/15] ALSA: usb: Export PCM operations Cezary Rojewski
2025-04-09 11:07 ` [RFC 11/15] ALSA: usb: Export usb_interface driver operations Cezary Rojewski
2025-04-09 11:07 ` [RFC 12/15] ALSA: usb: Export card-naming procedure Cezary Rojewski
2025-04-09 11:07 ` [RFC 13/15] ALSA: usb: Add getters to obtain endpoint information Cezary Rojewski
2025-04-09 11:07 ` [RFC 14/15] ASoC: codecs: Add USB-Audio driver Cezary Rojewski
2025-04-09 11:07 ` [RFC 15/15] ASoC: Intel: avs: Add USB machine board Cezary Rojewski
2025-04-09 12:10 ` [RFC 00/15] ALSA/ASoC: USB Audio Offload Greg KH
2025-04-09 13:06   ` Cezary Rojewski
2025-04-10 10:10     ` Takashi Iwai
2025-04-10 10:24 ` Takashi Iwai
2025-04-11  9:39   ` Cezary Rojewski
2025-04-15 16:15     ` Takashi Iwai
2025-04-17 10:15       ` Cezary Rojewski
2025-04-22 11:28         ` Pierre-Louis Bossart
2025-04-22 14:15           ` Cezary Rojewski
2025-04-25 16:53             ` Pierre-Louis Bossart
2025-04-11 14:04 ` Greg KH
2025-04-11 16:51   ` Cezary Rojewski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250409110731.3752332-7-cezary.rojewski@intel.com \
    --to=cezary.rojewski@intel.com \
    --cc=amadeuszx.slawinski@linux.intel.com \
    --cc=broonie@kernel.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-sound@vger.kernel.org \
    --cc=mathias.nyman@linux.intel.com \
    --cc=perex@perex.cz \
    --cc=quic_wcheng@quicinc.com \
    --cc=tiwai@suse.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox