Commit 6392dcd1 authored by Takashi Iwai's avatar Takashi Iwai
Browse files

ALSA: usb-audio: Register card at the last interface

The USB-audio driver matches per interface, and as default, it
registers the card instance at the very first instance.  This can be a
problem for the devices that have multiple interfaces to be probed, as
the udev rule isn't applied properly for the later appearing
interfaces.  Although we introduced the delayed_register option and
the quirks for covering those shortcomings, it's nothing but a
workaround for specific devices.

This patch is an another attempt to fix the problem in a more generic
way.  Now the driver checks the whole USB device descriptor at the
very first time when an interface is attached to a sound card.  It
looks at each matching interface in the descriptor and remembers the
last matching one.  The snd_card_register() is invoked only when this
last interface is probed.

After this change, the quirks for the delayed registration become
superfluous, hence they are removed along with the patch.  OTOH, the
delayed_register option is still kept, as it might be useful for some
corner cases (e.g. a special driver overtakes the interface probe from
the standard driver, and the last interface probe may miss).

Link: https://lore.kernel.org/r/20220904161247.16461-1-tiwai@suse.de


Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 16c5cde2
Loading
Loading
Loading
Loading
+25 −7
Original line number Diff line number Diff line
@@ -690,7 +690,7 @@ static bool get_alias_id(struct usb_device *dev, unsigned int *id)
	return false;
}

static bool check_delayed_register_option(struct snd_usb_audio *chip, int iface)
static int check_delayed_register_option(struct snd_usb_audio *chip)
{
	int i;
	unsigned int id, inum;
@@ -699,14 +699,31 @@ static bool check_delayed_register_option(struct snd_usb_audio *chip, int iface)
		if (delayed_register[i] &&
		    sscanf(delayed_register[i], "%x:%x", &id, &inum) == 2 &&
		    id == chip->usb_id)
			return iface < inum;
			return inum;
	}

	return false;
	return -1;
}

static const struct usb_device_id usb_audio_ids[]; /* defined below */

/* look for the last interface that matches with our ids and remember it */
static void find_last_interface(struct snd_usb_audio *chip)
{
	struct usb_host_config *config = chip->dev->actconfig;
	struct usb_interface *intf;
	int i;

	if (!config)
		return;
	for (i = 0; i < config->desc.bNumInterfaces; i++) {
		intf = config->interface[i];
		if (usb_match_id(intf, usb_audio_ids))
			chip->last_iface = intf->altsetting[0].desc.bInterfaceNumber;
	}
	usb_audio_dbg(chip, "Found last interface = %d\n", chip->last_iface);
}

/* look for the corresponding quirk */
static const struct snd_usb_audio_quirk *
get_alias_quirk(struct usb_device *dev, unsigned int id)
@@ -813,6 +830,7 @@ static int usb_audio_probe(struct usb_interface *intf,
			err = -ENODEV;
			goto __error;
		}
		find_last_interface(chip);
	}

	if (chip->num_interfaces >= MAX_CARD_INTERFACES) {
@@ -862,11 +880,11 @@ static int usb_audio_probe(struct usb_interface *intf,
		chip->need_delayed_register = false; /* clear again */
	}

	/* we are allowed to call snd_card_register() many times, but first
	 * check to see if a device needs to skip it or do anything special
	/* register card if we reach to the last interface or to the specified
	 * one given via option
	 */
	if (!snd_usb_registration_quirk(chip, ifnum) &&
	    !check_delayed_register_option(chip, ifnum)) {
	if (check_delayed_register_option(chip) == ifnum ||
	    chip->last_iface == ifnum) {
		err = snd_card_register(chip->card);
		if (err < 0)
			goto __error;
+0 −42
Original line number Diff line number Diff line
@@ -2030,48 +2030,6 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip,
	}
}

/*
 * registration quirk:
 * the registration is skipped if a device matches with the given ID,
 * unless the interface reaches to the defined one.  This is for delaying
 * the registration until the last known interface, so that the card and
 * devices appear at the same time.
 */

struct registration_quirk {
	unsigned int usb_id;	/* composed via USB_ID() */
	unsigned int interface;	/* the interface to trigger register */
};

#define REG_QUIRK_ENTRY(vendor, product, iface) \
	{ .usb_id = USB_ID(vendor, product), .interface = (iface) }

static const struct registration_quirk registration_quirks[] = {
	REG_QUIRK_ENTRY(0x0951, 0x16d8, 2),	/* Kingston HyperX AMP */
	REG_QUIRK_ENTRY(0x0951, 0x16ed, 2),	/* Kingston HyperX Cloud Alpha S */
	REG_QUIRK_ENTRY(0x0951, 0x16ea, 2),	/* Kingston HyperX Cloud Flight S */
	REG_QUIRK_ENTRY(0x0ecb, 0x1f46, 2),	/* JBL Quantum 600 */
	REG_QUIRK_ENTRY(0x0ecb, 0x1f47, 2),	/* JBL Quantum 800 */
	REG_QUIRK_ENTRY(0x0ecb, 0x1f4c, 2),	/* JBL Quantum 400 */
	REG_QUIRK_ENTRY(0x0ecb, 0x2039, 2),	/* JBL Quantum 400 */
	REG_QUIRK_ENTRY(0x0ecb, 0x203c, 2),	/* JBL Quantum 600 */
	REG_QUIRK_ENTRY(0x0ecb, 0x203e, 2),	/* JBL Quantum 800 */
	{ 0 }					/* terminator */
};

/* return true if skipping registration */
bool snd_usb_registration_quirk(struct snd_usb_audio *chip, int iface)
{
	const struct registration_quirk *q;

	for (q = registration_quirks; q->usb_id; q++)
		if (chip->usb_id == q->usb_id)
			return iface < q->interface;

	/* Register as normal */
	return false;
}

/*
 * driver behavior quirk flags
 */
+0 −2
Original line number Diff line number Diff line
@@ -48,8 +48,6 @@ void snd_usb_audioformat_attributes_quirk(struct snd_usb_audio *chip,
					  struct audioformat *fp,
					  int stream);

bool snd_usb_registration_quirk(struct snd_usb_audio *chip, int iface);

void snd_usb_init_quirk_flags(struct snd_usb_audio *chip);

#endif /* __USBAUDIO_QUIRKS_H */
+1 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ struct snd_usb_audio {
	unsigned int quirk_flags;
	unsigned int need_delayed_register:1; /* warn for delayed registration */
	int num_interfaces;
	int last_iface;
	int num_suspended_intf;
	int sample_rate_read_error;