mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 05:32:00 +00:00
6aa8700150
Registering Numark Party Mix II fails with error 'bogus bTerminalLink 1'. The problem stems from the driver not being able to find input/output terminals required to configure audio streaming. The information about those terminals is stored in AudioControl Interface. Numark device contains 2 AudioControl Interfaces and the driver checks only one of them. According to the USB standard, a device can have multiple audio functions, each represented by Audio Interface Collection. Every audio function is considered to be closed box and will contain unique AudioControl Interface and zero or more AudioStreaming and MIDIStreaming Interfaces. The Numark device adheres to the standard and defines two audio functions: - MIDIStreaming function - AudioStreaming function It starts with MIDI function, followed by the audio function. The driver saves the first AudioControl Interface in `snd_usb_audio` structure associated with the entire device. It then attempts to use this interface to query for terminals and clocks. However, this fails because the correct information is stored in the second AudioControl Interface, defined in the second Audio Interface Collection. This patch introduces a structure holding association between each MIDI/Audio Interface and its corresponding AudioControl Interface, instead of relying on AudioControl Interface defined for the entire device. This structure is populated during usb probing phase and leveraged later when querying for terminals and when sending USB requests. Alternative solutions considered include: - defining a quirk for Numark where the order of interface is manually changed, or terminals are hardcoded in the driver. This solution would have fixed only this model, though it seems that device is USB compliant, and it also seems that other devices from this company may be affected. What's more, it looks like products from other manufacturers have similar problems, i.e. Rane One DJ console - keeping a list of all AudioControl Interfaces and querying all of them to find required information. That would have solved my problem and have low probability of breaking other devices, as we would always start with the same logic of querying first AudioControl Interface. This solution would not have followed the standard though. This patch preserves the `snd_usb_audio.ctrl_intf` variable, which holds the first AudioControl Interface, and uses it as a fallback when some interfaces are not parsed correctly and lack an associated AudioControl Interface, i.e., when configured via quirks. Link: https://bugzilla.kernel.org/show_bug.cgi?id=217865 Signed-off-by: Karol Kosik <k.kosik@outlook.com> Link: https://patch.msgid.link/AS8P190MB1285893F4735C8B32AD3886BEC852@AS8P190MB1285.EURP190.PROD.OUTLOOK.COM Signed-off-by: Takashi Iwai <tiwai@suse.de>
167 lines
3.6 KiB
C
167 lines
3.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
*/
|
|
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/usb.h>
|
|
|
|
#include "usbaudio.h"
|
|
#include "helper.h"
|
|
#include "quirks.h"
|
|
|
|
/*
|
|
* combine bytes and get an integer value
|
|
*/
|
|
unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size)
|
|
{
|
|
switch (size) {
|
|
case 1: return *bytes;
|
|
case 2: return combine_word(bytes);
|
|
case 3: return combine_triple(bytes);
|
|
case 4: return combine_quad(bytes);
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* parse descriptor buffer and return the pointer starting the given
|
|
* descriptor type.
|
|
*/
|
|
void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype)
|
|
{
|
|
u8 *p, *end, *next;
|
|
|
|
p = descstart;
|
|
end = p + desclen;
|
|
for (; p < end;) {
|
|
if (p[0] < 2)
|
|
return NULL;
|
|
next = p + p[0];
|
|
if (next > end)
|
|
return NULL;
|
|
if (p[1] == dtype && (!after || (void *)p > after)) {
|
|
return p;
|
|
}
|
|
p = next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* find a class-specified interface descriptor with the given subtype.
|
|
*/
|
|
void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype)
|
|
{
|
|
unsigned char *p = after;
|
|
|
|
while ((p = snd_usb_find_desc(buffer, buflen, p,
|
|
USB_DT_CS_INTERFACE)) != NULL) {
|
|
if (p[0] >= 3 && p[2] == dsubtype)
|
|
return p;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Wrapper for usb_control_msg().
|
|
* Allocates a temp buffer to prevent dmaing from/to the stack.
|
|
*/
|
|
int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, __u8 request,
|
|
__u8 requesttype, __u16 value, __u16 index, void *data,
|
|
__u16 size)
|
|
{
|
|
int err;
|
|
void *buf = NULL;
|
|
int timeout;
|
|
|
|
if (usb_pipe_type_check(dev, pipe))
|
|
return -EINVAL;
|
|
|
|
if (size > 0) {
|
|
buf = kmemdup(data, size, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (requesttype & USB_DIR_IN)
|
|
timeout = USB_CTRL_GET_TIMEOUT;
|
|
else
|
|
timeout = USB_CTRL_SET_TIMEOUT;
|
|
|
|
err = usb_control_msg(dev, pipe, request, requesttype,
|
|
value, index, buf, size, timeout);
|
|
|
|
if (size > 0) {
|
|
memcpy(data, buf, size);
|
|
kfree(buf);
|
|
}
|
|
|
|
snd_usb_ctl_msg_quirk(dev, pipe, request, requesttype,
|
|
value, index, data, size);
|
|
|
|
return err;
|
|
}
|
|
|
|
unsigned char snd_usb_parse_datainterval(struct snd_usb_audio *chip,
|
|
struct usb_host_interface *alts)
|
|
{
|
|
switch (snd_usb_get_speed(chip->dev)) {
|
|
case USB_SPEED_HIGH:
|
|
case USB_SPEED_SUPER:
|
|
case USB_SPEED_SUPER_PLUS:
|
|
if (get_endpoint(alts, 0)->bInterval >= 1 &&
|
|
get_endpoint(alts, 0)->bInterval <= 4)
|
|
return get_endpoint(alts, 0)->bInterval - 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct usb_host_interface *
|
|
snd_usb_get_host_interface(struct snd_usb_audio *chip, int ifnum, int altsetting)
|
|
{
|
|
struct usb_interface *iface;
|
|
|
|
iface = usb_ifnum_to_if(chip->dev, ifnum);
|
|
if (!iface)
|
|
return NULL;
|
|
return usb_altnum_to_altsetting(iface, altsetting);
|
|
}
|
|
|
|
int snd_usb_add_ctrl_interface_link(struct snd_usb_audio *chip, int ifnum,
|
|
int ctrlif)
|
|
{
|
|
struct usb_device *dev = chip->dev;
|
|
struct usb_host_interface *host_iface;
|
|
|
|
if (chip->num_intf_to_ctrl >= MAX_CARD_INTERFACES) {
|
|
dev_info(&dev->dev, "Too many interfaces assigned to the single USB-audio card\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* find audiocontrol interface */
|
|
host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0];
|
|
|
|
chip->intf_to_ctrl[chip->num_intf_to_ctrl].interface = ifnum;
|
|
chip->intf_to_ctrl[chip->num_intf_to_ctrl].ctrl_intf = host_iface;
|
|
chip->num_intf_to_ctrl++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct usb_host_interface *snd_usb_find_ctrl_interface(struct snd_usb_audio *chip,
|
|
int ifnum)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < chip->num_intf_to_ctrl; ++i)
|
|
if (chip->intf_to_ctrl[i].interface == ifnum)
|
|
return chip->intf_to_ctrl[i].ctrl_intf;
|
|
|
|
/* Fallback to first audiocontrol interface */
|
|
return chip->ctrl_intf;
|
|
}
|