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 05/15] ALSA: usb: Implement two-stage stream creation mechanism
Date: Wed,  9 Apr 2025 13:07:20 +0200	[thread overview]
Message-ID: <20250409110731.3752332-6-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

On the streaming side, this translates to replacing existing functions
that do:
	create_all()
with new ones that:
	parse_pcms()
	create_nonpcms()

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

The implementation does try to align with existing equivalent as closely
as possible - no quirks and magic if-statements are skipped. Any kind of
cleanup in that area is outside of the scope of this change.

Existing snd_usb_create_stream() is represented by:
	snd_usb_streaming_iface()
	snd_usb_create_claim_pcm()
	snd_usb_create_claim_midi()

Part of existing usb_audio_probe(), responsible for stream creation is
represented by:
	snd_usb_parse_pcms()
	snd_usb_create_nonpcms()

Existing __snd_usb_add_audio_stream() is represented by:
	snd_usb_parse_pcms()
	snd_usb_pcm_add()
	snd_usb_add_pcms()

As ASoC is responsible for calling snd_pcm_new(), the component driver
has to bind the PCMs to USB streams manually. Helper snd_usb_bind_pcm()
addresses that need.

Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
 include/sound/usb.h |   5 ++
 sound/usb/card.c    | 168 ++++++++++++++++++++++++++++++++++++++++++++
 sound/usb/stream.c  | 145 ++++++++++++++++++++++++++++++++++++++
 sound/usb/stream.h  |   1 +
 4 files changed, 319 insertions(+)

diff --git a/include/sound/usb.h b/include/sound/usb.h
index 7e7a86b5c9c4..c50fc61f357c 100644
--- a/include/sound/usb.h
+++ b/include/sound/usb.h
@@ -8,6 +8,9 @@
  *   Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
  */
 
+#include <linux/list.h>
+#include <sound/pcm.h>
+
 /* handling of USB vendor/product ID pairs as 32-bit numbers */
 #define USB_ID(vendor, product) (((unsigned int)(vendor) << 16) | (product))
 #define USB_ID_VENDOR(id) ((id) >> 16)
@@ -87,6 +90,8 @@ struct snd_usb_audio {
 	struct snd_intf_to_ctrl intf_to_ctrl[MAX_CARD_INTERFACES];
 };
 
+int snd_usb_bind_pcm(struct list_head *stream_entry, struct snd_pcm *pcm);
+
 #define USB_AUDIO_IFACE_UNUSED	((void *)-1L)
 
 #define usb_audio_err(chip, fmt, args...) \
diff --git a/sound/usb/card.c b/sound/usb/card.c
index d8794adb7a88..4121ad03acd1 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -217,6 +217,110 @@ static int snd_usb_create_stream(struct snd_usb_audio *chip, int ctrlif, int int
 	return 0;
 }
 
+__maybe_unused
+static struct usb_interface *snd_usb_streaming_iface(struct snd_usb_audio *chip, int ctrlif,
+						     int interface, u8 subclass)
+{
+	struct usb_interface_descriptor *altsd;
+	struct usb_device *udev = chip->dev;
+	struct usb_host_interface *alts;
+	struct usb_interface *iface;
+
+	iface = usb_ifnum_to_if(udev, interface);
+	if (!iface) {
+		dev_err(&udev->dev, "%u:%d : does not exist\n", ctrlif, interface);
+		return ERR_PTR(-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(udev, interface);
+		if (!iface)
+			return ERR_PTR(-EINVAL);
+
+		alts = &iface->altsetting[0];
+		altsd = get_iface_desc(alts);
+	}
+
+	if (usb_interface_claimed(iface)) {
+		dev_dbg(&udev->dev, "%d:%d: skipping, already claimed\n", ctrlif, interface);
+		return ERR_PTR(-EINVAL);
+	}
+
+	if ((altsd->bInterfaceClass != USB_CLASS_AUDIO &&
+	     altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
+	    (altsd->bInterfaceSubClass != USB_SUBCLASS_AUDIOSTREAMING &&
+	     altsd->bInterfaceSubClass != USB_SUBCLASS_MIDISTREAMING)) {
+		dev_dbg(&udev->dev, "%u:%d: skipping non-supported interface %d\n",
+			ctrlif, interface, altsd->bInterfaceClass);
+		/* skip non-supported classes */
+		return ERR_PTR(-EINVAL);
+	}
+
+	if (altsd->bInterfaceSubClass == subclass)
+		return iface;
+	return NULL;
+}
+
+__maybe_unused
+static int snd_usb_parse_claim_pcm(struct snd_usb_audio *chip, int ctrlif,
+				   struct usb_interface *iface,
+				   struct usb_driver *iface_driver)
+{
+	struct usb_device *udev = chip->dev;
+	struct usb_host_interface *alt;
+	int ifnum;
+
+	alt = &iface->altsetting[0];
+	ifnum = alt->desc.bInterfaceNumber;
+
+	if (snd_usb_get_speed(udev) == USB_SPEED_LOW) {
+		dev_err(&udev->dev, "low speed audio streaming not supported\n");
+		return -EINVAL;
+	}
+
+	snd_usb_add_ctrl_interface_link(chip, ifnum, ctrlif);
+
+	if (!snd_usb_parse_audio_interface(chip, ifnum)) {
+		usb_set_interface(udev, ifnum, 0); /* reset the current interface */
+		return usb_driver_claim_interface(iface_driver, iface, USB_AUDIO_IFACE_UNUSED);
+	}
+
+	return 0;
+}
+
+__maybe_unused
+static int snd_usb_create_claim_midi(struct snd_usb_audio *chip, int ctrlif,
+				     struct usb_interface *iface,
+				     struct usb_driver *iface_driver)
+{
+	struct usb_device *udev = chip->dev;
+	struct usb_host_interface *alt;
+	int ifnum, ret;
+
+	alt = &iface->altsetting[0];
+	ifnum = alt->desc.bInterfaceNumber;
+
+	ret = snd_usb_midi_v2_create(chip, iface, NULL, chip->usb_id);
+	if (ret < 0) {
+		dev_err(&udev->dev, "%u:%d: cannot create sequencer device\n", ctrlif, ifnum);
+		return -EINVAL;
+	}
+
+	return usb_driver_claim_interface(iface_driver, iface, USB_AUDIO_IFACE_UNUSED);
+}
+
 /*
  * parse audio control descriptor and create pcm/midi streams
  */
@@ -737,6 +841,70 @@ 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)
+{
+	const struct snd_usb_audio_quirk *quirk = chip->quirk;
+	struct usb_host_interface *alt;
+	int ifnum, ret;
+
+	alt = &iface->altsetting[0];
+	ifnum = alt->desc.bInterfaceNumber;
+
+	/* Check for custom parsing behavior, favor it over the generic one. */
+	if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) {
+		ret = snd_usb_parse_pcms_quirk(chip, iface, driver, quirk);
+
+		/* Were custom pcm interfaces parsed? */
+		if (ret == 1)
+			return 0;
+		if (ret)
+			return ret;
+	}
+
+	/* Parse pcm interfaces in generic fashion. */
+	ret = snd_usb_create_streams(chip, ifnum);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+__maybe_unused
+static int snd_usb_create_nonpcms(struct snd_usb_audio *chip, struct usb_interface *iface,
+				  struct usb_driver *driver)
+{
+	const struct snd_usb_audio_quirk *quirk = chip->quirk;
+	struct usb_host_interface *alt;
+	int ifnum, ret;
+
+	alt = &iface->altsetting[0];
+	ifnum = alt->desc.bInterfaceNumber;
+
+	/* Check for custom create behavior, favor it over the generic one. */
+	if (quirk && quirk->ifnum != QUIRK_NO_INTERFACE) {
+		ret = snd_usb_create_quirk(chip, iface, driver, quirk);
+
+		/* Were custom midi/mixer interfaces created? */
+		if (ret == 1)
+			return 0;
+		if (ret)
+			return ret;
+	}
+
+	/* Create midi/mixer interfaces in generic fashion. */
+	ret = snd_usb_create_streams(chip, ifnum);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_usb_create_mixer(chip, ifnum);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
 static void snd_usb_chip_free(struct snd_usb_audio *chip)
 {
 	snd_usb_free_pcms(chip);
diff --git a/sound/usb/stream.c b/sound/usb/stream.c
index 6beeff03d711..1cf59e6d8ce6 100644
--- a/sound/usb/stream.c
+++ b/sound/usb/stream.c
@@ -556,6 +556,151 @@ static int __snd_usb_add_audio_stream(struct snd_usb_audio *chip,
 	return add_chmap(pcm, stream, &as->substream[stream]);
 }
 
+/* Parse and build ->pcm_list but do not create PCMs yet. */
+__maybe_unused
+static int snd_usb_parse_pcm(struct snd_usb_audio *chip, int stream, struct audioformat *fp,
+			     struct snd_usb_power_domain *pd)
+{
+	struct snd_usb_substream *subs;
+	struct snd_usb_stream *as;
+
+	/* Is there anything to do? */
+	list_for_each_entry(as, &chip->pcm_list, list) {
+		if (as->fmt_type != fp->fmt_type)
+			continue;
+		subs = &as->substream[stream];
+		if (subs->ep_num == fp->endpoint) {
+			list_add_tail(&fp->list, &subs->fmt_list);
+			subs->num_formats++;
+			subs->formats |= fp->formats;
+			return 0;
+		}
+	}
+
+	if (chip->card && chip->card->registered)
+		chip->need_delayed_register = true;
+
+	/*
+	 * Check for empty substreams (holes in the list) to minimize
+	 * the number of new snd_pcms created.
+	 */
+	list_for_each_entry(as, &chip->pcm_list, list) {
+		if (as->fmt_type != fp->fmt_type)
+			continue;
+		subs = &as->substream[stream];
+		if (!subs->num_formats) {
+			snd_usb_init_substream(as, stream, fp, pd);
+			return 0;
+		}
+	}
+
+	/* Otherwise create a new stream. */
+	as = kzalloc(sizeof(*as), GFP_KERNEL);
+	if (!as)
+		return -ENOMEM;
+
+	as->pcm_index = chip->pcm_devs;
+	as->chip = chip;
+	as->fmt_type = fp->fmt_type;
+	chip->pcm_devs++;
+	snd_usb_init_substream(as, stream, fp, pd);
+
+	/*
+	 * Keep using head insertion for M-Audio Audiophile USB (tm) which has a
+	 * fix to swap capture stream order in conf/cards/USB-audio.conf
+	 */
+	if (chip->usb_id == USB_ID(0x0763, 0x2003))
+		list_add(&as->list, &chip->pcm_list);
+	else
+		list_add_tail(&as->list, &chip->pcm_list);
+	return 0;
+}
+
+static int snd_usb_pcm_add(struct snd_usb_audio *chip, struct snd_usb_stream *as)
+{
+	struct snd_pcm *pcm;
+	unsigned int stream;
+	int ret;
+
+	ret = snd_pcm_new(chip->card, "USB Audio", as->pcm_index,
+			  as->substream[SNDRV_PCM_STREAM_PLAYBACK].ep_num ? 1 : 0,
+			  as->substream[SNDRV_PCM_STREAM_CAPTURE].ep_num ? 1 : 0,
+			  &pcm);
+	if (ret < 0)
+		return ret;
+
+	as->pcm = pcm;
+	/* ->private_data taken for 'rtd' on ASoC side. */
+	pcm->private_data = as;
+	pcm->info_flags = 0;
+
+	if (as->pcm_index)
+		sprintf(pcm->name, "USB Audio #%d", as->pcm_index);
+	else
+		strscpy(pcm->name, "USB Audio", sizeof(pcm->name));
+
+	snd_usb_proc_pcm_format_add(as);
+
+	for_each_pcm_streams(stream) {
+		if (as->substream[stream].num_formats) {
+			snd_usb_set_pcm_ops(pcm, stream);
+			snd_usb_preallocate_buffer(&as->substream[stream]);
+
+			ret = add_chmap(pcm, stream, &as->substream[stream]);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * snd_usb_bind_pcm - Bind USB stream with a PCM object.
+ *
+ * @stream_entry: USB stream, one of chip->pcm_list.
+ * @pcm: the PCM given USB stream shall be attached to.
+ *
+ * Note: Designed for ASoC users where snd_pcm_new() and pcm->private_data
+ * is owned by the framework.
+ */
+int snd_usb_bind_pcm(struct list_head *stream_entry, struct snd_pcm *pcm)
+{
+	struct snd_usb_stream *as;
+	unsigned int dir;
+	int ret;
+
+	as = list_entry(stream_entry, struct snd_usb_stream, list);
+	as->pcm = pcm;
+
+	snd_usb_proc_pcm_format_add(as);
+
+	for_each_pcm_streams(dir) {
+		if (as->substream[dir].num_formats) {
+			/* Buffer preallocation and PCM ops controlled by ASoC component. */
+			ret = add_chmap(pcm, dir, &as->substream[dir]);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(snd_usb_bind_pcm);
+
+int snd_usb_add_pcms(struct snd_usb_audio *chip)
+{
+	struct snd_usb_stream *as;
+	int ret;
+
+	list_for_each_entry(as, &chip->pcm_list, list) {
+		ret = snd_usb_pcm_add(chip, as);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
 int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
 			     int stream,
 			     struct audioformat *fp)
diff --git a/sound/usb/stream.h b/sound/usb/stream.h
index f70693502281..98cff9922cf1 100644
--- a/sound/usb/stream.h
+++ b/sound/usb/stream.h
@@ -9,6 +9,7 @@ int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
 			     int stream,
 			     struct audioformat *fp);
 void snd_usb_free_pcms(struct snd_usb_audio *chip);
+int snd_usb_add_pcms(struct snd_usb_audio *chip);
 
 #endif /* __USBAUDIO_STREAM_H */
 
-- 
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 ` Cezary Rojewski [this message]
2025-04-09 11:07 ` [RFC 06/15] ALSA: usb: Implement two-stage chip probing mechanism Cezary Rojewski
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-6-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