* [RFC 00/15] ALSA/ASoC: USB Audio Offload
@ 2025-04-09 11:07 Cezary Rojewski
2025-04-09 11:07 ` [RFC 01/15] ALSA: usb: Move media-filters to the media code Cezary Rojewski
` (17 more replies)
0 siblings, 18 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
Note: this series is based on Mark's broonie/for-next. The xHCI
dependency is missing so it won't compile. Goal of this RFC is
discussing the direction of sound/usb changes.
Note #2: Why exclude xHCI? Current form of xhci-sideband.c [1] does not
fare well with Intel's hardware design for USB Audio Offload feature.
The production shape for usb/xHCI subjects is being discussed with
Mathias. Once we're ready, I'll share the rest.
Note #3: this series does _NOT_ aim to block QCOM's equivalent series
[2]. The team does acknowledge that we came the "table" late. At the
same time, we're prepared to help QCOM switch to the presented sound/usb
approach if that would benefit the framework and its users as a whole.
Make it part of this very series if need be.
Apart from changes to sound/usb, the patchset contains exemplary
ASoC-based, offload-aware USB driver and a small sound card on the
avs-driver side to show how card/dai_link initialization looks like.
In short, USB Audio Offload functionality lowers CPU usage when
streaming PCM over a USB Audio-Class device. It has been first
introduced in xHCI 1.2 release and is described in section 7.9 [3].
Once hardware is prepared with hw_params(), all the USB endpoints
operations e.g.: start, stop, submitting URBs, are performed
internally, by the hardware and AudioDSP firmware. Software driver
shall not intervene.
Startup flow from:
usb_pcm_open()
usb_pcm_hw_params()
snd_usb_endpoint_open()
usb_pcm_prepare()
usb_set_interface()
snd_usb_endpoint_start()
usb_pcm_trigger(cmd: START/STOP etc.)
reduced to:
usb_pcm_open()
usb_pcm_hw_params()
snd_usb_endpoint_open()
usb_pcm_prepare()
usb_set_interface()
Handlers such as ack(), sync_stop(), pointer(), delay() are not used
here too. The AudioDSP driver will handle pointer(), rest is
firmware/hardware responsibility.
There's a hefty number of limitations, most importantly:
1) typically 2 USB devices tops, rest go the classic (non-offload) path
2) AUDIOSTREAMING interfaces only, MIDI not. While not an interface
type, Media (sound/usb/media) unsupported either
3) simple PCM, UAC_FORMAT_TYPE_I_PCM only
Current patchset shows this in form of 'udev->audsb_capable' field.
True if xHCI sideband reasource has been assigned to the device. The
filtering is code is not part of the patchset.
Important to highlight, 1) means both, offload-aware and offload-unaware
drivers could be utilized simultaneously on the system in runtime.
Opportunistically few devices would be controlled by the offload-aware
driver, whereas everything else by the offload-unaware one. This
differs from existing HDAudio Controller driver situation where either
classic, snd_hda_intel driver takes complete control -or- the
offload-aware snd_soc_avs driver.
In short, once all Audio Sideband resources are depleted, classic
sound/usb/card.c driver manages whatever comes next:
snd_usb_audio (offload un-aware, sound/usb/card.c)
snd_soc_usb_codec (offload aware, sound/soc/codecs/usb.c)
The design goals:
- make ASoC first class citizen of sound/usb
- re-use code found in sound/usb, mimic HDAudio integration in ASoC:
small sound/soc/codecs/hda.c driver leveraging power of entire
sound/pci/hda/
- no shared control over a USB device, either snd_usb_audio or
its ASoC equivalent takes control of the device
To do that, major tasks are identified:
a) On ASoC side 'struct snd_card' is part of 'struct snd_soc_card' and
is managed by the framework. Similar situation with 'struct snd_pcm'
and rtd->pcm. To keep the teardown path sane, drop card->private_free()
and pcm->private_free() usage.
b) To initialize ASoC components/DAI properly, PCM capabilities should be
known up-front. To do that, existing USB card probe() has to be split.
From one-stage to two-stage process:
- look ahead and parse usb_interface descriptors for PCM endpoints
but do not create any PCMs (sound devices) yet
- create all PCMs based on obtained ->pcm_list and follow with
MIDI/mixers/media
Such approach allows to feed DAIs proper data even when a valid
sound-card pointer is not yet present - the initialization occurs before
snd_soc_bind_card() is called.
Point a) is scaled for all three "domains" of sound/usb: chip, stream
and quirks. That's why there total of 6 commits doing that job. First
implement, then switch. Everything that follows is, in my opinion,
self-explanatory. No need to repeat commit messages.
[1]: https://lore.kernel.org/all/20250319005141.312805-2-quic_wcheng@quicinc.com/
[2]: https://lore.kernel.org/all/20250319005141.312805-1-quic_wcheng@quicinc.com/
[3]: https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf
Cezary Rojewski (15):
ALSA: usb: Move media-filters to the media code
ALSA: usb: Drop private_free() usage for card and pcms
ALSA: usb: Relocate the usbaudio header file
ALSA: usb: Implement two-stage quirk applying mechanism
ALSA: usb: Implement two-stage stream creation mechanism
ALSA: usb: Implement two-stage chip probing mechanism
ALSA: usb: Switch to the two-stage chip probing
ALSA: usb: Switch to the two-stage stream creation
ALSA: usb: Switch to the two-stage quirk applying
ALSA: usb: Export PCM operations
ALSA: usb: Export usb_interface driver operations
ALSA: usb: Export card-naming procedure
ALSA: usb: Add getters to obtain endpoint information
ASoC: codecs: Add USB-Audio driver
ASoC: Intel: avs: Add USB machine board
include/linux/usb.h | 15 +
include/linux/usb/ch9.h | 11 +
sound/usb/usbaudio.h => include/sound/usb.h | 53 +-
include/sound/usb_offload.h | 46 +
sound/soc/codecs/Kconfig | 6 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/usb.c | 441 +++++++++
sound/soc/intel/avs/boards/Kconfig | 8 +
sound/soc/intel/avs/boards/Makefile | 2 +
sound/soc/intel/avs/boards/usb.c | 115 +++
sound/usb/caiaq/device.h | 2 +-
sound/usb/card.c | 946 +++++++++++++-------
sound/usb/card.h | 4 +-
sound/usb/clock.c | 2 +-
sound/usb/endpoint.c | 2 +-
sound/usb/format.c | 2 +-
sound/usb/helper.c | 2 +-
sound/usb/implicit.c | 2 +-
sound/usb/media.c | 8 +-
sound/usb/midi.c | 2 +-
sound/usb/midi.h | 2 +
sound/usb/midi2.c | 2 +-
sound/usb/midi2.h | 1 +
sound/usb/misc/ua101.c | 2 +-
sound/usb/mixer.c | 2 +-
sound/usb/mixer_quirks.c | 2 +-
sound/usb/mixer_s1810c.c | 2 +-
sound/usb/mixer_scarlett.c | 2 +-
sound/usb/mixer_scarlett2.c | 2 +-
sound/usb/mixer_us16x08.c | 2 +-
sound/usb/pcm.c | 161 +++-
sound/usb/power.c | 2 +-
sound/usb/proc.c | 2 +-
sound/usb/quirks.c | 203 +++--
sound/usb/quirks.h | 6 +
sound/usb/stream.c | 217 +++--
sound/usb/stream.h | 2 +
sound/usb/usx2y/us122l.c | 2 +-
sound/usb/usx2y/usX2Yhwdep.c | 1 +
sound/usb/usx2y/usbusx2y.h | 2 +-
sound/usb/validate.c | 2 +-
41 files changed, 1749 insertions(+), 541 deletions(-)
rename sound/usb/usbaudio.h => include/sound/usb.h (82%)
create mode 100644 include/sound/usb_offload.h
create mode 100644 sound/soc/codecs/usb.c
create mode 100644 sound/soc/intel/avs/boards/usb.c
--
2.25.1
^ permalink raw reply [flat|nested] 28+ messages in thread
* [RFC 01/15] ALSA: usb: Move media-filters to the media code
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
@ 2025-04-09 11:07 ` Cezary Rojewski
2025-04-09 11:07 ` [RFC 02/15] ALSA: usb: Drop private_free() usage for card and pcms Cezary Rojewski
` (16 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
Creating and deleting process of media devices in the common code can
be simplified if quirk_flags are checked in callee.
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
sound/usb/card.c | 13 +++----------
sound/usb/media.c | 6 ++++++
2 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/sound/usb/card.c b/sound/usb/card.c
index 9c411b82a218..03694295491f 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -909,10 +909,8 @@ static int usb_audio_probe(struct usb_interface *intf,
if (err < 0)
goto __error_no_register;
- if (chip->quirk_flags & QUIRK_FLAG_SHARE_MEDIA_DEVICE) {
- /* don't want to fail when snd_media_device_create() fails */
- snd_media_device_create(chip, intf);
- }
+ /* don't want to fail when snd_media_device_create() fails */
+ snd_media_device_create(chip, intf);
if (quirk)
chip->quirk_type = quirk->type;
@@ -983,12 +981,7 @@ static void usb_audio_disconnect(struct usb_interface *intf)
snd_usbmidi_disconnect(p);
}
snd_usb_midi_v2_disconnect_all(chip);
- /*
- * 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() accesses mixer_list */
snd_media_device_delete(chip);
/* release mixer resources */
diff --git a/sound/usb/media.c b/sound/usb/media.c
index d48db6f3ae65..b175fa820345 100644
--- a/sound/usb/media.c
+++ b/sound/usb/media.c
@@ -258,6 +258,9 @@ int snd_media_device_create(struct snd_usb_audio *chip,
struct usb_device *usbdev = interface_to_usbdev(iface);
int ret = 0;
+ if (!(chip->quirk_flags & QUIRK_FLAG_SHARE_MEDIA_DEVICE))
+ return 0;
+
/* usb-audio driver is probed for each usb interface, and
* there are multiple interfaces per device. Avoid calling
* media_device_usb_allocate() each time usb_audio_probe()
@@ -312,6 +315,9 @@ void snd_media_device_delete(struct snd_usb_audio *chip)
struct media_device *mdev = chip->media_dev;
struct snd_usb_stream *stream;
+ if (!(chip->quirk_flags & QUIRK_FLAG_SHARE_MEDIA_DEVICE))
+ return;
+
/* release resources */
list_for_each_entry(stream, &chip->pcm_list, list) {
snd_media_stream_delete(&stream->substream[0]);
--
2.25.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 02/15] ALSA: usb: Drop private_free() usage for card and pcms
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 ` Cezary Rojewski
2025-04-09 11:07 ` [RFC 03/15] ALSA: usb: Relocate the usbaudio header file Cezary Rojewski
` (15 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
On ASoC side, drivers operate on instance of struct snd_soc_card instead
of struct snd_card. The latter is owned by the ASoC framework. To make
the existing sound/usb code ASoC-friendly and avoid freeing-order
problems when soc_cleanup_card_resources() is called, resign from
cardd->private_free() and extra-size when allocating new card with
snd_card_new(). Similar pattern applies to PCMs.
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
sound/usb/card.c | 50 ++++++++++++++++++++++------------------------
sound/usb/stream.c | 14 ++++++-------
sound/usb/stream.h | 1 +
3 files changed, 31 insertions(+), 34 deletions(-)
diff --git a/sound/usb/card.c b/sound/usb/card.c
index 03694295491f..65de8e7854b6 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -485,25 +485,6 @@ lookup_device_name(u32 id)
return NULL;
}
-/*
- * 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;
-
- snd_usb_endpoint_free_all(chip);
- snd_usb_midi_v2_free_all(chip);
-
- 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)
@@ -630,14 +611,17 @@ static int snd_usb_audio_create(struct usb_interface *intf,
return -ENXIO;
}
- err = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE,
- sizeof(*chip), &card);
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ err = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE, 0, &card);
if (err < 0) {
dev_err(&dev->dev, "cannot create card instance %d\n", idx);
+ kfree(chip);
return err;
}
- chip = card->private_data;
mutex_init(&chip->mutex);
init_waitqueue_head(&chip->shutdown_wait);
chip->index = idx;
@@ -665,8 +649,6 @@ static int snd_usb_audio_create(struct usb_interface *intf,
else
snd_usb_init_quirk_flags(chip);
- 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));
@@ -755,6 +737,19 @@ get_alias_quirk(struct usb_device *dev, unsigned int id)
return NULL;
}
+static void snd_usb_chip_free(struct snd_usb_audio *chip)
+{
+ snd_usb_free_pcms(chip);
+ snd_usb_endpoint_free_all(chip);
+ snd_usb_midi_v2_free_all(chip);
+ /* mixers and midi1.0 freed with snd_card_free(). */
+
+ mutex_destroy(&chip->mutex);
+ if (!atomic_read(&chip->shutdown))
+ dev_set_drvdata(&chip->dev->dev, NULL);
+ kfree(chip);
+}
+
/* register card if we reach to the last interface or to the specified
* one given via option
*/
@@ -934,8 +929,10 @@ static int usb_audio_probe(struct usb_interface *intf,
* decrement before memory is possibly returned.
*/
atomic_dec(&chip->active);
- if (!chip->num_interfaces)
+ if (!chip->num_interfaces) {
snd_card_free(chip->card);
+ snd_usb_chip_free(chip);
+ }
}
mutex_unlock(®ister_mutex);
return err;
@@ -997,7 +994,8 @@ static void usb_audio_disconnect(struct usb_interface *intf)
if (chip->num_interfaces <= 0) {
usb_chip[chip->index] = NULL;
mutex_unlock(®ister_mutex);
- snd_card_free_when_closed(card);
+ snd_card_free(card);
+ snd_usb_chip_free(chip);
} else {
mutex_unlock(®ister_mutex);
}
diff --git a/sound/usb/stream.c b/sound/usb/stream.c
index c1ea8844a46f..4d83c95bd9a8 100644
--- a/sound/usb/stream.c
+++ b/sound/usb/stream.c
@@ -55,7 +55,7 @@ static void free_substream(struct snd_usb_substream *subs)
/*
* free a usb stream instance
*/
-static void snd_usb_audio_stream_free(struct snd_usb_stream *stream)
+static void snd_usb_pcm_free(struct snd_usb_stream *stream)
{
free_substream(&stream->substream[0]);
free_substream(&stream->substream[1]);
@@ -63,13 +63,12 @@ static void snd_usb_audio_stream_free(struct snd_usb_stream *stream)
kfree(stream);
}
-static void snd_usb_audio_pcm_free(struct snd_pcm *pcm)
+void snd_usb_free_pcms(struct snd_usb_audio *chip)
{
- struct snd_usb_stream *stream = pcm->private_data;
- if (stream) {
- stream->pcm = NULL;
- snd_usb_audio_stream_free(stream);
- }
+ struct snd_usb_stream *as, *save;
+
+ list_for_each_entry_safe(as, save, &chip->pcm_list, list)
+ snd_usb_pcm_free(as);
}
/*
@@ -533,7 +532,6 @@ static int __snd_usb_add_audio_stream(struct snd_usb_audio *chip,
}
as->pcm = pcm;
pcm->private_data = as;
- pcm->private_free = snd_usb_audio_pcm_free;
pcm->info_flags = 0;
if (chip->pcm_devs > 0)
sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs);
diff --git a/sound/usb/stream.h b/sound/usb/stream.h
index d92e18d5818f..f70693502281 100644
--- a/sound/usb/stream.h
+++ b/sound/usb/stream.h
@@ -8,6 +8,7 @@ int snd_usb_parse_audio_interface(struct snd_usb_audio *chip,
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);
#endif /* __USBAUDIO_STREAM_H */
--
2.25.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 03/15] ALSA: usb: Relocate the usbaudio header file
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 ` Cezary Rojewski
2025-04-09 11:07 ` [RFC 04/15] ALSA: usb: Implement two-stage quirk applying mechanism Cezary Rojewski
` (14 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
For ALSA and ASoC to share the symbols, common header shall be defined.
Existing usbaudio.h holds most of important stuff, with few adjustments
it can be simply relocated to include/ to satisfy needs of both
frameworks. The adjstments are:
- rename quirk_type enumeration to snd_usb_quirk_type to avoid naming
conflits
- add quirk pointer so that quirks applied for a USB device can later be
referred to
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
sound/usb/usbaudio.h => include/sound/usb.h | 24 +++++++++++----------
sound/usb/caiaq/device.h | 2 +-
sound/usb/card.c | 2 +-
sound/usb/clock.c | 2 +-
sound/usb/endpoint.c | 2 +-
sound/usb/format.c | 2 +-
sound/usb/helper.c | 2 +-
sound/usb/implicit.c | 2 +-
sound/usb/media.c | 2 +-
sound/usb/midi.c | 2 +-
sound/usb/midi.h | 2 ++
sound/usb/midi2.c | 2 +-
sound/usb/midi2.h | 1 +
sound/usb/misc/ua101.c | 2 +-
sound/usb/mixer.c | 2 +-
sound/usb/mixer_quirks.c | 2 +-
sound/usb/mixer_s1810c.c | 2 +-
sound/usb/mixer_scarlett.c | 2 +-
sound/usb/mixer_scarlett2.c | 2 +-
sound/usb/mixer_us16x08.c | 2 +-
sound/usb/pcm.c | 2 +-
sound/usb/power.c | 2 +-
sound/usb/proc.c | 2 +-
sound/usb/quirks.c | 2 +-
sound/usb/quirks.h | 2 ++
sound/usb/stream.c | 2 +-
sound/usb/usx2y/us122l.c | 2 +-
sound/usb/usx2y/usX2Yhwdep.c | 1 +
sound/usb/usx2y/usbusx2y.h | 2 +-
sound/usb/validate.c | 2 +-
30 files changed, 44 insertions(+), 36 deletions(-)
rename sound/usb/usbaudio.h => include/sound/usb.h (98%)
diff --git a/sound/usb/usbaudio.h b/include/sound/usb.h
similarity index 98%
rename from sound/usb/usbaudio.h
rename to include/sound/usb.h
index 158ec053dc44..7e7a86b5c9c4 100644
--- a/sound/usb/usbaudio.h
+++ b/include/sound/usb.h
@@ -1,6 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
-#ifndef __USBAUDIO_H
-#define __USBAUDIO_H
+#ifndef __SOUND_USB_H
+#define __SOUND_USB_H
+
/*
* (Tentative) USB Audio Driver for ALSA
*
@@ -21,6 +22,14 @@ struct media_intf_devnode;
#define MAX_CARD_INTERFACES 16
+struct snd_usb_audio_quirk {
+ const char *vendor_name;
+ const char *product_name;
+ int16_t ifnum;
+ uint16_t type;
+ const void *data;
+};
+
/*
* Structure holding assosiation between Audio Control Interface
* and given Streaming or Midi Interface.
@@ -37,6 +46,7 @@ struct snd_usb_audio {
struct usb_interface *intf[MAX_CARD_INTERFACES];
u32 usb_id;
uint16_t quirk_type;
+ const struct snd_usb_audio_quirk *quirk;
struct mutex mutex;
unsigned int system_suspend;
atomic_t active;
@@ -99,7 +109,7 @@ struct snd_usb_audio {
#define QUIRK_NO_INTERFACE -2
#define QUIRK_ANY_INTERFACE -1
-enum quirk_type {
+enum snd_usb_quirk_type {
QUIRK_IGNORE_INTERFACE,
QUIRK_COMPOSITE,
QUIRK_AUTODETECT,
@@ -124,14 +134,6 @@ enum quirk_type {
QUIRK_TYPE_COUNT
};
-struct snd_usb_audio_quirk {
- const char *vendor_name;
- const char *product_name;
- int16_t ifnum;
- uint16_t type;
- const void *data;
-};
-
#define combine_word(s) ((*(s)) | ((unsigned int)(s)[1] << 8))
#define combine_triple(s) (combine_word(s) | ((unsigned int)(s)[2] << 16))
#define combine_quad(s) (combine_triple(s) | ((unsigned int)(s)[3] << 24))
diff --git a/sound/usb/caiaq/device.h b/sound/usb/caiaq/device.h
index 743eb0387b5f..2346dc8209c9 100644
--- a/sound/usb/caiaq/device.h
+++ b/sound/usb/caiaq/device.h
@@ -2,7 +2,7 @@
#ifndef CAIAQ_DEVICE_H
#define CAIAQ_DEVICE_H
-#include "../usbaudio.h"
+#include <sound/usb.h>
#define USB_VID_NATIVEINSTRUMENTS 0x17cc
diff --git a/sound/usb/card.c b/sound/usb/card.c
index 65de8e7854b6..d8794adb7a88 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -40,8 +40,8 @@
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "card.h"
#include "midi.h"
#include "midi2.h"
diff --git a/sound/usb/clock.c b/sound/usb/clock.c
index 842ba5b801ea..fa5bc7b8c79c 100644
--- a/sound/usb/clock.c
+++ b/sound/usb/clock.c
@@ -14,8 +14,8 @@
#include <sound/core.h>
#include <sound/info.h>
#include <sound/pcm.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "card.h"
#include "helper.h"
#include "clock.h"
diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c
index a29f28eb7d0c..bdd2b75ebfed 100644
--- a/sound/usb/endpoint.c
+++ b/sound/usb/endpoint.c
@@ -12,8 +12,8 @@
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "helper.h"
#include "card.h"
#include "endpoint.h"
diff --git a/sound/usb/format.c b/sound/usb/format.c
index 6049d957694c..1fa1711980ad 100644
--- a/sound/usb/format.c
+++ b/sound/usb/format.c
@@ -11,8 +11,8 @@
#include <sound/core.h>
#include <sound/pcm.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "card.h"
#include "quirks.h"
#include "helper.h"
diff --git a/sound/usb/helper.c b/sound/usb/helper.c
index 72b671fb2c84..2e4da52f8d8e 100644
--- a/sound/usb/helper.c
+++ b/sound/usb/helper.c
@@ -5,8 +5,8 @@
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/usb.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "helper.h"
#include "quirks.h"
diff --git a/sound/usb/implicit.c b/sound/usb/implicit.c
index 4727043fd745..fd0b23e7d92e 100644
--- a/sound/usb/implicit.c
+++ b/sound/usb/implicit.c
@@ -11,8 +11,8 @@
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "card.h"
#include "helper.h"
#include "pcm.h"
diff --git a/sound/usb/media.c b/sound/usb/media.c
index b175fa820345..7060410fd1c6 100644
--- a/sound/usb/media.c
+++ b/sound/usb/media.c
@@ -24,8 +24,8 @@
#include <sound/pcm.h>
#include <sound/core.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "card.h"
#include "mixer.h"
#include "media.h"
diff --git a/sound/usb/midi.c b/sound/usb/midi.c
index 767f1948cc5a..53d327ee8014 100644
--- a/sound/usb/midi.c
+++ b/sound/usb/midi.c
@@ -54,7 +54,7 @@
#include <sound/control.h>
#include <sound/rawmidi.h>
#include <sound/asequencer.h>
-#include "usbaudio.h"
+#include <sound/usb.h>
#include "midi.h"
#include "power.h"
#include "helper.h"
diff --git a/sound/usb/midi.h b/sound/usb/midi.h
index 2100f1486b03..bf3561652ba8 100644
--- a/sound/usb/midi.h
+++ b/sound/usb/midi.h
@@ -2,6 +2,8 @@
#ifndef __USBMIDI_H
#define __USBMIDI_H
+#include <sound/usb.h>
+
/* maximum number of endpoints per interface */
#define MIDI_MAX_ENDPOINTS 2
diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c
index 692dfc3c182f..f1d925b74692 100644
--- a/sound/usb/midi2.c
+++ b/sound/usb/midi2.c
@@ -18,7 +18,7 @@
#include <sound/core.h>
#include <sound/control.h>
#include <sound/ump.h>
-#include "usbaudio.h"
+#include <sound/usb.h>
#include "midi.h"
#include "midi2.h"
#include "helper.h"
diff --git a/sound/usb/midi2.h b/sound/usb/midi2.h
index 94a65fcbd58b..3e66531d5e80 100644
--- a/sound/usb/midi2.h
+++ b/sound/usb/midi2.h
@@ -2,6 +2,7 @@
#ifndef __USB_AUDIO_MIDI2_H
#define __USB_AUDIO_MIDI2_H
+#include <sound/usb.h>
#include "midi.h"
#if IS_ENABLED(CONFIG_SND_USB_AUDIO_MIDI_V2)
diff --git a/sound/usb/misc/ua101.c b/sound/usb/misc/ua101.c
index 4f6b20ed29dd..454587cdcad7 100644
--- a/sound/usb/misc/ua101.c
+++ b/sound/usb/misc/ua101.c
@@ -13,7 +13,7 @@
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
-#include "../usbaudio.h"
+#include <sound/usb.h>
#include "../midi.h"
MODULE_DESCRIPTION("Edirol UA-101/1000 driver");
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index e2e0ddf1957d..f95da9368921 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -43,8 +43,8 @@
#include <sound/hwdep.h>
#include <sound/info.h>
#include <sound/tlv.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "mixer.h"
#include "helper.h"
#include "mixer_quirks.h"
diff --git a/sound/usb/mixer_quirks.c b/sound/usb/mixer_quirks.c
index 23fcd680167d..54d16e0f8ac4 100644
--- a/sound/usb/mixer_quirks.c
+++ b/sound/usb/mixer_quirks.c
@@ -29,8 +29,8 @@
#include <sound/hwdep.h>
#include <sound/info.h>
#include <sound/tlv.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "mixer.h"
#include "mixer_quirks.h"
#include "mixer_scarlett.h"
diff --git a/sound/usb/mixer_s1810c.c b/sound/usb/mixer_s1810c.c
index fac4bbc6b275..d95edb1dc4d0 100644
--- a/sound/usb/mixer_s1810c.c
+++ b/sound/usb/mixer_s1810c.c
@@ -18,8 +18,8 @@
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/control.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "mixer.h"
#include "mixer_quirks.h"
#include "helper.h"
diff --git a/sound/usb/mixer_scarlett.c b/sound/usb/mixer_scarlett.c
index ff548041679b..f31718caa679 100644
--- a/sound/usb/mixer_scarlett.c
+++ b/sound/usb/mixer_scarlett.c
@@ -124,8 +124,8 @@
#include <sound/core.h>
#include <sound/control.h>
#include <sound/tlv.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "mixer.h"
#include "helper.h"
#include "power.h"
diff --git a/sound/usb/mixer_scarlett2.c b/sound/usb/mixer_scarlett2.c
index 7f595c1752a5..a3866e9dd1ac 100644
--- a/sound/usb/mixer_scarlett2.c
+++ b/sound/usb/mixer_scarlett2.c
@@ -158,10 +158,10 @@
#include <sound/control.h>
#include <sound/tlv.h>
#include <sound/hwdep.h>
+#include <sound/usb.h>
#include <uapi/sound/scarlett2.h>
-#include "usbaudio.h"
#include "mixer.h"
#include "helper.h"
diff --git a/sound/usb/mixer_us16x08.c b/sound/usb/mixer_us16x08.c
index 20ac32635f1f..bdceb3b38689 100644
--- a/sound/usb/mixer_us16x08.c
+++ b/sound/usb/mixer_us16x08.c
@@ -11,8 +11,8 @@
#include <sound/core.h>
#include <sound/control.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "mixer.h"
#include "helper.h"
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c
index 08bf535ed163..ea698f061af9 100644
--- a/sound/usb/pcm.c
+++ b/sound/usb/pcm.c
@@ -13,8 +13,8 @@
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "card.h"
#include "quirks.h"
#include "endpoint.h"
diff --git a/sound/usb/power.c b/sound/usb/power.c
index 66bd4daa68fd..ad847aadb4fb 100644
--- a/sound/usb/power.c
+++ b/sound/usb/power.c
@@ -8,8 +8,8 @@
#include <linux/usb/audio.h>
#include <linux/usb/audio-v2.h>
#include <linux/usb/audio-v3.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "helper.h"
#include "power.h"
diff --git a/sound/usb/proc.c b/sound/usb/proc.c
index e9bbaea7b2fa..b7e898722ff4 100644
--- a/sound/usb/proc.c
+++ b/sound/usb/proc.c
@@ -8,8 +8,8 @@
#include <sound/core.h>
#include <sound/info.h>
#include <sound/pcm.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "helper.h"
#include "card.h"
#include "endpoint.h"
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 8ba0aff8be2e..2a9470ef8b5f 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -13,8 +13,8 @@
#include <sound/core.h>
#include <sound/info.h>
#include <sound/pcm.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "card.h"
#include "mixer.h"
#include "mixer_quirks.h"
diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h
index f9bfd5ac7bab..0ea079688261 100644
--- a/sound/usb/quirks.h
+++ b/sound/usb/quirks.h
@@ -2,6 +2,8 @@
#ifndef __USBAUDIO_QUIRKS_H
#define __USBAUDIO_QUIRKS_H
+#include <sound/usb.h>
+
struct audioformat;
struct snd_usb_endpoint;
struct snd_usb_substream;
diff --git a/sound/usb/stream.c b/sound/usb/stream.c
index 4d83c95bd9a8..6beeff03d711 100644
--- a/sound/usb/stream.c
+++ b/sound/usb/stream.c
@@ -14,8 +14,8 @@
#include <sound/pcm.h>
#include <sound/control.h>
#include <sound/tlv.h>
+#include <sound/usb.h>
-#include "usbaudio.h"
#include "card.h"
#include "proc.h"
#include "quirks.h"
diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c
index 6bcf8b859ebb..3d00541a602c 100644
--- a/sound/usb/usx2y/us122l.c
+++ b/sound/usb/usx2y/us122l.c
@@ -11,9 +11,9 @@
#include <sound/hwdep.h>
#include <sound/pcm.h>
#include <sound/initval.h>
+#include <sound/usb.h>
#define MODNAME "US122L"
#include "usb_stream.c"
-#include "../usbaudio.h"
#include "../midi.h"
#include "us122l.h"
diff --git a/sound/usb/usx2y/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c
index 9fd6a86cc08e..2d6a9c80e5bf 100644
--- a/sound/usb/usx2y/usX2Yhwdep.c
+++ b/sound/usb/usx2y/usX2Yhwdep.c
@@ -14,6 +14,7 @@
#include <sound/memalloc.h>
#include <sound/pcm.h>
#include <sound/hwdep.h>
+#include <sound/usb.h>
#include "usx2y.h"
#include "usbusx2y.h"
#include "usX2Yhwdep.h"
diff --git a/sound/usb/usx2y/usbusx2y.h b/sound/usb/usx2y/usbusx2y.h
index 391fd7b4ed5e..689b565fb225 100644
--- a/sound/usb/usx2y/usbusx2y.h
+++ b/sound/usb/usx2y/usbusx2y.h
@@ -1,7 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef USBUSX2Y_H
#define USBUSX2Y_H
-#include "../usbaudio.h"
+#include <sound/usb.h>
#include "../midi.h"
#include "usbus428ctldefs.h"
diff --git a/sound/usb/validate.c b/sound/usb/validate.c
index 6fe206f6e911..8a7e8fc8197e 100644
--- a/sound/usb/validate.c
+++ b/sound/usb/validate.c
@@ -9,7 +9,7 @@
#include <linux/usb/audio-v2.h>
#include <linux/usb/audio-v3.h>
#include <linux/usb/midi.h>
-#include "usbaudio.h"
+#include <sound/usb.h>
#include "helper.h"
struct usb_desc_validator {
--
2.25.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 04/15] ALSA: usb: Implement two-stage quirk applying mechanism
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (2 preceding siblings ...)
2025-04-09 11:07 ` [RFC 03/15] ALSA: usb: Relocate the usbaudio header file Cezary Rojewski
@ 2025-04-09 11:07 ` Cezary Rojewski
2025-04-09 11:07 ` [RFC 05/15] ALSA: usb: Implement two-stage stream creation mechanism Cezary Rojewski
` (13 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
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 quirks side this translates to splitting all handlers that do combine
pcm/midi/mixer together into 'pcm-only' and 'others'. A top level
handler snd_usb_parse_pcms_quirk() is provided to invoke all the
necessary parse-pcm quirks depending on the quirk type. It mimics
behavior of snd_usb_create_quirk() but limits itself to just parsing the
PCMs.
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
sound/usb/quirks.c | 217 ++++++++++++++++++++++++++++++++++++---------
sound/usb/quirks.h | 4 +
2 files changed, 179 insertions(+), 42 deletions(-)
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 2a9470ef8b5f..4e402c6406a9 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -28,7 +28,36 @@
#include "stream.h"
/*
- * handle the quirks for the contained interfaces
+ * First run of composite quirk. Parses all PCM interfaces.
+ * These can be later created with snd_usb_add_pcms().
+ */
+static int create_composite_pcm_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk_comp)
+{
+ int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber;
+ const struct snd_usb_audio_quirk *quirk;
+ int err;
+
+ for (quirk = quirk_comp->data; quirk->ifnum >= 0; ++quirk) {
+ iface = usb_ifnum_to_if(chip->dev, quirk->ifnum);
+ if (!iface)
+ continue;
+ if (quirk->ifnum != probed_ifnum &&
+ usb_interface_claimed(iface))
+ continue;
+ err = snd_usb_parse_pcms_quirk(chip, iface, driver, quirk);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * Second run of composite quirk. Creates all non-PCM interfaces and
+ * claims the usb_interface @iface.
*/
static int create_composite_quirk(struct snd_usb_audio *chip,
struct usb_interface *iface,
@@ -375,6 +404,25 @@ static int create_auto_midi_quirk(struct snd_usb_audio *chip,
return create_std_midi_quirk(chip, iface, driver, alts);
}
+static int create_autodetect_pcm_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ return create_auto_pcm_quirk(chip, iface, driver);
+}
+
+__maybe_unused
+static int create_autodetect_midi_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ if (list_empty(&chip->pcm_list))
+ return create_auto_midi_quirk(chip, iface, driver);
+ return 1;
+}
+
static int create_autodetect_quirk(struct snd_usb_audio *chip,
struct usb_interface *iface,
struct usb_driver *driver,
@@ -388,14 +436,10 @@ static int create_autodetect_quirk(struct snd_usb_audio *chip,
return err;
}
-/*
- * Create a stream for an Edirol UA-700/UA-25/UA-4FX interface.
- * The only way to detect the sample rate is by looking at wMaxPacketSize.
- */
-static int create_uaxx_quirk(struct snd_usb_audio *chip,
- struct usb_interface *iface,
- struct usb_driver *driver,
- const struct snd_usb_audio_quirk *quirk)
+static int create_uaxx_pcm_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
{
static const struct audioformat ua_format = {
.formats = SNDRV_PCM_FMTBIT_S24_3LE,
@@ -405,8 +449,8 @@ static int create_uaxx_quirk(struct snd_usb_audio *chip,
.altset_idx = 1,
.rates = SNDRV_PCM_RATE_CONTINUOUS,
};
- struct usb_host_interface *alts;
struct usb_interface_descriptor *altsd;
+ struct usb_host_interface *alts;
struct audioformat *fp;
int err;
@@ -416,35 +460,15 @@ static int create_uaxx_quirk(struct snd_usb_audio *chip,
alts = &iface->altsetting[1];
altsd = get_iface_desc(alts);
- if (altsd->bNumEndpoints == 2) {
- static const struct snd_usb_midi_endpoint_info ua700_ep = {
- .out_cables = 0x0003,
- .in_cables = 0x0003
- };
- static const struct snd_usb_audio_quirk ua700_quirk = {
- .type = QUIRK_MIDI_FIXED_ENDPOINT,
- .data = &ua700_ep
- };
- static const struct snd_usb_midi_endpoint_info uaxx_ep = {
- .out_cables = 0x0001,
- .in_cables = 0x0001
- };
- static const struct snd_usb_audio_quirk uaxx_quirk = {
- .type = QUIRK_MIDI_FIXED_ENDPOINT,
- .data = &uaxx_ep
- };
- const struct snd_usb_audio_quirk *quirk =
- chip->usb_id == USB_ID(0x0582, 0x002b)
- ? &ua700_quirk : &uaxx_quirk;
- return __snd_usbmidi_create(chip->card, iface,
- &chip->midi_list, quirk,
- chip->usb_id,
- &chip->num_rawmidis);
+ switch (altsd->bNumEndpoints) {
+ case 2:
+ return 2; /* Leave this to midi. */
+ case 1:
+ break;
+ default:
+ return -ENXIO;
}
- if (altsd->bNumEndpoints != 1)
- return -ENXIO;
-
fp = kmemdup(&ua_format, sizeof(*fp), GFP_KERNEL);
if (!fp)
return -ENOMEM;
@@ -484,6 +508,78 @@ static int create_uaxx_quirk(struct snd_usb_audio *chip,
return 0;
}
+/*
+ * Create a stream for an Edirol UA-700/UA-25/UA-4FX interface.
+ * The only way to detect the sample rate is by looking at wMaxPacketSize.
+ */
+static int create_uaxx_midi_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ static const struct snd_usb_midi_endpoint_info ua700_ep = {
+ .out_cables = 0x0003,
+ .in_cables = 0x0003
+ };
+ static const struct snd_usb_audio_quirk ua700_quirk = {
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = &ua700_ep
+ };
+ static const struct snd_usb_midi_endpoint_info uaxx_ep = {
+ .out_cables = 0x0001,
+ .in_cables = 0x0001
+ };
+ static const struct snd_usb_audio_quirk uaxx_quirk = {
+ .type = QUIRK_MIDI_FIXED_ENDPOINT,
+ .data = &uaxx_ep
+ };
+ const struct snd_usb_audio_quirk *spec;
+ struct usb_host_interface *alt;
+
+ spec = chip->usb_id == USB_ID(0x0582, 0x002b) ? &ua700_quirk : &uaxx_quirk;
+
+ /* both PCM and MIDI interfaces have 2 or more altsettings */
+ if (iface->num_altsetting < 2)
+ return -ENXIO;
+ alt = &iface->altsetting[1];
+
+ switch (alt->desc.bNumEndpoints) {
+ case 2:
+ break;
+ case 1:
+ return 1; /* Leave this to pcm. */
+ default:
+ return -ENXIO;
+ }
+
+ return __snd_usbmidi_create(chip->card, iface, &chip->midi_list, spec, chip->usb_id,
+ &chip->num_rawmidis);
+}
+
+static int create_uaxx_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ struct usb_interface_descriptor *altsd;
+ struct usb_host_interface *alts;
+
+ /* both PCM and MIDI interfaces have 2 or more altsettings */
+ if (iface->num_altsetting < 2)
+ return -ENXIO;
+ alts = &iface->altsetting[1];
+ altsd = get_iface_desc(alts);
+
+ switch (altsd->bNumEndpoints) {
+ case 2:
+ return create_uaxx_midi_quirk(chip, iface, driver, quirk);
+ case 1:
+ return create_uaxx_pcm_quirk(chip, iface, driver, quirk);
+ default:
+ return -ENXIO;
+ }
+}
+
/*
* Create a standard mixer for the specified interface.
*/
@@ -498,6 +594,44 @@ static int create_standard_mixer_quirk(struct snd_usb_audio *chip,
return snd_usb_create_mixer(chip, quirk->ifnum);
}
+int snd_usb_parse_pcms_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk)
+{
+ typedef int (*quirk_func_t)(struct snd_usb_audio *,
+ struct usb_interface *,
+ struct usb_driver *,
+ const struct snd_usb_audio_quirk *);
+ static const quirk_func_t quirk_funcs[] = {
+ [QUIRK_IGNORE_INTERFACE] = ignore_interface_quirk,
+ [QUIRK_COMPOSITE] = create_composite_pcm_quirk,
+ [QUIRK_AUTODETECT] = create_autodetect_pcm_quirk,
+ [QUIRK_MIDI_STANDARD_INTERFACE] = ignore_interface_quirk,
+ [QUIRK_MIDI_FIXED_ENDPOINT] = ignore_interface_quirk,
+ [QUIRK_MIDI_YAMAHA] = ignore_interface_quirk,
+ [QUIRK_MIDI_ROLAND] = ignore_interface_quirk,
+ [QUIRK_MIDI_MIDIMAN] = ignore_interface_quirk,
+ [QUIRK_MIDI_NOVATION] = ignore_interface_quirk,
+ [QUIRK_MIDI_RAW_BYTES] = ignore_interface_quirk,
+ [QUIRK_MIDI_EMAGIC] = ignore_interface_quirk,
+ [QUIRK_MIDI_CME] = ignore_interface_quirk,
+ [QUIRK_MIDI_AKAI] = ignore_interface_quirk,
+ [QUIRK_MIDI_FTDI] = ignore_interface_quirk,
+ [QUIRK_MIDI_CH345] = ignore_interface_quirk,
+ [QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk,
+ [QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk,
+ [QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_pcm_quirk,
+ [QUIRK_AUDIO_STANDARD_MIXER] = ignore_interface_quirk,
+ };
+
+ if (quirk->type < QUIRK_TYPE_COUNT)
+ return quirk_funcs[quirk->type](chip, iface, driver, quirk);
+
+ usb_audio_err(chip, "invalid quirk type %d\n", quirk->type);
+ return -ENXIO;
+}
+
/*
* audio-interface quirks
*
@@ -537,12 +671,11 @@ int snd_usb_create_quirk(struct snd_usb_audio *chip,
[QUIRK_AUDIO_STANDARD_MIXER] = create_standard_mixer_quirk,
};
- if (quirk->type < QUIRK_TYPE_COUNT) {
+ if (quirk->type < QUIRK_TYPE_COUNT)
return quirk_funcs[quirk->type](chip, iface, driver, quirk);
- } else {
- usb_audio_err(chip, "invalid quirk type %d\n", quirk->type);
- return -ENXIO;
- }
+
+ usb_audio_err(chip, "invalid quirk type %d\n", quirk->type);
+ return -ENXIO;
}
/*
diff --git a/sound/usb/quirks.h b/sound/usb/quirks.h
index 0ea079688261..1344e4e635d8 100644
--- a/sound/usb/quirks.h
+++ b/sound/usb/quirks.h
@@ -8,6 +8,10 @@ struct audioformat;
struct snd_usb_endpoint;
struct snd_usb_substream;
+int snd_usb_parse_pcms_quirk(struct snd_usb_audio *chip,
+ struct usb_interface *iface,
+ struct usb_driver *driver,
+ const struct snd_usb_audio_quirk *quirk);
int snd_usb_create_quirk(struct snd_usb_audio *chip,
struct usb_interface *iface,
struct usb_driver *driver,
--
2.25.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 05/15] ALSA: usb: Implement two-stage stream creation mechanism
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (3 preceding siblings ...)
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
2025-04-09 11:07 ` [RFC 06/15] ALSA: usb: Implement two-stage chip probing mechanism Cezary Rojewski
` (12 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
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
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 06/15] ALSA: usb: Implement two-stage chip probing mechanism
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (4 preceding siblings ...)
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
2025-04-09 11:07 ` [RFC 07/15] ALSA: usb: Switch to the two-stage chip probing Cezary Rojewski
` (11 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
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
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 07/15] ALSA: usb: Switch to the two-stage chip probing
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (5 preceding siblings ...)
2025-04-09 11:07 ` [RFC 06/15] ALSA: usb: Implement two-stage chip probing mechanism Cezary Rojewski
@ 2025-04-09 11:07 ` Cezary Rojewski
2025-04-09 11:07 ` [RFC 08/15] ALSA: usb: Switch to the two-stage stream creation Cezary Rojewski
` (10 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
Utilize newly added functions and update existing probing procedure of
USB Audio-Class (AC) device from one-stage into two-stage process. In
consequence, drop snd_usb_audio_create() and rewrite usb_audio_probe()
to first do snd_usb_probe_unlocked() which is intended to be shared by
both ALSA and ASoC driver before continuing with creating all the
streams.
While at it, do not forget about snd_audio_disconnect(). Use
snd_usb_chip_release() to keep both probe() and disconnect()
synchronized.
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
sound/usb/card.c | 281 ++++++++---------------------------------------
1 file changed, 45 insertions(+), 236 deletions(-)
diff --git a/sound/usb/card.c b/sound/usb/card.c
index a9e37fe0d29e..4af69a69b5ce 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -21,6 +21,7 @@
#include <linux/bitops.h>
+#include <linux/cleanup.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/slab.h>
@@ -687,86 +688,6 @@ static void usb_audio_make_longname(struct usb_device *dev,
}
}
-/*
- * 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_SUPER:
- case USB_SPEED_SUPER_PLUS:
- break;
- default:
- dev_err(&dev->dev, "unknown device speed %d\n", snd_usb_get_speed(dev));
- return -ENXIO;
- }
-
- chip = kzalloc(sizeof(*chip), GFP_KERNEL);
- if (!chip)
- return -ENOMEM;
-
- err = snd_card_new(&intf->dev, index[idx], id[idx], THIS_MODULE, 0, &card);
- if (err < 0) {
- dev_err(&dev->dev, "cannot create card instance %d\n", idx);
- kfree(chip);
- return err;
- }
-
- 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->generic_implicit_fb = implicit_fb[idx];
- chip->autoclock = autoclock;
- chip->lowlatency = lowlatency;
- 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->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);
-
- if (quirk_flags[idx])
- chip->quirk_flags = quirk_flags[idx];
- else
- snd_usb_init_quirk_flags(chip);
-
- 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)
{
@@ -870,7 +791,6 @@ static int snd_usb_parse_pcms(struct snd_usb_audio *chip, struct usb_interface *
return 0;
}
-__maybe_unused
static int snd_usb_create_nonpcms(struct snd_usb_audio *chip, struct usb_interface *iface,
struct usb_driver *driver)
{
@@ -1118,7 +1038,6 @@ static int snd_usb_sanity_check(struct usb_interface *iface,
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)
{
@@ -1179,7 +1098,6 @@ int snd_usb_bind_card(struct snd_usb_audio *chip, struct snd_card *card,
}
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;
@@ -1227,170 +1145,68 @@ static int try_to_register_card(struct snd_usb_audio *chip, int ifnum)
* 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)
+static int usb_audio_probe(struct usb_interface *iface, 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;
+ struct snd_usb_audio *chip;
+ struct usb_device *udev;
+ int ifnum, ret;
- 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;
- if (quirk && quirk->ifnum == QUIRK_NODEV_INTERFACE)
+ alts = &iface->altsetting[0];
+ ifnum = alts->desc.bInterfaceNumber;
+ udev = interface_to_usbdev(iface);
+
+ udev = interface_to_usbdev(iface);
+ if (udev->audsb_capable)
return -ENODEV;
- 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;
- 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;
- }
- find_last_interface(chip);
- }
-
- 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;
+ guard(mutex)(®ister_mutex);
+
+ ret = snd_usb_probe_unlocked(iface, usb_id, &usb_audio_driver);
+ chip = dev_get_drvdata(&udev->dev);
+ /* If chip isn't there, probe failed at the first interface. */
+ if (!chip)
+ return ret;
+ if (ret)
+ goto err_try_register;
+ if (!chip->card) {
+ ret = snd_usb_create_card(chip, iface);
+ if (ret)
+ goto err_no_register;
}
- dev_set_drvdata(&dev->dev, chip);
+ ret = snd_usb_add_pcms(chip);
+ if (ret)
+ goto err_try_register;
- if (ignore_ctl_error)
- chip->quirk_flags |= QUIRK_FLAG_IGNORE_CTL_ERROR;
-
- if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND)
- usb_disable_autosuspend(interface_to_usbdev(intf));
-
- /*
- * 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;
-
- 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);
- if (err < 0)
- goto __error;
- }
+ ret = snd_usb_create_nonpcms(chip, iface, &usb_audio_driver);
+ if (ret)
+ goto err_try_register;
if (chip->need_delayed_register) {
- dev_info(&dev->dev,
- "Found post-registration device assignment: %08x:%02x\n",
+ dev_info(&udev->dev, "Found post-registration device assignment: %08x:%02x\n",
chip->usb_id, ifnum);
chip->need_delayed_register = false; /* clear again */
}
- err = try_to_register_card(chip, ifnum);
- if (err < 0)
- goto __error_no_register;
+ ret = try_to_register_card(chip, ifnum);
+ if (ret < 0)
+ goto err_no_register;
- /* don't want to fail when snd_media_device_create() fails */
- snd_media_device_create(chip, intf);
+ /* Media device is optional, ignore error checks. */
+ snd_media_device_create(chip, iface);
- 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);
- mutex_unlock(®ister_mutex);
return 0;
- __error:
- /* in the case of error in secondary interface, still try to register */
- if (chip)
- try_to_register_card(chip, ifnum);
+err_try_register:
+ /* In the case of error in secondary interface, still try to register. */
+ try_to_register_card(chip, ifnum);
- __error_no_register:
- 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);
- snd_usb_chip_free(chip);
- }
- }
- mutex_unlock(®ister_mutex);
- return err;
+err_no_register:
+ atomic_dec(&chip->active);
+ snd_usb_chip_release(chip, iface);
+ return ret;
}
/*
@@ -1445,15 +1261,8 @@ static void usb_audio_disconnect(struct usb_interface *intf)
if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND)
usb_enable_autosuspend(interface_to_usbdev(intf));
- chip->num_interfaces--;
- if (chip->num_interfaces <= 0) {
- usb_chip[chip->index] = NULL;
- mutex_unlock(®ister_mutex);
- snd_card_free(card);
- snd_usb_chip_free(chip);
- } else {
- mutex_unlock(®ister_mutex);
- }
+ snd_usb_chip_release(chip, intf);
+ mutex_unlock(®ister_mutex);
}
/* lock the shutdown (disconnect) task and autoresume */
--
2.25.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 08/15] ALSA: usb: Switch to the two-stage stream creation
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (6 preceding siblings ...)
2025-04-09 11:07 ` [RFC 07/15] ALSA: usb: Switch to the two-stage chip probing Cezary Rojewski
@ 2025-04-09 11:07 ` Cezary Rojewski
2025-04-09 11:07 ` [RFC 09/15] ALSA: usb: Switch to the two-stage quirk applying Cezary Rojewski
` (9 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
Utilize newly added functions and update existing stream craetion
procedure for USB Audio-Class (AC) device from one-stage into
two-stage process. In consequence, snd_usb_create_stream() and
__snd_usb_add_audio_stream().
To complete the process, update snd_usb_create_streams() so that it
performs correct operation: parse pcms -or- create non-pcms based on
the SubClass value of the USB interface.
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
sound/usb/card.c | 119 ++++++++++-----------------------------------
sound/usb/stream.c | 97 +-----------------------------------
2 files changed, 29 insertions(+), 187 deletions(-)
diff --git a/sound/usb/card.c b/sound/usb/card.c
index 4af69a69b5ce..354bf76988ab 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -138,87 +138,6 @@ static void snd_usb_stream_disconnect(struct snd_usb_stream *as)
}
}
-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_usb_midi_v2_create(chip, iface, NULL,
- chip->usb_id);
- if (err < 0) {
- dev_err(&dev->dev,
- "%u:%d: cannot create sequencer device\n",
- ctrlif, interface);
- return -EINVAL;
- }
- return usb_driver_claim_interface(&usb_audio_driver, iface,
- USB_AUDIO_IFACE_UNUSED);
- }
-
- 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;
- }
-
- snd_usb_add_ctrl_interface_link(chip, interface, ctrlif);
-
- if (! snd_usb_parse_audio_interface(chip, interface)) {
- usb_set_interface(dev, interface, 0); /* reset the current interface */
- return usb_driver_claim_interface(&usb_audio_driver, iface,
- USB_AUDIO_IFACE_UNUSED);
- }
-
- return 0;
-}
-
-__maybe_unused
static struct usb_interface *snd_usb_streaming_iface(struct snd_usb_audio *chip, int ctrlif,
int interface, u8 subclass)
{
@@ -274,7 +193,6 @@ static struct usb_interface *snd_usb_streaming_iface(struct snd_usb_audio *chip,
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)
@@ -301,7 +219,6 @@ static int snd_usb_parse_claim_pcm(struct snd_usb_audio *chip, int ctrlif,
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)
@@ -325,18 +242,30 @@ static int snd_usb_create_claim_midi(struct snd_usb_audio *chip, int ctrlif,
/*
* parse audio control descriptor and create pcm/midi streams
*/
-static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
+static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif,
+ struct usb_driver *iface_driver, u8 subclass)
{
+ int (*create_stream)(struct snd_usb_audio *, int,
+ struct usb_interface *, struct usb_driver *);
struct usb_device *dev = chip->dev;
struct usb_host_interface *host_iface;
struct usb_interface_descriptor *altsd;
+ struct usb_interface *iface;
int i, protocol;
+ int ifnum;
/* find audiocontrol interface */
host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0];
altsd = get_iface_desc(host_iface);
protocol = altsd->bInterfaceProtocol;
+ if (subclass == USB_SUBCLASS_AUDIOSTREAMING)
+ create_stream = snd_usb_parse_claim_pcm;
+ else if (subclass == USB_SUBCLASS_MIDISTREAMING)
+ create_stream = snd_usb_create_claim_midi;
+ else
+ return -EINVAL;
+
switch (protocol) {
default:
dev_warn(&dev->dev,
@@ -385,8 +314,12 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
return -EINVAL;
}
- for (i = 0; i < h1->bInCollection; i++)
- snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]);
+ for (i = 0; i < h1->bInCollection; i++) {
+ ifnum = h1->baInterfaceNr[i];
+ iface = snd_usb_streaming_iface(chip, ctrlif, ifnum, subclass);
+ if (!IS_ERR_OR_NULL(iface))
+ create_stream(chip, ctrlif, iface, iface_driver);
+ }
break;
}
@@ -431,10 +364,12 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
}
for (i = 0; i < assoc->bInterfaceCount; i++) {
- int intf = assoc->bFirstInterface + i;
-
- if (intf != ctrlif)
- snd_usb_create_stream(chip, ctrlif, intf);
+ ifnum = assoc->bFirstInterface + i;
+ if (ifnum == ctrlif)
+ continue;
+ iface = snd_usb_streaming_iface(chip, ctrlif, ifnum, subclass);
+ if (!IS_ERR_OR_NULL(iface))
+ create_stream(chip, ctrlif, iface, iface_driver);
}
break;
@@ -784,7 +719,7 @@ static int snd_usb_parse_pcms(struct snd_usb_audio *chip, struct usb_interface *
}
/* Parse pcm interfaces in generic fashion. */
- ret = snd_usb_create_streams(chip, ifnum);
+ ret = snd_usb_create_streams(chip, ifnum, driver, USB_SUBCLASS_AUDIOSTREAMING);
if (ret < 0)
return ret;
@@ -813,7 +748,7 @@ static int snd_usb_create_nonpcms(struct snd_usb_audio *chip, struct usb_interfa
}
/* Create midi/mixer interfaces in generic fashion. */
- ret = snd_usb_create_streams(chip, ifnum);
+ ret = snd_usb_create_streams(chip, ifnum, driver, USB_SUBCLASS_MIDISTREAMING);
if (ret < 0)
return ret;
diff --git a/sound/usb/stream.c b/sound/usb/stream.c
index 1cf59e6d8ce6..601dc9ed020a 100644
--- a/sound/usb/stream.c
+++ b/sound/usb/stream.c
@@ -94,8 +94,6 @@ static void snd_usb_init_substream(struct snd_usb_stream *as,
subs->pkt_offset_adj = 0;
subs->stream_offset_adj = 0;
- snd_usb_set_pcm_ops(as->pcm, stream);
-
list_add_tail(&fp->list, &subs->fmt_list);
subs->formats |= fp->formats;
subs->num_formats++;
@@ -110,8 +108,6 @@ static void snd_usb_init_substream(struct snd_usb_stream *as,
snd_usb_power_domain_set(subs->stream->chip, pd,
UAC3_PD_STATE_D1);
}
-
- snd_usb_preallocate_buffer(subs);
}
/* kctl callbacks for usb-audio channel maps */
@@ -468,96 +464,7 @@ snd_pcm_chmap_elem *convert_chmap_v3(struct uac3_cluster_header_descriptor
return chmap;
}
-/*
- * add this endpoint to the chip instance.
- * if a stream with the same endpoint already exists, append to it.
- * if not, create a new pcm stream. note, fp is added to the substream
- * fmt_list and will be freed on the chip instance release. do not free
- * fp or do remove it from the substream fmt_list to avoid double-free.
- */
-static int __snd_usb_add_audio_stream(struct snd_usb_audio *chip,
- int stream,
- struct audioformat *fp,
- struct snd_usb_power_domain *pd)
-
-{
- struct snd_usb_stream *as;
- struct snd_usb_substream *subs;
- struct snd_pcm *pcm;
- int err;
-
- 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->registered)
- chip->need_delayed_register = true;
-
- /* look for an empty stream */
- 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)
- continue;
- err = snd_pcm_new_stream(as->pcm, stream, 1);
- if (err < 0)
- return err;
- snd_usb_init_substream(as, stream, fp, pd);
- return add_chmap(as->pcm, stream, subs);
- }
-
- /* create a new pcm */
- 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;
- err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs,
- stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0,
- stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1,
- &pcm);
- if (err < 0) {
- kfree(as);
- return err;
- }
- as->pcm = pcm;
- pcm->private_data = as;
- pcm->info_flags = 0;
- if (chip->pcm_devs > 0)
- sprintf(pcm->name, "USB Audio #%d", chip->pcm_devs);
- else
- strcpy(pcm->name, "USB Audio");
-
- 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);
-
- chip->pcm_devs++;
-
- snd_usb_proc_pcm_format_add(as);
-
- 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)
{
@@ -705,7 +612,7 @@ int snd_usb_add_audio_stream(struct snd_usb_audio *chip,
int stream,
struct audioformat *fp)
{
- return __snd_usb_add_audio_stream(chip, stream, fp, NULL);
+ return snd_usb_parse_pcm(chip, stream, fp, NULL);
}
static int snd_usb_add_audio_stream_v3(struct snd_usb_audio *chip,
@@ -713,7 +620,7 @@ static int snd_usb_add_audio_stream_v3(struct snd_usb_audio *chip,
struct audioformat *fp,
struct snd_usb_power_domain *pd)
{
- return __snd_usb_add_audio_stream(chip, stream, fp, pd);
+ return snd_usb_parse_pcm(chip, stream, fp, pd);
}
static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip,
--
2.25.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 09/15] ALSA: usb: Switch to the two-stage quirk applying
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (7 preceding siblings ...)
2025-04-09 11:07 ` [RFC 08/15] ALSA: usb: Switch to the two-stage stream creation Cezary Rojewski
@ 2025-04-09 11:07 ` Cezary Rojewski
2025-04-09 11:07 ` [RFC 10/15] ALSA: usb: Export PCM operations Cezary Rojewski
` (8 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
Utilize newly added functions and update existing apply-quirks procedure
for USB Audio-Class (AC) device from one-stage into two-stage process.
In consequence, drop create_uaxx_quirk() and create_autodetect_quirk()
and update existing snd_usb_create_quirk() so that it only creates new
streams, leaving the PCM parsing process to snd_usb_parse_pcms_quirk().
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
sound/usb/quirks.c | 42 ++----------------------------------------
1 file changed, 2 insertions(+), 40 deletions(-)
diff --git a/sound/usb/quirks.c b/sound/usb/quirks.c
index 4e402c6406a9..037a45c79fbe 100644
--- a/sound/usb/quirks.c
+++ b/sound/usb/quirks.c
@@ -412,7 +412,6 @@ static int create_autodetect_pcm_quirk(struct snd_usb_audio *chip,
return create_auto_pcm_quirk(chip, iface, driver);
}
-__maybe_unused
static int create_autodetect_midi_quirk(struct snd_usb_audio *chip,
struct usb_interface *iface,
struct usb_driver *driver,
@@ -423,19 +422,6 @@ static int create_autodetect_midi_quirk(struct snd_usb_audio *chip,
return 1;
}
-static int create_autodetect_quirk(struct snd_usb_audio *chip,
- struct usb_interface *iface,
- struct usb_driver *driver,
- const struct snd_usb_audio_quirk *quirk)
-{
- int err;
-
- err = create_auto_pcm_quirk(chip, iface, driver);
- if (err == -ENODEV)
- err = create_auto_midi_quirk(chip, iface, driver);
- return err;
-}
-
static int create_uaxx_pcm_quirk(struct snd_usb_audio *chip,
struct usb_interface *iface,
struct usb_driver *driver,
@@ -556,30 +542,6 @@ static int create_uaxx_midi_quirk(struct snd_usb_audio *chip,
&chip->num_rawmidis);
}
-static int create_uaxx_quirk(struct snd_usb_audio *chip,
- struct usb_interface *iface,
- struct usb_driver *driver,
- const struct snd_usb_audio_quirk *quirk)
-{
- struct usb_interface_descriptor *altsd;
- struct usb_host_interface *alts;
-
- /* both PCM and MIDI interfaces have 2 or more altsettings */
- if (iface->num_altsetting < 2)
- return -ENXIO;
- alts = &iface->altsetting[1];
- altsd = get_iface_desc(alts);
-
- switch (altsd->bNumEndpoints) {
- case 2:
- return create_uaxx_midi_quirk(chip, iface, driver, quirk);
- case 1:
- return create_uaxx_pcm_quirk(chip, iface, driver, quirk);
- default:
- return -ENXIO;
- }
-}
-
/*
* Create a standard mixer for the specified interface.
*/
@@ -652,7 +614,7 @@ int snd_usb_create_quirk(struct snd_usb_audio *chip,
static const quirk_func_t quirk_funcs[] = {
[QUIRK_IGNORE_INTERFACE] = ignore_interface_quirk,
[QUIRK_COMPOSITE] = create_composite_quirk,
- [QUIRK_AUTODETECT] = create_autodetect_quirk,
+ [QUIRK_AUTODETECT] = create_autodetect_midi_quirk,
[QUIRK_MIDI_STANDARD_INTERFACE] = create_any_midi_quirk,
[QUIRK_MIDI_FIXED_ENDPOINT] = create_any_midi_quirk,
[QUIRK_MIDI_YAMAHA] = create_any_midi_quirk,
@@ -667,7 +629,7 @@ int snd_usb_create_quirk(struct snd_usb_audio *chip,
[QUIRK_MIDI_CH345] = create_any_midi_quirk,
[QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk,
[QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk,
- [QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_quirk,
+ [QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_midi_quirk,
[QUIRK_AUDIO_STANDARD_MIXER] = create_standard_mixer_quirk,
};
--
2.25.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 10/15] ALSA: usb: Export PCM operations
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (8 preceding siblings ...)
2025-04-09 11:07 ` [RFC 09/15] ALSA: usb: Switch to the two-stage quirk applying Cezary Rojewski
@ 2025-04-09 11:07 ` Cezary Rojewski
2025-04-09 11:07 ` [RFC 11/15] ALSA: usb: Export usb_interface driver operations Cezary Rojewski
` (7 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
Existing PCM operations can be reused by DAIs on the ASoC side. Not all
of them are exported as endpoints of the offloaded USB device are
entirely controlled by the Audio Sideband and AudioDSP hardware. As ASoC
occupies substream->private_data, update existing open() and close() to
utilize runtime->private_data for obtaining USB stream instead.
Provide a wrapper for ASoC driver i.e.: snd_usb_pcm_open() as they are
unaware of details of USB streams and do not access USB substreams
directly.
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
include/sound/usb.h | 9 +++
sound/usb/pcm.c | 159 +++++++++++++++++++++++++++++++-------------
2 files changed, 120 insertions(+), 48 deletions(-)
diff --git a/include/sound/usb.h b/include/sound/usb.h
index f30a8a96c49e..daba868fb6e0 100644
--- a/include/sound/usb.h
+++ b/include/sound/usb.h
@@ -93,6 +93,15 @@ struct snd_usb_audio {
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);
+void snd_usb_pcm_hw_init(struct list_head *stream_entry, int dir, struct snd_pcm_hardware *hw);
+
+/* PCM operations, see struct snd_pcm_ops. */
+int snd_usb_pcm_open(struct snd_pcm_substream *substream);
+int snd_usb_pcm_close(struct snd_pcm_substream *substream);
+int snd_usb_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params);
+int snd_usb_pcm_hw_free(struct snd_pcm_substream *substream);
+int snd_usb_pcm_prepare(struct snd_pcm_substream *substream);
#define USB_AUDIO_IFACE_UNUSED ((void *)-1L)
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c
index ea698f061af9..e04e47c0a35e 100644
--- a/sound/usb/pcm.c
+++ b/sound/usb/pcm.c
@@ -471,8 +471,8 @@ static void close_endpoints(struct snd_usb_audio *chip,
* if sg buffer is supported on the later version of alsa, we'll follow
* that.
*/
-static int snd_usb_hw_params(struct snd_pcm_substream *substream,
- struct snd_pcm_hw_params *hw_params)
+int snd_usb_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
{
struct snd_usb_substream *subs = substream->runtime->private_data;
struct snd_usb_audio *chip = subs->stream->chip;
@@ -579,13 +579,14 @@ static int snd_usb_hw_params(struct snd_pcm_substream *substream,
return ret;
}
+EXPORT_SYMBOL_GPL(snd_usb_pcm_hw_params);
/*
* hw_free callback
*
* reset the audio format and release the buffer
*/
-static int snd_usb_hw_free(struct snd_pcm_substream *substream)
+int snd_usb_pcm_hw_free(struct snd_pcm_substream *substream)
{
struct snd_usb_substream *subs = substream->runtime->private_data;
struct snd_usb_audio *chip = subs->stream->chip;
@@ -603,6 +604,7 @@ static int snd_usb_hw_free(struct snd_pcm_substream *substream)
return 0;
}
+EXPORT_SYMBOL_GPL(snd_usb_pcm_hw_free);
/* free-wheeling mode? (e.g. dmix) */
static int in_free_wheeling_mode(struct snd_pcm_runtime *runtime)
@@ -634,7 +636,7 @@ static int lowlatency_playback_available(struct snd_pcm_runtime *runtime,
*
* only a few subtle things...
*/
-static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
+int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_usb_substream *subs = runtime->private_data;
@@ -693,6 +695,7 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
snd_usb_unlock_shutdown(chip);
return ret;
}
+EXPORT_SYMBOL_GPL(snd_usb_pcm_prepare);
/*
* h/w constraints
@@ -1075,6 +1078,47 @@ static int hw_rule_periods_implicit_fb(struct snd_pcm_hw_params *params,
return apply_hw_params_minmax(it, rmin, rmax);
}
+static void usb_pcm_hw_init(struct snd_usb_substream *subs, struct snd_pcm_hardware *hw)
+{
+ const struct audioformat *fp;
+
+ hw->formats = subs->formats;
+ hw->rate_min = 0x7fffffff;
+ hw->rate_max = 0;
+ hw->channels_min = 256;
+ hw->channels_max = 0;
+ hw->rates = 0;
+
+ /* Now reduce the scope based on substream capabilities. */
+ list_for_each_entry(fp, &subs->fmt_list, list) {
+ hw->rates |= fp->rates;
+ if (hw->rate_min > fp->rate_min)
+ hw->rate_min = fp->rate_min;
+ if (hw->rate_max < fp->rate_max)
+ hw->rate_max = fp->rate_max;
+ if (hw->channels_min > fp->channels)
+ hw->channels_min = fp->channels;
+ if (hw->channels_max < fp->channels)
+ hw->channels_max = fp->channels;
+ if (fp->fmt_type == UAC_FORMAT_TYPE_II && fp->frame_size > 0) {
+ /* FIXME: there might be more than one audio formats... */
+ hw->period_bytes_min = fp->frame_size;
+ hw->period_bytes_max = fp->frame_size;
+ }
+ }
+}
+
+void snd_usb_pcm_hw_init(struct list_head *stream_entry, int dir, struct snd_pcm_hardware *hw)
+{
+ struct snd_usb_stream *as;
+
+ if (dir <= SNDRV_PCM_STREAM_LAST) {
+ as = list_entry(stream_entry, struct snd_usb_stream, list);
+ usb_pcm_hw_init(&as->substream[dir], hw);
+ }
+}
+EXPORT_SYMBOL_GPL(snd_usb_pcm_hw_init);
+
/*
* set up the runtime hardware information.
*/
@@ -1086,30 +1130,10 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre
int param_period_time_if_needed = -1;
int err;
- runtime->hw.formats = subs->formats;
-
- runtime->hw.rate_min = 0x7fffffff;
- runtime->hw.rate_max = 0;
- runtime->hw.channels_min = 256;
- runtime->hw.channels_max = 0;
- runtime->hw.rates = 0;
ptmin = UINT_MAX;
- /* check min/max rates and channels */
+ usb_pcm_hw_init(subs, &runtime->hw);
+
list_for_each_entry(fp, &subs->fmt_list, list) {
- runtime->hw.rates |= fp->rates;
- if (runtime->hw.rate_min > fp->rate_min)
- runtime->hw.rate_min = fp->rate_min;
- if (runtime->hw.rate_max < fp->rate_max)
- runtime->hw.rate_max = fp->rate_max;
- if (runtime->hw.channels_min > fp->channels)
- runtime->hw.channels_min = fp->channels;
- if (runtime->hw.channels_max < fp->channels)
- runtime->hw.channels_max = fp->channels;
- if (fp->fmt_type == UAC_FORMAT_TYPE_II && fp->frame_size > 0) {
- /* FIXME: there might be more than one audio formats... */
- runtime->hw.period_bytes_min = runtime->hw.period_bytes_max =
- fp->frame_size;
- }
pt = 125 * (1 << fp->datainterval);
ptmin = min(ptmin, pt);
}
@@ -1202,12 +1226,12 @@ static int setup_hw_info(struct snd_pcm_runtime *runtime, struct snd_usb_substre
return 0;
}
-static int snd_usb_pcm_open(struct snd_pcm_substream *substream)
+static int __usb_pcm_open(struct snd_pcm_substream *substream)
{
- int direction = substream->stream;
- struct snd_usb_stream *as = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
- struct snd_usb_substream *subs = &as->substream[direction];
+ struct snd_usb_substream *subs = runtime->private_data;
+ struct snd_usb_stream *as = subs->stream;
+ int direction = substream->stream;
int ret;
runtime->hw = snd_usb_hardware;
@@ -1215,7 +1239,6 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream)
if (direction == SNDRV_PCM_STREAM_PLAYBACK &&
as->chip->lowlatency)
runtime->hw.info |= SNDRV_PCM_INFO_SYNC_APPLPTR;
- runtime->private_data = subs;
subs->pcm_substream = substream;
/* runtime PM is also done there */
@@ -1227,36 +1250,76 @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream)
ret = setup_hw_info(runtime, subs);
if (ret < 0)
return ret;
- ret = snd_usb_autoresume(subs->stream->chip);
+ ret = snd_usb_autoresume(as->chip);
if (ret < 0)
return ret;
- ret = snd_media_stream_init(subs, as->pcm, direction);
+ ret = snd_media_stream_init(subs, substream->pcm, direction);
if (ret < 0)
- snd_usb_autosuspend(subs->stream->chip);
+ snd_usb_autosuspend(as->chip);
return ret;
}
-static int snd_usb_pcm_close(struct snd_pcm_substream *substream)
+static int usb_pcm_open(struct snd_pcm_substream *substream)
{
- int direction = substream->stream;
struct snd_usb_stream *as = snd_pcm_substream_chip(substream);
- struct snd_usb_substream *subs = &as->substream[direction];
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ /*
+ * On ASoC side, substream->private_data is occupied by the
+ * framework. For ALSA and ASoC to share USB PCM operations
+ * runtime->private_data shall be utilized instead.
+ */
+ runtime->private_data = &as->substream[substream->stream];
+
+ return __usb_pcm_open(substream);
+}
+
+int snd_usb_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_usb_stream *as;
+
+ /*
+ * On ASoC side, substream->private_data is occupied by the
+ * framework. For ALSA and ASoC to share USB PCM operations
+ * runtime->private_data shall be utilized instead.
+ */
+ as = list_entry(runtime->private_data, struct snd_usb_stream, list);
+ runtime->private_data = &as->substream[substream->stream];
+
+ return __usb_pcm_open(substream);
+}
+EXPORT_SYMBOL_GPL(snd_usb_pcm_open);
+
+int snd_usb_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_usb_substream *subs = substream->runtime->private_data;
+ struct snd_usb_stream *as = subs->stream;
int ret;
snd_media_stop_pipeline(subs);
- if (!snd_usb_lock_shutdown(subs->stream->chip)) {
+ if (!snd_usb_lock_shutdown(as->chip)) {
ret = snd_usb_pcm_change_state(subs, UAC3_PD_STATE_D1);
- snd_usb_unlock_shutdown(subs->stream->chip);
+ snd_usb_unlock_shutdown(as->chip);
if (ret < 0)
return ret;
}
subs->pcm_substream = NULL;
- snd_usb_autosuspend(subs->stream->chip);
+ snd_usb_autosuspend(as->chip);
return 0;
}
+EXPORT_SYMBOL_GPL(snd_usb_pcm_close);
+
+static int usb_pcm_close(struct snd_pcm_substream *substream)
+{
+ int ret = snd_usb_pcm_close(substream);
+
+ substream->runtime->private_data = NULL;
+ return ret;
+}
/* Since a URB can handle only a single linear buffer, we must use double
* buffering when the data to be transferred overflows the buffer boundary.
@@ -1744,10 +1807,10 @@ static int snd_usb_substream_capture_trigger(struct snd_pcm_substream *substream
}
static const struct snd_pcm_ops snd_usb_playback_ops = {
- .open = snd_usb_pcm_open,
- .close = snd_usb_pcm_close,
- .hw_params = snd_usb_hw_params,
- .hw_free = snd_usb_hw_free,
+ .open = usb_pcm_open,
+ .close = usb_pcm_close,
+ .hw_params = snd_usb_pcm_hw_params,
+ .hw_free = snd_usb_pcm_hw_free,
.prepare = snd_usb_pcm_prepare,
.trigger = snd_usb_substream_playback_trigger,
.sync_stop = snd_usb_pcm_sync_stop,
@@ -1756,10 +1819,10 @@ static const struct snd_pcm_ops snd_usb_playback_ops = {
};
static const struct snd_pcm_ops snd_usb_capture_ops = {
- .open = snd_usb_pcm_open,
- .close = snd_usb_pcm_close,
- .hw_params = snd_usb_hw_params,
- .hw_free = snd_usb_hw_free,
+ .open = usb_pcm_open,
+ .close = usb_pcm_close,
+ .hw_params = snd_usb_pcm_hw_params,
+ .hw_free = snd_usb_pcm_hw_free,
.prepare = snd_usb_pcm_prepare,
.trigger = snd_usb_substream_capture_trigger,
.sync_stop = snd_usb_pcm_sync_stop,
--
2.25.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 11/15] ALSA: usb: Export usb_interface driver operations
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (9 preceding siblings ...)
2025-04-09 11:07 ` [RFC 10/15] ALSA: usb: Export PCM operations Cezary Rojewski
@ 2025-04-09 11:07 ` Cezary Rojewski
2025-04-09 11:07 ` [RFC 12/15] ALSA: usb: Export card-naming procedure Cezary Rojewski
` (6 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
Existing usb_driver operations can be reused by component-drivers on the
ASoC side.
ASoC embraces modular approach and it is entirely acceptable for
component to be unbound while the USB device is still plugged in. In
such case, PCM list built when scanning USB interfaces can be kept
intact to avoid scanning the device a second time when the component is
probed again. Add snd_usb_release_resources() to cover that scenario.
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
include/sound/usb.h | 8 +++++
sound/usb/card.c | 88 +++++++++++++++++++++++++++++++++++++++------
2 files changed, 85 insertions(+), 11 deletions(-)
diff --git a/include/sound/usb.h b/include/sound/usb.h
index daba868fb6e0..fbcfa2b985f7 100644
--- a/include/sound/usb.h
+++ b/include/sound/usb.h
@@ -90,11 +90,19 @@ struct snd_usb_audio {
struct snd_intf_to_ctrl intf_to_ctrl[MAX_CARD_INTERFACES];
};
+void snd_usb_release_resources(struct snd_usb_audio *chip);
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);
void snd_usb_pcm_hw_init(struct list_head *stream_entry, int dir, struct snd_pcm_hardware *hw);
+/* USB interface operations, see struct usb_driver. */
+int snd_usb_probe(struct usb_interface *iface, const struct usb_device_id *usb_id,
+ struct usb_driver *driver);
+void snd_usb_disconnect(struct usb_interface *iface);
+int snd_usb_suspend(struct usb_interface *intf, pm_message_t message);
+int snd_usb_resume(struct usb_interface *intf);
+
/* PCM operations, see struct snd_pcm_ops. */
int snd_usb_pcm_open(struct snd_pcm_substream *substream);
int snd_usb_pcm_close(struct snd_pcm_substream *substream);
diff --git a/sound/usb/card.c b/sound/usb/card.c
index 354bf76988ab..a4bd8ba6a26c 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -773,7 +773,8 @@ static void snd_usb_chip_free(struct snd_usb_audio *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)
+static void snd_usb_chip_release(struct snd_usb_audio *chip, struct usb_interface *iface,
+ bool free_card)
{
chip->num_interfaces--;
chip->intf[chip->num_interfaces] = NULL;
@@ -781,7 +782,7 @@ static void snd_usb_chip_release(struct snd_usb_audio *chip, struct usb_interfac
if (!chip->num_interfaces) {
usb_chip[chip->index] = NULL;
- if (chip->card)
+ if (free_card && chip->card)
snd_card_free(chip->card);
snd_usb_chip_free(chip);
}
@@ -1018,10 +1019,18 @@ static int snd_usb_probe_unlocked(struct usb_interface *iface, const struct usb_
goto err;
return 0;
err:
- snd_usb_chip_release(chip, iface);
+ snd_usb_chip_release(chip, iface, false);
return ret;
}
+int snd_usb_probe(struct usb_interface *iface, const struct usb_device_id *usb_id,
+ struct usb_driver *driver)
+{
+ guard(mutex)(®ister_mutex);
+ return snd_usb_probe_unlocked(iface, usb_id, driver);
+}
+EXPORT_SYMBOL_GPL(snd_usb_probe);
+
int snd_usb_bind_card(struct snd_usb_audio *chip, struct snd_card *card,
struct usb_driver *driver)
{
@@ -1140,15 +1149,59 @@ static int usb_audio_probe(struct usb_interface *iface, const struct usb_device_
err_no_register:
atomic_dec(&chip->active);
- snd_usb_chip_release(chip, iface);
+ snd_usb_chip_release(chip, iface, true);
return ret;
}
+/**
+ * snd_usb_release_resources - Release resources occupied by USB AC device
+ *
+ * @chip: Driver context for the USB AC device
+ *
+ * Note: PCMs and endpoints created when scanning USB interface descriptors
+ * are not freed here. That information is static and can be used to re-create
+ * PCMs without the need to scan the interfaces again.
+ *
+ * Compared to usb_disconnect(), this does free midi2.0 resources.
+ */
+void snd_usb_release_resources(struct snd_usb_audio *chip)
+{
+ struct usb_mixer_interface *mixer;
+ struct list_head *midi_entry;
+ struct snd_usb_endpoint *ep;
+ struct snd_usb_stream *as;
+
+ /* 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(midi_entry, &chip->midi_list)
+ snd_usbmidi_disconnect(midi_entry);
+
+ snd_usb_midi_v2_disconnect_all(chip);
+ snd_usb_midi_v2_free_all(chip);
+ /*
+ * snd_media_device_delete() accesses mixer_list, do it
+ * before releasing mixers.
+ */
+ snd_media_device_delete(chip);
+
+ /* release mixer resources */
+ list_for_each_entry(mixer, &chip->mixer_list, list)
+ snd_usb_mixer_disconnect(mixer);
+}
+EXPORT_SYMBOL_GPL(snd_usb_release_resources);
+
/*
* 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)
+static void usb_disconnect(struct usb_interface *intf, bool free_card)
{
struct snd_usb_audio *chip = usb_get_intfdata(intf);
struct snd_card *card;
@@ -1196,10 +1249,21 @@ static void usb_audio_disconnect(struct usb_interface *intf)
if (chip->quirk_flags & QUIRK_FLAG_DISABLE_AUTOSUSPEND)
usb_enable_autosuspend(interface_to_usbdev(intf));
- snd_usb_chip_release(chip, intf);
+ snd_usb_chip_release(chip, intf, free_card);
mutex_unlock(®ister_mutex);
}
+static void usb_audio_disconnect(struct usb_interface *iface)
+{
+ usb_disconnect(iface, true);
+}
+
+void snd_usb_disconnect(struct usb_interface *iface)
+{
+ usb_disconnect(iface, false);
+}
+EXPORT_SYMBOL_GPL(snd_usb_disconnect);
+
/* lock the shutdown (disconnect) task and autoresume */
int snd_usb_lock_shutdown(struct snd_usb_audio *chip)
{
@@ -1264,7 +1328,7 @@ void snd_usb_autosuspend(struct snd_usb_audio *chip)
usb_autopm_put_interface(chip->intf[i]);
}
-static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message)
+int snd_usb_suspend(struct usb_interface *intf, pm_message_t message)
{
struct snd_usb_audio *chip = usb_get_intfdata(intf);
struct snd_usb_stream *as;
@@ -1294,8 +1358,9 @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message)
return 0;
}
+EXPORT_SYMBOL_GPL(snd_usb_suspend);
-static int usb_audio_resume(struct usb_interface *intf)
+int snd_usb_resume(struct usb_interface *intf)
{
struct snd_usb_audio *chip = usb_get_intfdata(intf);
struct snd_usb_stream *as;
@@ -1343,6 +1408,7 @@ static int usb_audio_resume(struct usb_interface *intf)
atomic_dec(&chip->active); /* allow autopm after this point */
return err;
}
+EXPORT_SYMBOL_GPL(snd_usb_resume);
static const struct usb_device_id usb_audio_ids [] = {
#include "quirks-table.h"
@@ -1361,9 +1427,9 @@ 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_resume,
+ .suspend = snd_usb_suspend,
+ .resume = snd_usb_resume,
+ .reset_resume = snd_usb_resume,
.id_table = usb_audio_ids,
.supports_autosuspend = 1,
};
--
2.25.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 12/15] ALSA: usb: Export card-naming procedure
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (10 preceding siblings ...)
2025-04-09 11:07 ` [RFC 11/15] ALSA: usb: Export usb_interface driver operations Cezary Rojewski
@ 2025-04-09 11:07 ` Cezary Rojewski
2025-04-09 11:07 ` [RFC 13/15] ALSA: usb: Add getters to obtain endpoint information Cezary Rojewski
` (5 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
USB sound card features customized naming procedure. For ALSA and ASoC
drivers to share UseCaseManagement (UCM) configuration files, it is best
if the naming is aligned between the two.
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
include/sound/usb.h | 1 +
sound/usb/card.c | 78 ++++++++++++++++++++++-----------------------
2 files changed, 39 insertions(+), 40 deletions(-)
diff --git a/include/sound/usb.h b/include/sound/usb.h
index fbcfa2b985f7..b20badfda6a6 100644
--- a/include/sound/usb.h
+++ b/include/sound/usb.h
@@ -91,6 +91,7 @@ struct snd_usb_audio {
};
void snd_usb_release_resources(struct snd_usb_audio *chip);
+void snd_usb_make_card_names(struct snd_usb_audio *chip, char *shortname, char *longname);
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);
diff --git a/sound/usb/card.c b/sound/usb/card.c
index a4bd8ba6a26c..20a77e63a7eb 100644
--- a/sound/usb/card.c
+++ b/sound/usb/card.c
@@ -525,103 +525,102 @@ lookup_device_name(u32 id)
return NULL;
}
-static void usb_audio_make_shortname(struct usb_device *dev,
- struct snd_usb_audio *chip,
- const struct snd_usb_audio_quirk *quirk)
+static void snd_usb_make_card_shortname(struct snd_usb_audio *chip, char *shortname)
{
- struct snd_card *card = chip->card;
const struct usb_audio_device_name *preset;
+ struct usb_device *udev = chip->dev;
const char *s = NULL;
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;
+ else if (chip->quirk && chip->quirk->product_name)
+ s = chip->quirk->product_name;
if (s && *s) {
- strscpy(card->shortname, s, sizeof(card->shortname));
+ strscpy(shortname, s, sizeof_field(struct snd_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) {
+ if (!udev->descriptor.iProduct ||
+ usb_string(udev, udev->descriptor.iProduct,
+ shortname, sizeof_field(struct snd_card, shortname)) <= 0) {
/* no name available from anywhere, so use ID */
- sprintf(card->shortname, "USB Device %#04x:%#04x",
+ sprintf(shortname, "USB Device %#04x:%#04x",
USB_ID_VENDOR(chip->usb_id),
USB_ID_PRODUCT(chip->usb_id));
}
- strim(card->shortname);
+ strim(shortname);
}
-static void usb_audio_make_longname(struct usb_device *dev,
- struct snd_usb_audio *chip,
- const struct snd_usb_audio_quirk *quirk)
+void snd_usb_make_card_names(struct snd_usb_audio *chip, char *shortname, char *longname)
{
- struct snd_card *card = chip->card;
const struct usb_audio_device_name *preset;
+ struct usb_device *udev = chip->dev;
const char *s = NULL;
int len;
+ snd_usb_make_card_shortname(chip, shortname);
+
preset = lookup_device_name(chip->usb_id);
/* shortcut - if any pre-defined string is given, use it */
if (preset && preset->profile_name)
s = preset->profile_name;
if (s && *s) {
- strscpy(card->longname, s, sizeof(card->longname));
+ strscpy(longname, s, sizeof_field(struct snd_card, longname));
return;
}
if (preset && preset->vendor_name)
s = preset->vendor_name;
- else if (quirk && quirk->vendor_name)
- s = quirk->vendor_name;
- *card->longname = 0;
+ else if (chip->quirk && chip->quirk->vendor_name)
+ s = chip->quirk->vendor_name;
+ *longname = 0;
if (s && *s) {
- strscpy(card->longname, s, sizeof(card->longname));
+ strscpy(longname, s, sizeof_field(struct snd_card, longname));
} else {
/* retrieve the vendor and device strings as longname */
- if (dev->descriptor.iManufacturer)
- usb_string(dev, dev->descriptor.iManufacturer,
- card->longname, sizeof(card->longname));
+ if (udev->descriptor.iManufacturer)
+ usb_string(udev, udev->descriptor.iManufacturer,
+ longname, sizeof_field(struct snd_card, longname));
/* we don't really care if there isn't any vendor string */
}
- if (*card->longname) {
- strim(card->longname);
- if (*card->longname)
- strlcat(card->longname, " ", sizeof(card->longname));
+ if (*longname) {
+ strim(longname);
+ if (*longname)
+ strlcat(longname, " ", sizeof_field(struct snd_card, longname));
}
- strlcat(card->longname, card->shortname, sizeof(card->longname));
+ strlcat(longname, shortname, sizeof_field(struct snd_card, longname));
- len = strlcat(card->longname, " at ", sizeof(card->longname));
+ len = strlcat(longname, " at ", sizeof_field(struct snd_card, longname));
- if (len < sizeof(card->longname))
- usb_make_path(dev, card->longname + len, sizeof(card->longname) - len);
+ if (len < sizeof_field(struct snd_card, longname))
+ usb_make_path(udev, longname + len, sizeof_field(struct snd_card, longname) - len);
- switch (snd_usb_get_speed(dev)) {
+ switch (snd_usb_get_speed(udev)) {
case USB_SPEED_LOW:
- strlcat(card->longname, ", low speed", sizeof(card->longname));
+ strlcat(longname, ", low speed", sizeof_field(struct snd_card, longname));
break;
case USB_SPEED_FULL:
- strlcat(card->longname, ", full speed", sizeof(card->longname));
+ strlcat(longname, ", full speed", sizeof_field(struct snd_card, longname));
break;
case USB_SPEED_HIGH:
- strlcat(card->longname, ", high speed", sizeof(card->longname));
+ strlcat(longname, ", high speed", sizeof_field(struct snd_card, longname));
break;
case USB_SPEED_SUPER:
- strlcat(card->longname, ", super speed", sizeof(card->longname));
+ strlcat(longname, ", super speed", sizeof_field(struct snd_card, longname));
break;
case USB_SPEED_SUPER_PLUS:
- strlcat(card->longname, ", super speed plus", sizeof(card->longname));
+ strlcat(longname, ", super speed plus", sizeof_field(struct snd_card, longname));
break;
default:
break;
}
}
+EXPORT_SYMBOL_GPL(snd_usb_make_card_names);
/* look for a matching quirk alias id */
static bool get_alias_id(struct usb_device *dev, unsigned int *id)
@@ -1058,8 +1057,7 @@ static int snd_usb_create_card(struct snd_usb_audio *chip, struct usb_interface
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);
+ snd_usb_make_card_names(chip, card->shortname, card->longname);
sprintf(component, "USB%04x:%04x", usb_device_vid(udev), usb_device_pid(udev));
snd_component_add(card, component);
snd_usb_audio_create_proc(chip);
--
2.25.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 13/15] ALSA: usb: Add getters to obtain endpoint information
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (11 preceding siblings ...)
2025-04-09 11:07 ` [RFC 12/15] ALSA: usb: Export card-naming procedure Cezary Rojewski
@ 2025-04-09 11:07 ` Cezary Rojewski
2025-04-09 11:07 ` [RFC 14/15] ASoC: codecs: Add USB-Audio driver Cezary Rojewski
` (4 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
Intel's Audio Link Hub (ALH) requires information about the data and
feedback endpoints to manage the offloaded PCM stream properly.
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
include/sound/usb.h | 4 ++++
sound/usb/card.h | 4 ++--
sound/usb/stream.c | 51 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 57 insertions(+), 2 deletions(-)
diff --git a/include/sound/usb.h b/include/sound/usb.h
index b20badfda6a6..8f6e26d4de48 100644
--- a/include/sound/usb.h
+++ b/include/sound/usb.h
@@ -96,6 +96,10 @@ 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);
void snd_usb_pcm_hw_init(struct list_head *stream_entry, int dir, struct snd_pcm_hardware *hw);
+void snd_usb_pcm_get_epaddr(struct list_head *stream_entry, int dir, u8 *data_epaddr,
+ u8 *fb_epaddr);
+void snd_usb_pcm_get_epinfo(struct list_head *stream_entry, int dir, u16 *data_maxp, u16 *fb_maxp,
+ u8 *fb_syncinterval);
/* USB interface operations, see struct usb_driver. */
int snd_usb_probe(struct usb_interface *iface, const struct usb_device_id *usb_id,
diff --git a/sound/usb/card.h b/sound/usb/card.h
index 6ec95b2edf86..00c6e9046296 100644
--- a/sound/usb/card.h
+++ b/sound/usb/card.h
@@ -21,10 +21,10 @@ struct audioformat {
unsigned char ep_idx; /* endpoint array index */
unsigned char altset_idx; /* array index of alternate setting */
unsigned char attributes; /* corresponding attributes of cs endpoint */
- unsigned char endpoint; /* endpoint */
+ unsigned char endpoint; /* bEndpointAddress of data endpoint */
unsigned char ep_attr; /* endpoint attributes */
bool implicit_fb; /* implicit feedback endpoint */
- unsigned char sync_ep; /* sync endpoint number */
+ unsigned char sync_ep; /* bEndpointAddress of feedback endpoint */
unsigned char sync_iface; /* sync EP interface */
unsigned char sync_altsetting; /* sync EP alternate setting */
unsigned char sync_ep_idx; /* sync EP array index */
diff --git a/sound/usb/stream.c b/sound/usb/stream.c
index 601dc9ed020a..8f8a049f5b43 100644
--- a/sound/usb/stream.c
+++ b/sound/usb/stream.c
@@ -623,6 +623,57 @@ static int snd_usb_add_audio_stream_v3(struct snd_usb_audio *chip,
return snd_usb_parse_pcm(chip, stream, fp, pd);
}
+/**
+ * snd_usb_pcm_get_epaddr - Obtain addresses of the stream endpoints.
+ *
+ * @stream_entry: The USB stream
+ * @dir: Substream's direction
+ * @data_epaddr: returned bEndpointAddress of the data endpoint
+ * @fb_epaddr: returned bEndpointAddress of the feedback endpoint
+ */
+void snd_usb_pcm_get_epaddr(struct list_head *stream_entry, int dir, u8 *data_epaddr,
+ u8 *fb_epaddr)
+{
+ struct snd_usb_substream *subs;
+ struct snd_usb_stream *as;
+ struct audioformat *fp;
+
+ as = list_entry(stream_entry, struct snd_usb_stream, list);
+ subs = &as->substream[dir];
+ fp = list_first_entry(&subs->fmt_list, struct audioformat, list);
+
+ *data_epaddr = fp->endpoint;
+ *fb_epaddr = fp->sync_ep;
+}
+EXPORT_SYMBOL_GPL(snd_usb_pcm_get_epaddr);
+
+/**
+ * snd_usb_pcm_get_epinfo - Obtain runtime information of the stream endpoints.
+ *
+ * @stream_entry: The USB stream
+ * @dir: Substream's direction
+ * @data_maxp: returned wMaxPacketSize of the data endpoint
+ * @fb_maxp: returned wMaxPacketSize of the feedback endpoint
+ * @fb_syncinterval: returned synchronization interval of the feedback endpoint
+ */
+void snd_usb_pcm_get_epinfo(struct list_head *stream_entry, int dir, u16 *data_maxp, u16 *fb_maxp,
+ u8 *fb_syncinterval)
+{
+ struct snd_usb_substream *subs;
+ struct snd_usb_stream *as;
+
+ as = list_entry(stream_entry, struct snd_usb_stream, list);
+ subs = &as->substream[dir];
+
+ if (subs->data_endpoint)
+ *data_maxp = subs->data_endpoint->maxpacksize;
+ if (subs->sync_endpoint) {
+ *fb_maxp = subs->sync_endpoint->maxpacksize;
+ *fb_syncinterval = subs->sync_endpoint->syncinterval;
+ }
+}
+EXPORT_SYMBOL_GPL(snd_usb_pcm_get_epinfo);
+
static int parse_uac_endpoint_attributes(struct snd_usb_audio *chip,
struct usb_host_interface *alts,
int protocol, int iface_no)
--
2.25.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 14/15] ASoC: codecs: Add USB-Audio driver
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (12 preceding siblings ...)
2025-04-09 11:07 ` [RFC 13/15] ALSA: usb: Add getters to obtain endpoint information Cezary Rojewski
@ 2025-04-09 11:07 ` Cezary Rojewski
2025-04-09 11:07 ` [RFC 15/15] ASoC: Intel: avs: Add USB machine board Cezary Rojewski
` (3 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
Sound card on the ASoC side is typically a marriage of two major
components: codec and platform. Platform usually covers DSP subject so
the USB Audio-Class (AC) driver fits the codec role better.
The driver takes heavily from the 'classic' driver defined in
sound/usb/card.c. The difference worth noting is lack of tolerance for
quirks - given the limited number of Audio Link Hub (ALH) streams, only
candidates (USB devices) we can be sure of have green light for
offloading.
There are two important responsibilities of the driver on top of
PCM/component operations:
- register platform device for machine-board driver to hook onto
- allocation and free of Audio Sideband resources and storing up-to-date
information about USB endpoints engaged in the streaming.
The platform component can leverage the latter to configure their
hardware accordingly to what is present on xHCI side.
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
include/sound/usb_offload.h | 46 ++++
sound/soc/codecs/Kconfig | 6 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/usb.c | 441 ++++++++++++++++++++++++++++++++++++
4 files changed, 495 insertions(+)
create mode 100644 include/sound/usb_offload.h
create mode 100644 sound/soc/codecs/usb.c
diff --git a/include/sound/usb_offload.h b/include/sound/usb_offload.h
new file mode 100644
index 000000000000..029a8f22d32f
--- /dev/null
+++ b/include/sound/usb_offload.h
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * USB Audio Offload(ed) stream information
+ *
+ * Copyright(c) 2025 Intel Corporation
+ */
+#ifndef __SOUND_USB_OFFLOAD_H
+#define __SOUND_USB_OFFLOAD_H
+
+#include <uapi/linux/usb/ch9.h> /* enum usb_device_speed */
+
+/**
+ * struct uao_stream_info - USB Audio Offload stream information
+ *
+ * @bus_devid: Device ID of the owning xHCI controller, see pci_dev_id().
+ * @slot_id: slot_id of the USB device.
+ * @tt: true if behind USB2.0 Transaction Translator (TT)
+ * @speed: USB_SPEED_XXX of the USB device.
+ *
+ * @data_epaddr: bEndpointAddress of the data endpoint.
+ * @data_audsb_res: Audio Sideband resource assigned by xHCI.
+ * @data_maxp: wMaxPacketSize of the data endpoint.
+ * @fb_epaddr: bEndpointAddress of the feedback endpoint.
+ * @fb_audsb_res: Audio Sideband resource assigned by xHCI.
+ * @fb_maxp: wMaxPacketSize of the feedback endpoint.
+ * @fb_interval: Synchornization interval of the feedback endpoint.
+ *
+ * If the owning xHCI controller is not a PCI device, @bus_devid is 0.
+ * If no feedback endpoint exists for a given stream, all fb_xxx are 0.
+ */
+struct uao_stream_info {
+ u16 bus_devid;
+ u8 slot_id;
+ u8 tt;
+ enum usb_device_speed speed;
+
+ u8 data_epaddr;
+ u8 data_audsb_res;
+ u16 data_maxp;
+ u8 fb_epaddr;
+ u8 fb_audsb_res;
+ u16 fb_maxp;
+ u8 fb_syncinterval;
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index f8a21c4b74c2..eda9268cf0b4 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -2177,6 +2177,12 @@ config SND_SOC_UDA1380
tristate
depends on I2C
+config SND_SOC_USB_CODEC
+ tristate "USB \"codec\" representative in ASoC"
+ depends on SND_USB_AUDIO
+ help
+ TODO
+
config SND_SOC_WCD_CLASSH
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 578d6aedc69a..cc1206057aae 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -331,6 +331,7 @@ snd-soc-twl6040-y := twl6040.o
snd-soc-uda1334-y := uda1334.o
snd-soc-uda1342-y := uda1342.o
snd-soc-uda1380-y := uda1380.o
+snd-soc-usb-codec-y := usb.o
snd-soc-wcd-classh-y := wcd-clsh-v2.o
snd-soc-wcd-mbhc-y := wcd-mbhc-v2.o
snd-soc-wcd9335-y := wcd9335.o
@@ -746,6 +747,7 @@ obj-$(CONFIG_SND_SOC_TWL6040) += snd-soc-twl6040.o
obj-$(CONFIG_SND_SOC_UDA1334) += snd-soc-uda1334.o
obj-$(CONFIG_SND_SOC_UDA1342) += snd-soc-uda1342.o
obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o
+obj-$(CONFIG_SND_SOC_USB_CODEC) += snd-soc-usb-codec.o
obj-$(CONFIG_SND_SOC_WCD_CLASSH) += snd-soc-wcd-classh.o
obj-$(CONFIG_SND_SOC_WCD_MBHC) += snd-soc-wcd-mbhc.o
obj-$(CONFIG_SND_SOC_WCD9335) += snd-soc-wcd9335.o
diff --git a/sound/soc/codecs/usb.c b/sound/soc/codecs/usb.c
new file mode 100644
index 000000000000..1013147d66b4
--- /dev/null
+++ b/sound/soc/codecs/usb.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright(c) 2025 Intel Corporation
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/list.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/pci.h>
+#include <linux/pm.h> /* pm_message_t */
+#include <linux/usb.h>
+
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-component.h>
+#include <sound/soc-dai.h>
+#include <sound/soc-dapm.h>
+#include <sound/usb.h>
+#include <sound/usb_offload.h>
+#include <uapi/linux/usb/audio.h>
+
+#define UAO_COMPONENT_NAME "usb-codec"
+
+static struct usb_driver uaol_driver;
+
+struct uao_dai_data {
+ struct uao_stream_info uao;
+ struct list_head *stream_entry; /* see struct snd_usb_stream. */
+};
+
+static int uaol_dai_pcm_new(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai)
+{
+ struct uao_dai_data *data = snd_soc_dai_dma_data_get(dai, 0);
+
+ /* Complete a USB stream initialization by binding it with a PCM. */
+ return snd_usb_bind_pcm(data->stream_entry, rtd->pcm);
+}
+
+static int uaol_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ struct snd_usb_audio *chip = dev_get_drvdata(dai->component->dev);
+ struct usb_device *udev = chip->dev;
+ struct uao_dai_data *data;
+ u8 data_res, fb_res;
+ int ret;
+
+ fb_res = 0;
+ data = snd_soc_dai_get_dma_data(dai, substream);
+
+ ret = usb_alloc_audsb_resource(udev, data->uao.data_epaddr, &data_res);
+ if (ret)
+ return ret;
+ if (data->uao.fb_epaddr) {
+ ret = usb_alloc_audsb_resource(udev, data->uao.fb_epaddr, &fb_res);
+ if (ret)
+ goto err_alloc_fb;
+ }
+
+ /* sound/usb/pcm.c operations expect valid USB stream in ->private_data. */
+ substream->runtime->private_data = data->stream_entry;
+
+ ret = snd_usb_pcm_open(substream);
+ if (ret)
+ goto err_pcm_open;
+
+ data->uao.data_audsb_res = data_res;
+ data->uao.fb_audsb_res = fb_res;
+ return 0;
+
+err_pcm_open:
+ substream->runtime->private_data = NULL;
+ if (data->uao.fb_epaddr)
+ usb_free_audsb_resource(udev, data->uao.fb_epaddr);
+err_alloc_fb:
+ usb_free_audsb_resource(udev, data->uao.data_epaddr);
+ return ret;
+}
+
+static void uaol_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ struct snd_usb_audio *chip = dev_get_drvdata(dai->component->dev);
+ struct usb_device *udev = chip->dev;
+ struct uao_dai_data *data;
+
+ data = snd_soc_dai_get_dma_data(dai, substream);
+ data->uao.data_audsb_res = 0;
+ data->uao.fb_audsb_res = 0;
+
+ snd_usb_pcm_close(substream);
+ substream->runtime->private_data = NULL;
+
+ if (data->uao.fb_epaddr)
+ usb_free_audsb_resource(udev, data->uao.fb_epaddr);
+ usb_free_audsb_resource(udev, data->uao.data_epaddr);
+}
+
+static int uaol_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params, struct snd_soc_dai *dai)
+{
+ struct uao_dai_data *data = snd_soc_dai_get_dma_data(dai, substream);
+ int ret;
+
+ ret = snd_usb_pcm_hw_params(substream, hw_params);
+ if (ret)
+ return ret;
+
+ /* With USB endpoints configured, assign the dynamic information. */
+ snd_usb_pcm_get_epinfo(data->stream_entry, substream->stream, &data->uao.data_maxp,
+ &data->uao.fb_maxp, &data->uao.fb_syncinterval);
+ return 0;
+}
+
+static int uaol_dai_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ struct uao_dai_data *data;
+
+ data = snd_soc_dai_get_dma_data(dai, substream);
+ data->uao.data_maxp = 0;
+ data->uao.fb_maxp = 0;
+ data->uao.fb_syncinterval = 0;
+
+ return snd_usb_pcm_hw_free(substream);
+}
+
+static int uaol_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+ return snd_usb_pcm_prepare(substream);
+}
+
+static const struct snd_soc_dai_ops uaol_dai_ops = {
+ .pcm_new = uaol_dai_pcm_new,
+ .startup = uaol_dai_startup,
+ .shutdown = uaol_dai_shutdown,
+ .hw_params = uaol_dai_hw_params,
+ .hw_free = uaol_dai_hw_free,
+ .prepare = uaol_dai_prepare,
+};
+
+static const struct snd_soc_dapm_route dapm_routes[] = {
+ { "AIF1TX", NULL, "Codec Input Pin1" },
+ { "Codec Output Pin1", NULL, "AIF1RX" },
+};
+
+static const struct snd_soc_dapm_widget dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("AIF1RX", "Analog Codec Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("AIF1TX", "Analog Codec Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_INPUT("Codec Input Pin1"),
+ SND_SOC_DAPM_OUTPUT("Codec Output Pin1"),
+};
+
+static int uaol_init_dai_driver(struct snd_usb_audio *chip, struct snd_soc_dai_driver *driver,
+ struct list_head *stream_entry, int idx)
+{
+ struct device *dev = &chip->dev->dev;
+ struct snd_soc_pcm_stream *stream;
+ struct snd_pcm_hardware hw;
+ int dir;
+
+ driver->id = idx;
+ driver->ops = &uaol_dai_ops;
+ driver->name = devm_kasprintf(dev, GFP_KERNEL, "usb-codec-dai%d", idx);
+ if (!driver->name)
+ return -ENOMEM;
+
+ for_each_pcm_streams(dir) {
+ if (dir)
+ stream = &driver->capture;
+ else
+ stream = &driver->playback;
+
+ /* If the direction is not supported, skip it. */
+ memset(&hw, 0, sizeof(hw));
+ snd_usb_pcm_hw_init(stream_entry, dir, &hw);
+ if (!hw.formats)
+ continue;
+
+ stream->stream_name = devm_kasprintf(dev, GFP_KERNEL, "UAO Audio #%i %s",
+ idx, snd_pcm_direction_name(dir));
+ if (!stream->stream_name)
+ return -ENOMEM;
+ stream->rates = hw.rates;
+ stream->rate_min = hw.rate_min;
+ stream->rate_max = hw.rate_max;
+ stream->channels_min = hw.channels_min;
+ stream->channels_max = hw.channels_max;
+ stream->formats = hw.formats;
+ }
+
+ if (!driver->playback.formats && !driver->capture.formats)
+ return -EINVAL;
+ return 0;
+}
+
+static int uaol_init_dai_dma_data(struct snd_usb_audio *chip, struct snd_soc_dai *dai,
+ struct list_head *stream_entry, u16 bus_devid)
+{
+ struct usb_device *udev = chip->dev;
+ struct uao_dai_data *data;
+ int dir;
+
+ for_each_pcm_streams(dir) {
+ if (!snd_soc_dai_get_pcm_stream(dai, dir)->formats)
+ continue;
+
+ data = devm_kzalloc(&udev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ /* The dynamic information is assigned in hw_params(). */
+ data->stream_entry = stream_entry;
+ data->uao.bus_devid = bus_devid;
+ data->uao.slot_id = udev->slot_id;
+ if (udev->tt)
+ data->uao.tt = true;
+ data->uao.speed = udev->speed;
+ snd_usb_pcm_get_epaddr(stream_entry, dir, &data->uao.data_epaddr,
+ &data->uao.fb_epaddr);
+
+ snd_soc_dai_dma_data_set(dai, dir, data);
+ }
+
+ return 0;
+}
+
+static void uaol_unregister_dais(struct snd_soc_component *component)
+{
+ struct snd_soc_dai *dai, *save;
+
+ for_each_component_dais_safe(component, dai, save)
+ if (strstr(dai->driver->name, UAO_COMPONENT_NAME))
+ snd_soc_unregister_dai(dai);
+}
+
+static int uaol_register_dais(struct snd_soc_component *component)
+{
+ struct snd_usb_audio *chip = dev_get_drvdata(component->dev);
+ struct usb_device *udev = chip->dev;
+ struct snd_soc_dai_driver *drivers;
+ struct list_head *stream_entry;
+ struct snd_soc_dai *dai;
+ u16 bus_devid = 0;
+ int ret, i = 0;
+
+ if (list_empty(&chip->pcm_list))
+ return -EINVAL;
+
+ drivers = devm_kcalloc(&udev->dev, chip->pcm_devs, sizeof(*drivers), GFP_KERNEL);
+ if (!drivers)
+ return -ENOMEM;
+
+ if (dev_is_pci(udev->bus->controller)) {
+ struct pci_dev *pci = to_pci_dev(udev->bus->controller);
+
+ bus_devid = pci_dev_id(pci);
+ }
+
+ list_for_each(stream_entry, &chip->pcm_list) {
+ ret = uaol_init_dai_driver(chip, &drivers[i], stream_entry, i);
+ if (ret)
+ goto err;
+
+ dai = snd_soc_register_dai(component, &drivers[i], false);
+ if (!dai) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = uaol_init_dai_dma_data(chip, dai, stream_entry, bus_devid);
+ if (ret)
+ goto err;
+ i++;
+ }
+
+ return 0;
+err:
+ uaol_unregister_dais(component);
+ return ret;
+}
+
+static int uaol_component_probe(struct snd_soc_component *component)
+{
+ struct snd_usb_audio *chip = dev_get_drvdata(component->dev);
+
+ return snd_usb_bind_card(chip, component->card->snd_card, &uaol_driver);
+}
+
+static void uaol_component_remove(struct snd_soc_component *component)
+{
+ struct snd_usb_audio *chip = dev_get_drvdata(component->dev);
+
+ /* Prevent autosuspend of the chip. */
+ atomic_add(chip->num_interfaces, &chip->active);
+
+ /*
+ * Unregister DAIs that were created manually in uaol_register_dais()
+ * and release all (audio) resources attached tt the chip. The parsed
+ * information about PCMs (chip->pcm_list) remains intact so the card
+ * can be re-bound without the need to re-parse USB interface again.
+ *
+ * Nothing for proc as ASoC did snd_card_disconnect() already, it is gone.
+ */
+ snd_usb_release_resources(chip);
+ chip->card = NULL;
+}
+
+static const struct snd_soc_component_driver uaol_codec_driver = {
+ .name = "usb-codec-driver",
+ .probe = uaol_component_probe,
+ .remove = uaol_component_remove,
+ .dapm_widgets = dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(dapm_widgets),
+ .dapm_routes = dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(dapm_routes),
+};
+
+static int uaol_register_component(struct snd_usb_audio *chip)
+{
+ struct snd_soc_component *component;
+ struct usb_device *udev = chip->dev;
+ int ret;
+
+ component = devm_kzalloc(&udev->dev, sizeof(*component), GFP_KERNEL);
+ if (!component)
+ return -ENOMEM;
+
+ component->name = UAO_COMPONENT_NAME;
+
+ ret = snd_soc_component_initialize(component, &uaol_codec_driver, &udev->dev);
+ if (ret)
+ return ret;
+
+ ret = uaol_register_dais(component);
+ if (ret)
+ return ret;
+
+ return snd_soc_add_component(component, NULL, 0);
+}
+
+static void uaol_unregister_board(void *pdev)
+{
+ platform_device_unregister(pdev);
+}
+
+static int uaol_register_chip(struct snd_usb_audio *chip)
+{
+ struct snd_soc_acpi_mach mach = {{0}};
+ struct device *dev = &chip->dev->dev;
+ struct platform_device *pdev;
+ int ret;
+
+ mach.tplg_filename = "uao-tplg.bin";
+
+ ret = uaol_register_component(chip);
+ if (ret)
+ return ret;
+
+ pdev = platform_device_register_data(dev, "uao_board", PLATFORM_DEVID_AUTO,
+ &mach, sizeof(mach));
+ if (IS_ERR(pdev))
+ return PTR_ERR(pdev);
+
+ ret = devm_add_action(dev, uaol_unregister_board, pdev);
+ if (ret) {
+ platform_device_unregister(pdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int uaol_probe(struct usb_interface *iface, const struct usb_device_id *usb_id)
+{
+ struct snd_usb_audio *chip;
+ struct usb_device *udev;
+ int ret;
+
+ udev = interface_to_usbdev(iface);
+ if (!udev->audsb_capable)
+ return -ENODEV;
+
+ /* UAO is optional, leave the device to the classic driver if probe fails. */
+ ret = snd_usb_probe(iface, usb_id, &uaol_driver);
+ if (ret) {
+ dev_warn(&udev->dev, "USB Audio Offload driver probe failed: %d\n", ret);
+ udev->audsb_capable = false;
+ return -EPROBE_DEFER;
+ }
+
+ chip = usb_get_intfdata(iface);
+ return uaol_register_chip(chip);
+}
+
+static void uaol_disconnect(struct usb_interface *iface)
+{
+ struct snd_usb_audio *chip = usb_get_intfdata(iface);
+
+ if (chip == USB_AUDIO_IFACE_UNUSED)
+ return;
+
+ snd_soc_unregister_component(&chip->dev->dev);
+ return snd_usb_disconnect(iface);
+}
+
+static int uaol_suspend(struct usb_interface *iface, pm_message_t message)
+{
+ return snd_usb_suspend(iface, message);
+}
+
+static int uaol_resume(struct usb_interface *iface)
+{
+ return snd_usb_resume(iface);
+}
+
+static const struct usb_device_id uaol_device_ids[] = {
+ {
+ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS,
+ .bInterfaceClass = USB_CLASS_AUDIO,
+ .bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, uaol_device_ids);
+
+static struct usb_driver uaol_driver = {
+ .name = "snd-soc-usb-codec",
+ .probe = uaol_probe,
+ .disconnect = uaol_disconnect,
+ .suspend = uaol_suspend,
+ .resume = uaol_resume,
+ .reset_resume = uaol_resume,
+ .id_table = uaol_device_ids,
+ .supports_autosuspend = 1,
+};
+
+module_usb_driver(uaol_driver);
+
+MODULE_DESCRIPTION("USB Audio driver for ASoC");
+MODULE_LICENSE("GPL");
--
2.25.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [RFC 15/15] ASoC: Intel: avs: Add USB machine board
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (13 preceding siblings ...)
2025-04-09 11:07 ` [RFC 14/15] ASoC: codecs: Add USB-Audio driver Cezary Rojewski
@ 2025-04-09 11:07 ` Cezary Rojewski
2025-04-09 12:10 ` [RFC 00/15] ALSA/ASoC: USB Audio Offload Greg KH
` (2 subsequent siblings)
17 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 11:07 UTC (permalink / raw)
To: broonie, tiwai, perex
Cc: amadeuszx.slawinski, linux-sound, gregkh, quic_wcheng,
mathias.nyman, Cezary Rojewski
Provide machine board driver that represents USB Audio sound card. The
card connects ASoC USB codec driver with Intel AudioDSP avs-driver
driver so that user can enjoy streaming over USB device without engaging
their CPU - transfer is offloaded onto DSP instead.
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
---
sound/soc/intel/avs/boards/Kconfig | 8 ++
sound/soc/intel/avs/boards/Makefile | 2 +
sound/soc/intel/avs/boards/usb.c | 115 ++++++++++++++++++++++++++++
3 files changed, 125 insertions(+)
create mode 100644 sound/soc/intel/avs/boards/usb.c
diff --git a/sound/soc/intel/avs/boards/Kconfig b/sound/soc/intel/avs/boards/Kconfig
index 7938e6d6e44d..f036cb96c48f 100644
--- a/sound/soc/intel/avs/boards/Kconfig
+++ b/sound/soc/intel/avs/boards/Kconfig
@@ -244,4 +244,12 @@ config SND_SOC_INTEL_AVS_MACH_TDF8532
Say Y or m if you have such a device. This is a recommended option.
If unsure select "N".
+config SND_SOC_INTEL_AVS_MACH_USB
+ tristate "USB-Audio board"
+ depends on USB_XHCI_PCI || COMPILE_TEST
+ select SND_SOC_USB_CODEC
+ help
+ This allows Intel AVS driver to pair with xHCI Audio Sideband.
+ If unsure select "N".
+
endmenu
diff --git a/sound/soc/intel/avs/boards/Makefile b/sound/soc/intel/avs/boards/Makefile
index 7c718c0ce120..4fdea026a216 100644
--- a/sound/soc/intel/avs/boards/Makefile
+++ b/sound/soc/intel/avs/boards/Makefile
@@ -24,6 +24,7 @@ snd-soc-avs-rt5663-y := rt5663.o
snd-soc-avs-rt5682-y := rt5682.o
snd-soc-avs-ssm4567-y := ssm4567.o
snd-soc-avs-tdf8532-y := tdf8532.o
+snd-soc-avs-usb-y := usb.o
obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DA7219) += snd-soc-avs-da7219.o
obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_DMIC) += snd-soc-avs-dmic.o
@@ -49,3 +50,4 @@ obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT5663) += snd-soc-avs-rt5663.o
obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_RT5682) += snd-soc-avs-rt5682.o
obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_SSM4567) += snd-soc-avs-ssm4567.o
obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_TDF8532) += snd-soc-avs-tdf8532.o
+obj-$(CONFIG_SND_SOC_INTEL_AVS_MACH_USB) += snd-soc-avs-usb.o
diff --git a/sound/soc/intel/avs/boards/usb.c b/sound/soc/intel/avs/boards/usb.c
new file mode 100644
index 000000000000..d103a8a5474b
--- /dev/null
+++ b/sound/soc/intel/avs/boards/usb.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright(c) 2025 Intel Corporation
+//
+// Author: Cezary Rojewski <cezary.rojewski@intel.com>
+//
+
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/usb.h>
+#include <sound/soc.h>
+#include <sound/soc-card.h>
+#include <sound/usb.h>
+
+static int uao_create_dai_links(struct device *dev, struct snd_usb_audio *chip,
+ struct snd_soc_dai_link **links, int *num_links)
+{
+ struct snd_soc_dai_link *dl;
+ int i;
+
+ dl = devm_kcalloc(dev, chip->pcm_devs, sizeof(*dl), GFP_KERNEL);
+ if (!dl)
+ return -ENOMEM;
+
+ for (i = 0; i < chip->pcm_devs; i++) {
+ dl[i].name = devm_kasprintf(dev, GFP_KERNEL, "uao-be-link%d", i);
+ if (!dl[i].name)
+ return -ENOMEM;
+ dl[i].stream_name = dl[i].name;
+
+ dl[i].no_pcm = 1;
+ dl[i].num_cpus = 1;
+ dl[i].num_codecs = 1;
+ dl[i].nonatomic = 1;
+ dl[i].codecs = devm_kzalloc(dev, sizeof(*dl[i].codecs), GFP_KERNEL);
+ dl[i].cpus = devm_kzalloc(dev, sizeof(*dl[i].cpus), GFP_KERNEL);
+ if (!dl[i].codecs || !dl[i].cpus)
+ return -ENOMEM;
+ dl[i].codecs->name = "usb-codec";
+ dl[i].codecs->dai_name = devm_kasprintf(dev, GFP_KERNEL, "usb-codec-dai%d", i);
+ if (!dl[i].codecs->dai_name)
+ return -ENOMEM;
+ dl[i].cpus->dai_name = "uaol-cpu0";
+ }
+
+ *links = dl;
+ *num_links = chip->pcm_devs;
+ return 0;
+}
+
+static int uao_board_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct snd_usb_audio *chip;
+ struct snd_soc_card *card;
+ struct usb_device *udev;
+ char shortname[32];
+ char longname[80];
+ int ret;
+
+ chip = dev_get_drvdata(dev->parent);
+ udev = chip->dev;
+
+ switch (udev->speed) {
+ case USB_SPEED_FULL:
+ case USB_SPEED_HIGH:
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+
+ ret = uao_create_dai_links(dev, chip, &card->dai_link, &card->num_links);
+ if (ret)
+ return ret;
+
+ card->dev = dev;
+ card->owner = THIS_MODULE;
+ card->fully_routed = true;
+ card->driver_name = "USB-Audio";
+
+ snd_usb_make_card_names(chip, shortname, longname);
+ /* card->driver/shortname/longname set by ASoC. */
+ /* card->private_data/free redundant for ASoC-based. */
+ card->name = devm_kstrdup(dev, shortname, GFP_KERNEL);
+ card->long_name = devm_kstrdup(dev, longname, GFP_KERNEL);
+ card->components = devm_kasprintf(dev, GFP_KERNEL, "USB%04x:%04x",
+ usb_device_vid(udev), usb_device_pid(udev));
+ if (!card->name || !card->long_name || !card->components)
+ return -ENOMEM;
+
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static const struct platform_device_id uao_board_driver_ids[] = {
+ { .name = "uao_board", },
+ { },
+};
+
+static struct platform_driver uao_board_driver = {
+ .probe = uao_board_probe,
+ .driver = {
+ .name = "uao_board",
+ .pm = &snd_soc_pm_ops,
+ },
+ .id_table = uao_board_driver_ids,
+};
+
+module_platform_driver(uao_board_driver);
+
+MODULE_DESCRIPTION("USB Audio Offload machine driver");
+MODULE_LICENSE("GPL");
--
2.25.1
^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [RFC 00/15] ALSA/ASoC: USB Audio Offload
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (14 preceding siblings ...)
2025-04-09 11:07 ` [RFC 15/15] ASoC: Intel: avs: Add USB machine board Cezary Rojewski
@ 2025-04-09 12:10 ` Greg KH
2025-04-09 13:06 ` Cezary Rojewski
2025-04-10 10:24 ` Takashi Iwai
2025-04-11 14:04 ` Greg KH
17 siblings, 1 reply; 28+ messages in thread
From: Greg KH @ 2025-04-09 12:10 UTC (permalink / raw)
To: Cezary Rojewski
Cc: broonie, tiwai, perex, amadeuszx.slawinski, linux-sound,
quic_wcheng, mathias.nyman
On Wed, Apr 09, 2025 at 01:07:15PM +0200, Cezary Rojewski wrote:
> Note: this series is based on Mark's broonie/for-next. The xHCI
> dependency is missing so it won't compile. Goal of this RFC is
> discussing the direction of sound/usb changes.
>
> Note #2: Why exclude xHCI? Current form of xhci-sideband.c [1] does not
> fare well with Intel's hardware design for USB Audio Offload feature.
> The production shape for usb/xHCI subjects is being discussed with
> Mathias. Once we're ready, I'll share the rest.
How is that going to work with the patches that add xhci offload that
have already been reviewed on the list?
> Note #3: this series does _NOT_ aim to block QCOM's equivalent series
> [2]. The team does acknowledge that we came the "table" late. At the
> same time, we're prepared to help QCOM switch to the presented sound/usb
> approach if that would benefit the framework and its users as a whole.
> Make it part of this very series if need be.
I don't understand, I'm just about to take the QCOM patches now. They
have been on the list and reviewed for years now, why hasn't this work
happened before now? Please work to make your changes on top of that
patch series.
So what do you expect me to do here?
confused,
greg k-h
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 00/15] ALSA/ASoC: USB Audio Offload
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
0 siblings, 1 reply; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-09 13:06 UTC (permalink / raw)
To: Greg KH
Cc: broonie, tiwai, perex, amadeuszx.slawinski, linux-sound,
quic_wcheng, mathias.nyman
On 2025-04-09 2:10 PM, Greg KH wrote:
> On Wed, Apr 09, 2025 at 01:07:15PM +0200, Cezary Rojewski wrote:
>> Note: this series is based on Mark's broonie/for-next. The xHCI
>> dependency is missing so it won't compile. Goal of this RFC is
>> discussing the direction of sound/usb changes.
>>
>> Note #2: Why exclude xHCI? Current form of xhci-sideband.c [1] does not
>> fare well with Intel's hardware design for USB Audio Offload feature.
>> The production shape for usb/xHCI subjects is being discussed with
>> Mathias. Once we're ready, I'll share the rest.
>
> How is that going to work with the patches that add xhci offload that
> have already been reviewed on the list?
>
>> Note #3: this series does _NOT_ aim to block QCOM's equivalent series
>> [2]. The team does acknowledge that we came the "table" late. At the
>> same time, we're prepared to help QCOM switch to the presented sound/usb
>> approach if that would benefit the framework and its users as a whole.
>> Make it part of this very series if need be.
>
> I don't understand, I'm just about to take the QCOM patches now. They
> have been on the list and reviewed for years now, why hasn't this work
> happened before now? Please work to make your changes on top of that
> patch series.
>
> So what do you expect me to do here?
>
> confused,
Hi Greg,
I wanted the effort to be transparent, that's it. Given the size of the
task, I've decided to get the feedback earlier. I believe asking
sound-maintainers now for their take on sound/usb is better than coming
here much later and shooting everything at once. If the proposed
approach to sound/usb is not welcomed by the sound maintainers,
obviously xhci-sideband additions that will arrive here later will be
impacted two. In regard to the sound changes alone, there is no
intention to blow up the parallel solution.
TLDR:
Work present here will be placed on top of the QCOM patches, no question
about it. xhci-sideband.c will receive a number of changes to cope with
Intel's design but nothing the list haven't seen before.
Kind regards,
Czarek
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 00/15] ALSA/ASoC: USB Audio Offload
2025-04-09 13:06 ` Cezary Rojewski
@ 2025-04-10 10:10 ` Takashi Iwai
0 siblings, 0 replies; 28+ messages in thread
From: Takashi Iwai @ 2025-04-10 10:10 UTC (permalink / raw)
To: Cezary Rojewski
Cc: Greg KH, broonie, tiwai, perex, amadeuszx.slawinski, linux-sound,
quic_wcheng, mathias.nyman
On Wed, 09 Apr 2025 15:06:18 +0200,
Cezary Rojewski wrote:
>
> On 2025-04-09 2:10 PM, Greg KH wrote:
> > On Wed, Apr 09, 2025 at 01:07:15PM +0200, Cezary Rojewski wrote:
> >> Note: this series is based on Mark's broonie/for-next. The xHCI
> >> dependency is missing so it won't compile. Goal of this RFC is
> >> discussing the direction of sound/usb changes.
> >>
> >> Note #2: Why exclude xHCI? Current form of xhci-sideband.c [1] does not
> >> fare well with Intel's hardware design for USB Audio Offload feature.
> >> The production shape for usb/xHCI subjects is being discussed with
> >> Mathias. Once we're ready, I'll share the rest.
> >
> > How is that going to work with the patches that add xhci offload that
> > have already been reviewed on the list?
> >
> >> Note #3: this series does _NOT_ aim to block QCOM's equivalent series
> >> [2]. The team does acknowledge that we came the "table" late. At the
> >> same time, we're prepared to help QCOM switch to the presented sound/usb
> >> approach if that would benefit the framework and its users as a whole.
> >> Make it part of this very series if need be.
> >
> > I don't understand, I'm just about to take the QCOM patches now. They
> > have been on the list and reviewed for years now, why hasn't this work
> > happened before now? Please work to make your changes on top of that
> > patch series.
> >
> > So what do you expect me to do here?
> >
> > confused,
>
> Hi Greg,
>
> I wanted the effort to be transparent, that's it. Given the size of
> the task, I've decided to get the feedback earlier. I believe asking
> sound-maintainers now for their take on sound/usb is better than
> coming here much later and shooting everything at once. If the
> proposed approach to sound/usb is not welcomed by the sound
> maintainers, obviously xhci-sideband additions that will arrive here
> later will be impacted two. In regard to the sound changes alone,
> there is no intention to blow up the parallel solution.
>
> TLDR:
> Work present here will be placed on top of the QCOM patches, no
> question about it. xhci-sideband.c will receive a number of changes
> to cope with Intel's design but nothing the list haven't seen before.
Thanks for the patches, but they appeared obviously too late to the
game. Qcom patches are close to the merge, and I'd love to make
things proceeded finally after many years struggle at first.
The basic question, though again, is how the offloading is managed --
to which card or device is applied. The same question was applied to
qcom implementation and their solution was to manage via the control
API. The rest are rather opaque to users, and it allows us to even
switch the complete implementation later to a different way like what
you suggested, too. But it's only when the user-space will keep
working as is.
Takashi
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 00/15] ALSA/ASoC: USB Audio Offload
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (15 preceding siblings ...)
2025-04-09 12:10 ` [RFC 00/15] ALSA/ASoC: USB Audio Offload Greg KH
@ 2025-04-10 10:24 ` Takashi Iwai
2025-04-11 9:39 ` Cezary Rojewski
2025-04-11 14:04 ` Greg KH
17 siblings, 1 reply; 28+ messages in thread
From: Takashi Iwai @ 2025-04-10 10:24 UTC (permalink / raw)
To: Cezary Rojewski
Cc: broonie, tiwai, perex, amadeuszx.slawinski, linux-sound, gregkh,
quic_wcheng, mathias.nyman
On Wed, 09 Apr 2025 13:07:15 +0200,
Cezary Rojewski wrote:
>
> Note: this series is based on Mark's broonie/for-next. The xHCI
> dependency is missing so it won't compile. Goal of this RFC is
> discussing the direction of sound/usb changes.
>
> Note #2: Why exclude xHCI? Current form of xhci-sideband.c [1] does not
> fare well with Intel's hardware design for USB Audio Offload feature.
> The production shape for usb/xHCI subjects is being discussed with
> Mathias. Once we're ready, I'll share the rest.
>
> Note #3: this series does _NOT_ aim to block QCOM's equivalent series
> [2]. The team does acknowledge that we came the "table" late. At the
> same time, we're prepared to help QCOM switch to the presented sound/usb
> approach if that would benefit the framework and its users as a whole.
> Make it part of this very series if need be.
>
>
>
> Apart from changes to sound/usb, the patchset contains exemplary
> ASoC-based, offload-aware USB driver and a small sound card on the
> avs-driver side to show how card/dai_link initialization looks like.
>
> In short, USB Audio Offload functionality lowers CPU usage when
> streaming PCM over a USB Audio-Class device. It has been first
> introduced in xHCI 1.2 release and is described in section 7.9 [3].
> Once hardware is prepared with hw_params(), all the USB endpoints
> operations e.g.: start, stop, submitting URBs, are performed
> internally, by the hardware and AudioDSP firmware. Software driver
> shall not intervene.
>
> Startup flow from:
>
> usb_pcm_open()
> usb_pcm_hw_params()
> snd_usb_endpoint_open()
> usb_pcm_prepare()
> usb_set_interface()
> snd_usb_endpoint_start()
> usb_pcm_trigger(cmd: START/STOP etc.)
>
> reduced to:
>
> usb_pcm_open()
> usb_pcm_hw_params()
> snd_usb_endpoint_open()
> usb_pcm_prepare()
> usb_set_interface()
Hmm, how can it be? The start of EP at prepare stage is done only
conditionally for non-lowlatency or implicit-feedback mode, for
example.
> Handlers such as ack(), sync_stop(), pointer(), delay() are not used
> here too. The AudioDSP driver will handle pointer(), rest is
> firmware/hardware responsibility.
>
>
> There's a hefty number of limitations, most importantly:
>
> 1) typically 2 USB devices tops, rest go the classic (non-offload) path
> 2) AUDIOSTREAMING interfaces only, MIDI not. While not an interface
> type, Media (sound/usb/media) unsupported either
> 3) simple PCM, UAC_FORMAT_TYPE_I_PCM only
>
> Current patchset shows this in form of 'udev->audsb_capable' field.
> True if xHCI sideband reasource has been assigned to the device. The
> filtering is code is not part of the patchset.
>
> Important to highlight, 1) means both, offload-aware and offload-unaware
> drivers could be utilized simultaneously on the system in runtime.
> Opportunistically few devices would be controlled by the offload-aware
> driver, whereas everything else by the offload-unaware one. This
> differs from existing HDAudio Controller driver situation where either
> classic, snd_hda_intel driver takes complete control -or- the
> offload-aware snd_soc_avs driver.
> In short, once all Audio Sideband resources are depleted, classic
> sound/usb/card.c driver manages whatever comes next:
>
> snd_usb_audio (offload un-aware, sound/usb/card.c)
> snd_soc_usb_codec (offload aware, sound/soc/codecs/usb.c)
>
>
> The design goals:
> - make ASoC first class citizen of sound/usb
> - re-use code found in sound/usb, mimic HDAudio integration in ASoC:
> small sound/soc/codecs/hda.c driver leveraging power of entire
> sound/pci/hda/
> - no shared control over a USB device, either snd_usb_audio or
> its ASoC equivalent takes control of the device
>
> To do that, major tasks are identified:
>
> a) On ASoC side 'struct snd_card' is part of 'struct snd_soc_card' and
> is managed by the framework. Similar situation with 'struct snd_pcm'
> and rtd->pcm. To keep the teardown path sane, drop card->private_free()
> and pcm->private_free() usage.
Well, this is a generic problem of ASoC framework.
I believe this should be better handled in ASoC core side at first.
e.g. the card object could be created at the very first step of the
snd_soc_card creation, too (but without the actual slot assignment or
device creation).
> b) To initialize ASoC components/DAI properly, PCM capabilities should be
> known up-front. To do that, existing USB card probe() has to be split.
> From one-stage to two-stage process:
>
> - look ahead and parse usb_interface descriptors for PCM endpoints
> but do not create any PCMs (sound devices) yet
> - create all PCMs based on obtained ->pcm_list and follow with
> MIDI/mixers/media
>
> Such approach allows to feed DAIs proper data even when a valid
> sound-card pointer is not yet present - the initialization occurs before
> snd_soc_bind_card() is called.
This one is another thing that is needed to adjust for ASoC
framework. But when snd_card object is available, this can be
resolved automatically, too? e.g. snd_pcm object or such can be
created at that point. The actual device registration is done anyway
later via snd_device_register() call.
thanks,
Takashi
> Point a) is scaled for all three "domains" of sound/usb: chip, stream
> and quirks. That's why there total of 6 commits doing that job. First
> implement, then switch. Everything that follows is, in my opinion,
> self-explanatory. No need to repeat commit messages.
>
>
> [1]: https://lore.kernel.org/all/20250319005141.312805-2-quic_wcheng@quicinc.com/
> [2]: https://lore.kernel.org/all/20250319005141.312805-1-quic_wcheng@quicinc.com/
> [3]: https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf
>
>
> Cezary Rojewski (15):
> ALSA: usb: Move media-filters to the media code
> ALSA: usb: Drop private_free() usage for card and pcms
> ALSA: usb: Relocate the usbaudio header file
> ALSA: usb: Implement two-stage quirk applying mechanism
> ALSA: usb: Implement two-stage stream creation mechanism
> ALSA: usb: Implement two-stage chip probing mechanism
> ALSA: usb: Switch to the two-stage chip probing
> ALSA: usb: Switch to the two-stage stream creation
> ALSA: usb: Switch to the two-stage quirk applying
> ALSA: usb: Export PCM operations
> ALSA: usb: Export usb_interface driver operations
> ALSA: usb: Export card-naming procedure
> ALSA: usb: Add getters to obtain endpoint information
> ASoC: codecs: Add USB-Audio driver
> ASoC: Intel: avs: Add USB machine board
>
> include/linux/usb.h | 15 +
> include/linux/usb/ch9.h | 11 +
> sound/usb/usbaudio.h => include/sound/usb.h | 53 +-
> include/sound/usb_offload.h | 46 +
> sound/soc/codecs/Kconfig | 6 +
> sound/soc/codecs/Makefile | 2 +
> sound/soc/codecs/usb.c | 441 +++++++++
> sound/soc/intel/avs/boards/Kconfig | 8 +
> sound/soc/intel/avs/boards/Makefile | 2 +
> sound/soc/intel/avs/boards/usb.c | 115 +++
> sound/usb/caiaq/device.h | 2 +-
> sound/usb/card.c | 946 +++++++++++++-------
> sound/usb/card.h | 4 +-
> sound/usb/clock.c | 2 +-
> sound/usb/endpoint.c | 2 +-
> sound/usb/format.c | 2 +-
> sound/usb/helper.c | 2 +-
> sound/usb/implicit.c | 2 +-
> sound/usb/media.c | 8 +-
> sound/usb/midi.c | 2 +-
> sound/usb/midi.h | 2 +
> sound/usb/midi2.c | 2 +-
> sound/usb/midi2.h | 1 +
> sound/usb/misc/ua101.c | 2 +-
> sound/usb/mixer.c | 2 +-
> sound/usb/mixer_quirks.c | 2 +-
> sound/usb/mixer_s1810c.c | 2 +-
> sound/usb/mixer_scarlett.c | 2 +-
> sound/usb/mixer_scarlett2.c | 2 +-
> sound/usb/mixer_us16x08.c | 2 +-
> sound/usb/pcm.c | 161 +++-
> sound/usb/power.c | 2 +-
> sound/usb/proc.c | 2 +-
> sound/usb/quirks.c | 203 +++--
> sound/usb/quirks.h | 6 +
> sound/usb/stream.c | 217 +++--
> sound/usb/stream.h | 2 +
> sound/usb/usx2y/us122l.c | 2 +-
> sound/usb/usx2y/usX2Yhwdep.c | 1 +
> sound/usb/usx2y/usbusx2y.h | 2 +-
> sound/usb/validate.c | 2 +-
> 41 files changed, 1749 insertions(+), 541 deletions(-)
> rename sound/usb/usbaudio.h => include/sound/usb.h (82%)
> create mode 100644 include/sound/usb_offload.h
> create mode 100644 sound/soc/codecs/usb.c
> create mode 100644 sound/soc/intel/avs/boards/usb.c
>
> --
> 2.25.1
>
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 00/15] ALSA/ASoC: USB Audio Offload
2025-04-10 10:24 ` Takashi Iwai
@ 2025-04-11 9:39 ` Cezary Rojewski
2025-04-15 16:15 ` Takashi Iwai
0 siblings, 1 reply; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-11 9:39 UTC (permalink / raw)
To: Takashi Iwai
Cc: broonie, tiwai, perex, amadeuszx.slawinski, linux-sound, gregkh,
quic_wcheng, mathias.nyman
On 2025-04-10 12:24 PM, Takashi Iwai wrote:
> On Wed, 09 Apr 2025 13:07:15 +0200,
> Cezary Rojewski wrote:
>>
>> Note: this series is based on Mark's broonie/for-next. The xHCI
>> dependency is missing so it won't compile. Goal of this RFC is
>> discussing the direction of sound/usb changes.
>>
>> Note #2: Why exclude xHCI? Current form of xhci-sideband.c [1] does not
>> fare well with Intel's hardware design for USB Audio Offload feature.
>> The production shape for usb/xHCI subjects is being discussed with
>> Mathias. Once we're ready, I'll share the rest.
>>
>> Note #3: this series does _NOT_ aim to block QCOM's equivalent series
>> [2]. The team does acknowledge that we came the "table" late. At the
>> same time, we're prepared to help QCOM switch to the presented sound/usb
>> approach if that would benefit the framework and its users as a whole.
>> Make it part of this very series if need be.
>>
>>
>>
>> Apart from changes to sound/usb, the patchset contains exemplary
>> ASoC-based, offload-aware USB driver and a small sound card on the
>> avs-driver side to show how card/dai_link initialization looks like.
>>
>> In short, USB Audio Offload functionality lowers CPU usage when
>> streaming PCM over a USB Audio-Class device. It has been first
>> introduced in xHCI 1.2 release and is described in section 7.9 [3].
>> Once hardware is prepared with hw_params(), all the USB endpoints
>> operations e.g.: start, stop, submitting URBs, are performed
>> internally, by the hardware and AudioDSP firmware. Software driver
>> shall not intervene.
>>
>> Startup flow from:
>>
>> usb_pcm_open()
>> usb_pcm_hw_params()
>> snd_usb_endpoint_open()
>> usb_pcm_prepare()
>> usb_set_interface()
>> snd_usb_endpoint_start()
>> usb_pcm_trigger(cmd: START/STOP etc.)
>>
>> reduced to:
>>
>> usb_pcm_open()
>> usb_pcm_hw_params()
>> snd_usb_endpoint_open()
>> usb_pcm_prepare()
>> usb_set_interface()
>
> Hmm, how can it be? The start of EP at prepare stage is done only
> conditionally for non-lowlatency or implicit-feedback mode, for
> example.
On Intel architecture the AudioDSP utilizes internal channel to
communicate with xHCI and perform all the transfer operations. In
essence, once SET_INTERFACE TRB is done, the usb-driver is not supposed
to do anything. The avs-driver (sound driver) would be the trigger here
- sends the start/stop/etc. requests to the AudioDSP firmware, DSP does
the rest.
By trigger I mean SET_PIPELINE_STATE IPC and avs_path_xxx() which are
utilized for any transfer-type (sound/soc/intel/avs/pcm.c).
>> Handlers such as ack(), sync_stop(), pointer(), delay() are not used
>> here too. The AudioDSP driver will handle pointer(), rest is
>> firmware/hardware responsibility.
>>
>>
>> There's a hefty number of limitations, most importantly:
>>
>> 1) typically 2 USB devices tops, rest go the classic (non-offload) path
>> 2) AUDIOSTREAMING interfaces only, MIDI not. While not an interface
>> type, Media (sound/usb/media) unsupported either
>> 3) simple PCM, UAC_FORMAT_TYPE_I_PCM only
>>
>> Current patchset shows this in form of 'udev->audsb_capable' field.
>> True if xHCI sideband reasource has been assigned to the device. The
>> filtering is code is not part of the patchset.
>>
>> Important to highlight, 1) means both, offload-aware and offload-unaware
>> drivers could be utilized simultaneously on the system in runtime.
>> Opportunistically few devices would be controlled by the offload-aware
>> driver, whereas everything else by the offload-unaware one. This
>> differs from existing HDAudio Controller driver situation where either
>> classic, snd_hda_intel driver takes complete control -or- the
>> offload-aware snd_soc_avs driver.
>> In short, once all Audio Sideband resources are depleted, classic
>> sound/usb/card.c driver manages whatever comes next:
>>
>> snd_usb_audio (offload un-aware, sound/usb/card.c)
>> snd_soc_usb_codec (offload aware, sound/soc/codecs/usb.c)
>>
>>
>> The design goals:
>> - make ASoC first class citizen of sound/usb
>> - re-use code found in sound/usb, mimic HDAudio integration in ASoC:
>> small sound/soc/codecs/hda.c driver leveraging power of entire
>> sound/pci/hda/
>> - no shared control over a USB device, either snd_usb_audio or
>> its ASoC equivalent takes control of the device
>>
>> To do that, major tasks are identified:
>>
>> a) On ASoC side 'struct snd_card' is part of 'struct snd_soc_card' and
>> is managed by the framework. Similar situation with 'struct snd_pcm'
>> and rtd->pcm. To keep the teardown path sane, drop card->private_free()
>> and pcm->private_free() usage.
>
> Well, this is a generic problem of ASoC framework.
> I believe this should be better handled in ASoC core side at first.
> e.g. the card object could be created at the very first step of the
> snd_soc_card creation, too (but without the actual slot assignment or
> device creation).
Well, in my opinion the card's tailing private context (extra size),
private_free() and all that comes with them in sound/core brings
unnecessary complexity to the ALSA framework. devm_xxx() is enough.
Polluting snd_card and sound/core/init.c with driver-specifics causes
the teardown procedures on the ALSA framework side harder to read/maintain.
>> b) To initialize ASoC components/DAI properly, PCM capabilities should be
>> known up-front. To do that, existing USB card probe() has to be split.
>> From one-stage to two-stage process:
>>
>> - look ahead and parse usb_interface descriptors for PCM endpoints
>> but do not create any PCMs (sound devices) yet
>> - create all PCMs based on obtained ->pcm_list and follow with
>> MIDI/mixers/media
>>
>> Such approach allows to feed DAIs proper data even when a valid
>> sound-card pointer is not yet present - the initialization occurs before
>> snd_soc_bind_card() is called.
>
> This one is another thing that is needed to adjust for ASoC
> framework. But when snd_card object is available, this can be
> resolved automatically, too? e.g. snd_pcm object or such can be
> created at that point. The actual device registration is done anyway
> later via snd_device_register() call.
While I'm up for upgrading either framework in any area necessary to
have proper UAO support in ASoC, not sure whether it's a good idea to
make it a requirement for the feature. This will enlarge the series -
well, guess it will be separate series (dependency) entirely.
>> Point a) is scaled for all three "domains" of sound/usb: chip, stream
>> and quirks. That's why there total of 6 commits doing that job. First
>> implement, then switch. Everything that follows is, in my opinion,
>> self-explanatory. No need to repeat commit messages.
>>
>>
>> [1]: https://lore.kernel.org/all/20250319005141.312805-2-quic_wcheng@quicinc.com/
>> [2]: https://lore.kernel.org/all/20250319005141.312805-1-quic_wcheng@quicinc.com/
>> [3]: https://www.intel.com/content/dam/www/public/us/en/documents/technical-specifications/extensible-host-controler-interface-usb-xhci.pdf
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 00/15] ALSA/ASoC: USB Audio Offload
2025-04-09 11:07 [RFC 00/15] ALSA/ASoC: USB Audio Offload Cezary Rojewski
` (16 preceding siblings ...)
2025-04-10 10:24 ` Takashi Iwai
@ 2025-04-11 14:04 ` Greg KH
2025-04-11 16:51 ` Cezary Rojewski
17 siblings, 1 reply; 28+ messages in thread
From: Greg KH @ 2025-04-11 14:04 UTC (permalink / raw)
To: Cezary Rojewski
Cc: broonie, tiwai, perex, amadeuszx.slawinski, linux-sound,
quic_wcheng, mathias.nyman
On Wed, Apr 09, 2025 at 01:07:15PM +0200, Cezary Rojewski wrote:
> Note: this series is based on Mark's broonie/for-next. The xHCI
> dependency is missing so it won't compile. Goal of this RFC is
> discussing the direction of sound/usb changes.
Any specific reason you aren't cc: linux-usb@vger.kernel.org on USB
stuff like this?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 00/15] ALSA/ASoC: USB Audio Offload
2025-04-11 14:04 ` Greg KH
@ 2025-04-11 16:51 ` Cezary Rojewski
0 siblings, 0 replies; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-11 16:51 UTC (permalink / raw)
To: Greg KH
Cc: broonie, tiwai, perex, amadeuszx.slawinski, linux-sound,
quic_wcheng, mathias.nyman
On 2025-04-11 4:04 PM, Greg KH wrote:
> On Wed, Apr 09, 2025 at 01:07:15PM +0200, Cezary Rojewski wrote:
>> Note: this series is based on Mark's broonie/for-next. The xHCI
>> dependency is missing so it won't compile. Goal of this RFC is
>> discussing the direction of sound/usb changes.
>
> Any specific reason you aren't cc: linux-usb@vger.kernel.org on USB
> stuff like this?
Actually I was expecting the exact opposite feedback had I included
linux-usb, given lack of drivers/usb changes. Lesson learned.
However, given the current feedback, for the next revision I plan to
rebase onto your tree and include the xHCI changes. While I'm familiar
with sound/ practices, I clearly was not with drivers/usb/ ones. The
initial offload-aware vs offload-unaware USB audio-device filtering
stuff was done by xHCI. Thanks to Mathias now I'm aware there is no
place for such specific class code in drivers/usb/host.
I'll try to get the usb changes adjusted as quickly as possible so that
the list can see the entire thing.
Kind regards,
Czarek
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 00/15] ALSA/ASoC: USB Audio Offload
2025-04-11 9:39 ` Cezary Rojewski
@ 2025-04-15 16:15 ` Takashi Iwai
2025-04-17 10:15 ` Cezary Rojewski
0 siblings, 1 reply; 28+ messages in thread
From: Takashi Iwai @ 2025-04-15 16:15 UTC (permalink / raw)
To: Cezary Rojewski
Cc: Takashi Iwai, broonie, tiwai, perex, amadeuszx.slawinski,
linux-sound, gregkh, quic_wcheng, mathias.nyman
On Fri, 11 Apr 2025 11:39:55 +0200,
Cezary Rojewski wrote:
>
> On 2025-04-10 12:24 PM, Takashi Iwai wrote:
> > On Wed, 09 Apr 2025 13:07:15 +0200,
> > Cezary Rojewski wrote:
> >>
> >> Note: this series is based on Mark's broonie/for-next. The xHCI
> >> dependency is missing so it won't compile. Goal of this RFC is
> >> discussing the direction of sound/usb changes.
> >>
> >> Note #2: Why exclude xHCI? Current form of xhci-sideband.c [1] does not
> >> fare well with Intel's hardware design for USB Audio Offload feature.
> >> The production shape for usb/xHCI subjects is being discussed with
> >> Mathias. Once we're ready, I'll share the rest.
> >>
> >> Note #3: this series does _NOT_ aim to block QCOM's equivalent series
> >> [2]. The team does acknowledge that we came the "table" late. At the
> >> same time, we're prepared to help QCOM switch to the presented sound/usb
> >> approach if that would benefit the framework and its users as a whole.
> >> Make it part of this very series if need be.
> >>
> >>
> >>
> >> Apart from changes to sound/usb, the patchset contains exemplary
> >> ASoC-based, offload-aware USB driver and a small sound card on the
> >> avs-driver side to show how card/dai_link initialization looks like.
> >>
> >> In short, USB Audio Offload functionality lowers CPU usage when
> >> streaming PCM over a USB Audio-Class device. It has been first
> >> introduced in xHCI 1.2 release and is described in section 7.9 [3].
> >> Once hardware is prepared with hw_params(), all the USB endpoints
> >> operations e.g.: start, stop, submitting URBs, are performed
> >> internally, by the hardware and AudioDSP firmware. Software driver
> >> shall not intervene.
> >>
> >> Startup flow from:
> >>
> >> usb_pcm_open()
> >> usb_pcm_hw_params()
> >> snd_usb_endpoint_open()
> >> usb_pcm_prepare()
> >> usb_set_interface()
> >> snd_usb_endpoint_start()
> >> usb_pcm_trigger(cmd: START/STOP etc.)
> >>
> >> reduced to:
> >>
> >> usb_pcm_open()
> >> usb_pcm_hw_params()
> >> snd_usb_endpoint_open()
> >> usb_pcm_prepare()
> >> usb_set_interface()
> >
> > Hmm, how can it be? The start of EP at prepare stage is done only
> > conditionally for non-lowlatency or implicit-feedback mode, for
> > example.
>
>
> On Intel architecture the AudioDSP utilizes internal channel to
> communicate with xHCI and perform all the transfer operations. In
> essence, once SET_INTERFACE TRB is done, the usb-driver is not
> supposed to do anything. The avs-driver (sound driver) would be the
> trigger here - sends the start/stop/etc. requests to the AudioDSP
> firmware, DSP does the rest.
>
> By trigger I mean SET_PIPELINE_STATE IPC and avs_path_xxx() which are
> utilized for any transfer-type (sound/soc/intel/avs/pcm.c).
But does the DSP handles the different ways for the implicit feedback
mode, the low-latency playback and else? A specific mode like the
implicit feedback mode is mandatory for many devices. Similarly, the
low-latency mode is essential for some application setups
(e.g. pipewire or JACK prefers), while it's not always applicable
depending on the PCM parameters (such as the free-wheeling mode).
AFAIK, the qcom offloading doesn't support those fully, hence the
configuration must be dynamic for them.
> >> Handlers such as ack(), sync_stop(), pointer(), delay() are not used
> >> here too. The AudioDSP driver will handle pointer(), rest is
> >> firmware/hardware responsibility.
> >>
> >>
> >> There's a hefty number of limitations, most importantly:
> >>
> >> 1) typically 2 USB devices tops, rest go the classic (non-offload) path
> >> 2) AUDIOSTREAMING interfaces only, MIDI not. While not an interface
> >> type, Media (sound/usb/media) unsupported either
> >> 3) simple PCM, UAC_FORMAT_TYPE_I_PCM only
> >>
> >> Current patchset shows this in form of 'udev->audsb_capable' field.
> >> True if xHCI sideband reasource has been assigned to the device. The
> >> filtering is code is not part of the patchset.
> >>
> >> Important to highlight, 1) means both, offload-aware and offload-unaware
> >> drivers could be utilized simultaneously on the system in runtime.
> >> Opportunistically few devices would be controlled by the offload-aware
> >> driver, whereas everything else by the offload-unaware one. This
> >> differs from existing HDAudio Controller driver situation where either
> >> classic, snd_hda_intel driver takes complete control -or- the
> >> offload-aware snd_soc_avs driver.
> >> In short, once all Audio Sideband resources are depleted, classic
> >> sound/usb/card.c driver manages whatever comes next:
> >>
> >> snd_usb_audio (offload un-aware, sound/usb/card.c)
> >> snd_soc_usb_codec (offload aware, sound/soc/codecs/usb.c)
> >>
> >>
> >> The design goals:
> >> - make ASoC first class citizen of sound/usb
> >> - re-use code found in sound/usb, mimic HDAudio integration in ASoC:
> >> small sound/soc/codecs/hda.c driver leveraging power of entire
> >> sound/pci/hda/
> >> - no shared control over a USB device, either snd_usb_audio or
> >> its ASoC equivalent takes control of the device
> >>
> >> To do that, major tasks are identified:
> >>
> >> a) On ASoC side 'struct snd_card' is part of 'struct snd_soc_card' and
> >> is managed by the framework. Similar situation with 'struct snd_pcm'
> >> and rtd->pcm. To keep the teardown path sane, drop card->private_free()
> >> and pcm->private_free() usage.
> >
> > Well, this is a generic problem of ASoC framework.
> > I believe this should be better handled in ASoC core side at first.
> > e.g. the card object could be created at the very first step of the
> > snd_soc_card creation, too (but without the actual slot assignment or
> > device creation).
>
>
> Well, in my opinion the card's tailing private context (extra size),
> private_free() and all that comes with them in sound/core brings
> unnecessary complexity to the ALSA framework. devm_xxx() is
> enough. Polluting snd_card and sound/core/init.c with driver-specifics
> causes the teardown procedures on the ALSA framework side harder to
> read/maintain.
Well, I'd say it depends, and pretty much a matter of taste. The
resources managed by the card free should be the last thing that is
tied with the card object itself. So, yes, it can be devm, but this
wouldn't make things easier; the devm is merely a serialized release,
after all.
> >> b) To initialize ASoC components/DAI properly, PCM capabilities should be
> >> known up-front. To do that, existing USB card probe() has to be split.
> >> From one-stage to two-stage process:
> >>
> >> - look ahead and parse usb_interface descriptors for PCM endpoints
> >> but do not create any PCMs (sound devices) yet
> >> - create all PCMs based on obtained ->pcm_list and follow with
> >> MIDI/mixers/media
> >>
> >> Such approach allows to feed DAIs proper data even when a valid
> >> sound-card pointer is not yet present - the initialization occurs before
> >> snd_soc_bind_card() is called.
> > This one is another thing that is needed to adjust for ASoC
> > framework. But when snd_card object is available, this can be
> > resolved automatically, too? e.g. snd_pcm object or such can be
> > created at that point. The actual device registration is done anyway
> > later via snd_device_register() call.
>
>
> While I'm up for upgrading either framework in any area necessary to
> have proper UAO support in ASoC, not sure whether it's a good idea to
> make it a requirement for the feature. This will enlarge the series -
> well, guess it will be separate series (dependency) entirely.
The series contains lots of hackish API exposure, and I really would
like to avoid that if possible. In the case of HD-audio, the whole
stuff was moved to its own bus at first for adapting to ASoC hd-audio
ext implementation, but for USB-audio, I don't see the need for that
yet. And, if the concern is about the snd_card object lifecycle and
ASoC binding, it can be improved in the lower level at first.
thanks,
Takashi
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 00/15] ALSA/ASoC: USB Audio Offload
2025-04-15 16:15 ` Takashi Iwai
@ 2025-04-17 10:15 ` Cezary Rojewski
2025-04-22 11:28 ` Pierre-Louis Bossart
0 siblings, 1 reply; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-17 10:15 UTC (permalink / raw)
To: Takashi Iwai
Cc: broonie, tiwai, perex, amadeuszx.slawinski, linux-sound, gregkh,
quic_wcheng, mathias.nyman
On 2025-04-15 6:15 PM, Takashi Iwai wrote:
> On Fri, 11 Apr 2025 11:39:55 +0200,
> Cezary Rojewski wrote:
>>
>> On 2025-04-10 12:24 PM, Takashi Iwai wrote:
>>> On Wed, 09 Apr 2025 13:07:15 +0200,
>>> Cezary Rojewski wrote:
...
>>>> Apart from changes to sound/usb, the patchset contains exemplary
>>>> ASoC-based, offload-aware USB driver and a small sound card on the
>>>> avs-driver side to show how card/dai_link initialization looks like.
>>>>
>>>> In short, USB Audio Offload functionality lowers CPU usage when
>>>> streaming PCM over a USB Audio-Class device. It has been first
>>>> introduced in xHCI 1.2 release and is described in section 7.9 [3].
>>>> Once hardware is prepared with hw_params(), all the USB endpoints
>>>> operations e.g.: start, stop, submitting URBs, are performed
>>>> internally, by the hardware and AudioDSP firmware. Software driver
>>>> shall not intervene.
>>>>
>>>> Startup flow from:
>>>>
>>>> usb_pcm_open()
>>>> usb_pcm_hw_params()
>>>> snd_usb_endpoint_open()
>>>> usb_pcm_prepare()
>>>> usb_set_interface()
>>>> snd_usb_endpoint_start()
>>>> usb_pcm_trigger(cmd: START/STOP etc.)
>>>>
>>>> reduced to:
>>>>
>>>> usb_pcm_open()
>>>> usb_pcm_hw_params()
>>>> snd_usb_endpoint_open()
>>>> usb_pcm_prepare()
>>>> usb_set_interface()
>>>
>>> Hmm, how can it be? The start of EP at prepare stage is done only
>>> conditionally for non-lowlatency or implicit-feedback mode, for
>>> example.
>>
>>
>> On Intel architecture the AudioDSP utilizes internal channel to
>> communicate with xHCI and perform all the transfer operations. In
>> essence, once SET_INTERFACE TRB is done, the usb-driver is not
>> supposed to do anything. The avs-driver (sound driver) would be the
>> trigger here - sends the start/stop/etc. requests to the AudioDSP
>> firmware, DSP does the rest.
>>
>> By trigger I mean SET_PIPELINE_STATE IPC and avs_path_xxx() which are
>> utilized for any transfer-type (sound/soc/intel/avs/pcm.c).
>
> But does the DSP handles the different ways for the implicit feedback
> mode, the low-latency playback and else? A specific mode like the
> implicit feedback mode is mandatory for many devices. Similarly, the
> low-latency mode is essential for some application setups
> (e.g. pipewire or JACK prefers), while it's not always applicable
> depending on the PCM parameters (such as the free-wheeling mode).
>
> AFAIK, the qcom offloading doesn't support those fully, hence the
> configuration must be dynamic for them.
The hw design is unfortunately not simple:
xHCI <> SIO <> ALH
^ USB side ^Audio DSP side
It's an internal channel between so-called Audio Sideband located on
xHCI controller side, goes through Scalable-IO and links with Audio Link
Hub that's acts as 'front-end' for DSP. Every block has their own
requirements and restrictions.
And thus the number of available 'streams' in such configuration is very
limited - typically to just 6. These are not even bi-direction streams
but an internal map: IDs {1, 2} for outputs, IDs {3, 4, 5, 6} for
inputs. Due to such small number of available streams, there is
pressure to filter USB devices. In regard to endpoint types, only DATA
and FEEDBACK are supported, IMPLICIT_FEEDBACK is not. Such device
should fail offload-candidate filtering and be serviced by snd_usb_audio
driver instead.
The official xHCI spec and its section regarding Audio Sideband is
tailored for "claim entire device for offload or ignore" approach:
- scan the USB device descriptors
- verify if it's an offload-candidate
- count number of DATA + FEEDBACK endpoints
- ask xHCI to _reserve_ Audio Sideband resources up-front. This means
the resources are reserved long before sound-devices are visible from
userspace, yet alone opened for streaming
- pcm->open/close() just ask xHCI to perform
SET_ASSIGNMENT(reserved_resources_here) TRBs to prepare/close the
internal channel for runtime operations, that's it
There is no intention to allow for switching the ownership between
offload-aware and offload-unaware. Either entire USB device and all its
endpoints have Audio Sideband resources pre-allocated or no offload is
present for the device at all.
...
>>>> The design goals:
>>>> - make ASoC first class citizen of sound/usb
>>>> - re-use code found in sound/usb, mimic HDAudio integration in ASoC:
>>>> small sound/soc/codecs/hda.c driver leveraging power of entire
>>>> sound/pci/hda/
>>>> - no shared control over a USB device, either snd_usb_audio or
>>>> its ASoC equivalent takes control of the device
>>>>
>>>> To do that, major tasks are identified:
>>>>
>>>> a) On ASoC side 'struct snd_card' is part of 'struct snd_soc_card' and
>>>> is managed by the framework. Similar situation with 'struct snd_pcm'
>>>> and rtd->pcm. To keep the teardown path sane, drop card->private_free()
>>>> and pcm->private_free() usage.
>>>
>>> Well, this is a generic problem of ASoC framework.
>>> I believe this should be better handled in ASoC core side at first.
>>> e.g. the card object could be created at the very first step of the
>>> snd_soc_card creation, too (but without the actual slot assignment or
>>> device creation).
>>
>>
>> Well, in my opinion the card's tailing private context (extra size),
>> private_free() and all that comes with them in sound/core brings
>> unnecessary complexity to the ALSA framework. devm_xxx() is
>> enough. Polluting snd_card and sound/core/init.c with driver-specifics
>> causes the teardown procedures on the ALSA framework side harder to
>> read/maintain.
>
> Well, I'd say it depends, and pretty much a matter of taste. The
> resources managed by the card free should be the last thing that is
> tied with the card object itself. So, yes, it can be devm, but this
> wouldn't make things easier; the devm is merely a serialized release,
> after all.
>
>>>> b) To initialize ASoC components/DAI properly, PCM capabilities should be
>>>> known up-front. To do that, existing USB card probe() has to be split.
>>>> From one-stage to two-stage process:
>>>>
>>>> - look ahead and parse usb_interface descriptors for PCM endpoints
>>>> but do not create any PCMs (sound devices) yet
>>>> - create all PCMs based on obtained ->pcm_list and follow with
>>>> MIDI/mixers/media
>>>>
>>>> Such approach allows to feed DAIs proper data even when a valid
>>>> sound-card pointer is not yet present - the initialization occurs before
>>>> snd_soc_bind_card() is called.
>>> This one is another thing that is needed to adjust for ASoC
>>> framework. But when snd_card object is available, this can be
>>> resolved automatically, too? e.g. snd_pcm object or such can be
>>> created at that point. The actual device registration is done anyway
>>> later via snd_device_register() call.
>>
>>
>> While I'm up for upgrading either framework in any area necessary to
>> have proper UAO support in ASoC, not sure whether it's a good idea to
>> make it a requirement for the feature. This will enlarge the series -
>> well, guess it will be separate series (dependency) entirely.
>
> The series contains lots of hackish API exposure, and I really would
> like to avoid that if possible. In the case of HD-audio, the whole
> stuff was moved to its own bus at first for adapting to ASoC hd-audio
> ext implementation, but for USB-audio, I don't see the need for that
> yet. And, if the concern is about the snd_card object lifecycle and
> ASoC binding, it can be improved in the lower level at first.
I tried hard to avoid hackish stuff, perhaps not hard enough :) Would
it be possible to list the areas which you believe need to be look at?
This is frankly the feedback which I'm most interested in. Mathias gave
me a number of these, e.g.: do not access udev->slot_id in sound/, avoid
manipulating hcd/controller in sound/ and such.
In regard to the HDAudio point, I see clear benefits by having HDAudio
and USB aligned in the approach on ASoC side. It's a path that's known,
works and is well tested.
In regard to the snd_soc_card vs snd_card, in my opinion the refactor of
card initialization is a large task. In the past I did similar change
for the snd_soc_component [1] but the card is an entirely different
beast. That's why I'm suggesting incremental approach - update ASoC
initialization as a next step.
[1]:
https://lore.kernel.org/all/20200731144146.6678-1-cezary.rojewski@intel.com/
Kind regards,
Czarek
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 00/15] ALSA/ASoC: USB Audio Offload
2025-04-17 10:15 ` Cezary Rojewski
@ 2025-04-22 11:28 ` Pierre-Louis Bossart
2025-04-22 14:15 ` Cezary Rojewski
0 siblings, 1 reply; 28+ messages in thread
From: Pierre-Louis Bossart @ 2025-04-22 11:28 UTC (permalink / raw)
To: Cezary Rojewski, Takashi Iwai
Cc: broonie, tiwai, perex, amadeuszx.slawinski, linux-sound, gregkh,
quic_wcheng, mathias.nyman
> In regard to the HDAudio point, I see clear benefits by having HDAudio and USB aligned in the approach on ASoC side. It's a path that's known, works and is well tested.
The current direction for HDaudio is to have the DSP handle ALL streams with DSP-enabled drivers (or none with snd-hda-intel).
But for USB we absolutely need the ability to bypass the DSP when the resources are exceeded (too many endpoints, too many channels, etc), or when low-latency is required (lowering CPU utilization comes at the expense of latency).
In other words, the USB solution MUST expose two PCM paths, a legacy one and a DSP-one, and a sideband communication between DSP and legacy drivers to manage resources.
HDAudio has none of those concepts, which makes it hard to see what the suggested alignment is?
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 00/15] ALSA/ASoC: USB Audio Offload
2025-04-22 11:28 ` Pierre-Louis Bossart
@ 2025-04-22 14:15 ` Cezary Rojewski
2025-04-25 16:53 ` Pierre-Louis Bossart
0 siblings, 1 reply; 28+ messages in thread
From: Cezary Rojewski @ 2025-04-22 14:15 UTC (permalink / raw)
To: Pierre-Louis Bossart
Cc: broonie, tiwai, perex, amadeuszx.slawinski, linux-sound, gregkh,
quic_wcheng, mathias.nyman, Takashi Iwai
On 2025-04-22 1:28 PM, Pierre-Louis Bossart wrote:
>
>> In regard to the HDAudio point, I see clear benefits by having HDAudio and USB aligned in the approach on ASoC side. It's a path that's known, works and is well tested.
>
> The current direction for HDaudio is to have the DSP handle ALL streams with DSP-enabled drivers (or none with snd-hda-intel).
>
> But for USB we absolutely need the ability to bypass the DSP when the resources are exceeded (too many endpoints, too many channels, etc), or when low-latency is required (lowering CPU utilization comes at the expense of latency).
>
> In other words, the USB solution MUST expose two PCM paths, a legacy one and a DSP-one, and a sideband communication between DSP and legacy drivers to manage resources.
>
> HDAudio has none of those concepts, which makes it hard to see what the suggested alignment is?
Hi Pierre,
I see your point but the spec describes the exact opposite. The
intention is to follow the Intel's software architecture specification
for USB Audio Offload Link (UAOL). The high-level steps provided in
previous replies align with the recommendations of the spec. The
reservation of Audio Sideband resources and negotiation of audio format
are part of that. There is no intention to offload every single USB
device and the device is to be claimed for offload entirely or fallback
to the classic, offload-unaware driver.
That design (UAOL) is known, well tested and proven on the market. It
shares a number of logical flows with the HDAudio case and thus having
similar programming on the software side both makes the code easier to
understand and aligns with the spec's recommendations.
Kind regards,
Czarek
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [RFC 00/15] ALSA/ASoC: USB Audio Offload
2025-04-22 14:15 ` Cezary Rojewski
@ 2025-04-25 16:53 ` Pierre-Louis Bossart
0 siblings, 0 replies; 28+ messages in thread
From: Pierre-Louis Bossart @ 2025-04-25 16:53 UTC (permalink / raw)
To: Cezary Rojewski
Cc: broonie, tiwai, perex, amadeuszx.slawinski, linux-sound, gregkh,
quic_wcheng, mathias.nyman, Takashi Iwai
>>> In regard to the HDAudio point, I see clear benefits by having HDAudio and USB aligned in the approach on ASoC side. It's a path that's known, works and is well tested.
>>
>> The current direction for HDaudio is to have the DSP handle ALL streams with DSP-enabled drivers (or none with snd-hda-intel).
>>
>> But for USB we absolutely need the ability to bypass the DSP when the resources are exceeded (too many endpoints, too many channels, etc), or when low-latency is required (lowering CPU utilization comes at the expense of latency).
>>
>> In other words, the USB solution MUST expose two PCM paths, a legacy one and a DSP-one, and a sideband communication between DSP and legacy drivers to manage resources.
>>
>> HDAudio has none of those concepts, which makes it hard to see what the suggested alignment is?
>
>
> Hi Pierre,
>
> I see your point but the spec describes the exact opposite. The intention is to follow the Intel's software architecture specification for USB Audio Offload Link (UAOL). The high-level steps provided in previous replies align with the recommendations of the spec. The reservation of Audio Sideband resources and negotiation of audio format are part of that. There is no intention to offload every single USB device and the device is to be claimed for offload entirely or fallback to the classic, offload-unaware driver.
>
> That design (UAOL) is known, well tested and proven on the market. It shares a number of logical flows with the HDAudio case and thus having similar programming on the software side both makes the code easier to understand and aligns with the spec's recommendations.
I don't know what specification you are referring to... We've consistently agreed with others that the use of the offload capability was a run-time decision left as an exercise for the application. The low-level details of the UAOL flows and IPC should not define how USB is used, they are a 'detail' that should be well compartmentalized in Intel-specific host configurations.
The main argument I would have to keep the legacy non-offload solution always available is that USB is the only solution for users if for some reason their DSP-based solution is not supported (missing or incomplete machine driver, missing firmware, etc). Forcing users to rely on offload is not a good direction, it has to opt-in for Linux distributions.
I should also point out that one key requirement is that the DSP-based solution does not degrade the quality with any asynchronous sample-rate converter, which requires firmware to be able to avoid working with the 1ms tick but instead adjust the amount of data processed per tick, specifically in the case of endpoints with feedback. If the firmware does resample with a TBD filter, then the performance of that TBD filter should be clearly documented. We had similar discussions in the past with MP3 offload, the expectation is that offload only improves power consumption but does not result in any quality shortcuts taken.
^ permalink raw reply [flat|nested] 28+ messages in thread
end of thread, other threads:[~2025-04-25 17:14 UTC | newest]
Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox