[alsa-devel] [PATCH 3/4] ALSA: usbaudio: implement basic set of class v2.0 parser
Daniel Mack
daniel at caiaq.de
Mon Feb 22 17:28:56 CET 2010
This adds a number of parsers for audio class v2.0. In particular, the
following internals are different and now handled by the code:
* the number of streaming interfaces is now reported by an interface
association descriptor. The old approach using a proprietary
descriptor is deprecated.
* The number of channels per interface is now stored in the AS_GENERAL
descriptor (used to be part of the FORMAT_TYPE descriptor).
* The list of supported sample rates is no longer stored in a variable
length appendix of the format_type descriptor but is retrieved from
the device using a class specific GET_RANGE command.
* Supported sample formats are now reported as 32bit bitmap rather than
a fixed value. For now, this is worked around by choosing just one of
them.
* A devices needs to have at least one CLOCK_SOURCE descriptor which
denotes a clockID that is needed im the class request command.
* Many descriptors (format_type, ...) have changed their layout. Handle
this by casting the descriptors to the appropriate structs.
Signed-off-by: Daniel Mack <daniel at caiaq.de>
---
sound/usb/usbaudio.c | 342 +++++++++++++++++++++++++++++++++++++++++---------
sound/usb/usbaudio.h | 3 +
2 files changed, 288 insertions(+), 57 deletions(-)
diff --git a/sound/usb/usbaudio.c b/sound/usb/usbaudio.c
index 8925879..4fd0ef7 100644
--- a/sound/usb/usbaudio.c
+++ b/sound/usb/usbaudio.c
@@ -2412,7 +2412,7 @@ static int is_big_endian_format(struct snd_usb_audio *chip, struct audioformat *
}
/*
- * parse the audio format type I descriptor
+ * parse the audio format type I descriptor (audio class v1)
* and returns the corresponding pcm format
*
* @dev: usb device
@@ -2420,17 +2420,53 @@ static int is_big_endian_format(struct snd_usb_audio *chip, struct audioformat *
* @format: the format tag (wFormatTag)
* @fmt: the format type descriptor
*/
-static int parse_audio_format_i_type(struct snd_usb_audio *chip, struct audioformat *fp,
- int format, void *fmt_raw)
+static int parse_audio_format_i_type(struct snd_usb_audio *chip,
+ struct audioformat *fp,
+ int format, void *fmt_raw,
+ int protocol)
{
- int pcm_format;
+ int pcm_format, i;
int sample_width, sample_bytes;
- struct usb_format_type_i *fmt = fmt_raw;
+
+ switch (protocol) {
+ case USB_AC_VERSION_1: {
+ struct usb_format_type_i *fmt = fmt_raw;
+ sample_width = fmt->bBitResolution;
+ sample_bytes = fmt->bSubframeSize;
+ break;
+ }
+
+ case USB_AC_VERSION_2: {
+ struct usb_format_type_i_ext *fmt = fmt_raw;
+ sample_width = fmt->bBitResolution;
+ sample_bytes = fmt->bSubslotSize;
+
+ /*
+ * FIXME
+ * USB audio class v2 devices specify a bitmap of possible
+ * audio formats rather than one fix value. For now, we just
+ * pick one of them and report that as the only possible
+ * value for this setting.
+ * The bit allocation map is in fact compatible to the
+ * wFormatTag of the v1 AS streaming descriptors, which is why
+ * we can simply map the matrix.
+ */
+
+ for (i = 0; i < 5; i++)
+ if (format & (1UL << i)) {
+ format = i + 1;
+ break;
+ }
+
+ break;
+ }
+
+ default:
+ return -EINVAL;
+ }
/* FIXME: correct endianess and sign? */
pcm_format = -1;
- sample_width = fmt->bBitResolution;
- sample_bytes = fmt->bSubframeSize;
switch (format) {
case 0: /* some devices don't define this correctly... */
@@ -2498,7 +2534,7 @@ static int parse_audio_format_i_type(struct snd_usb_audio *chip, struct audiofor
/*
* parse the format descriptor and stores the possible sample rates
- * on the audioformat table.
+ * on the audioformat table (audio class v1).
*
* @dev: usb device
* @fp: audioformat record
@@ -2506,8 +2542,8 @@ static int parse_audio_format_i_type(struct snd_usb_audio *chip, struct audiofor
* @offset: the start offset of descriptor pointing the rate type
* (7 for type I and II, 8 for type II)
*/
-static int parse_audio_format_rates(struct snd_usb_audio *chip, struct audioformat *fp,
- unsigned char *fmt, int offset)
+static int parse_audio_format_rates_v1(struct snd_usb_audio *chip, struct audioformat *fp,
+ unsigned char *fmt, int offset)
{
int nr_rates = fmt[offset];
@@ -2563,13 +2599,85 @@ static int parse_audio_format_rates(struct snd_usb_audio *chip, struct audioform
}
/*
- * parse the format type I and III descriptors
+ * parse the format descriptor and stores the possible sample rates
+ * on the audioformat table (audio class v2).
+ */
+static int parse_audio_format_rates_v2(struct snd_usb_audio *chip,
+ struct audioformat *fp,
+ struct usb_host_interface *iface)
+{
+ struct usb_device *dev = chip->dev;
+ unsigned char tmp[2], *data;
+ int i, nr_rates, data_size, ret = 0;
+
+ /* get the number of sample rates first by only fetching 2 bytes */
+ ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), CS_RANGE,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+ 0x0100, chip->clock_id << 8, tmp, sizeof(tmp), 1000);
+
+ if (ret < 0) {
+ snd_printk(KERN_ERR "unable to retrieve number of sample rates\n");
+ goto err;
+ }
+
+ nr_rates = (tmp[1] << 8) | tmp[0];
+ data_size = 2 + 12 * nr_rates;
+ data = kzalloc(data_size, GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ /* now get the full information */
+ ret = snd_usb_ctl_msg(dev, usb_rcvctrlpipe(dev, 0), CS_RANGE,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
+ 0x0100, chip->clock_id << 8, data, data_size, 1000);
+
+ if (ret < 0) {
+ snd_printk(KERN_ERR "unable to retrieve sample rate range\n");
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ fp->rate_table = kmalloc(sizeof(int) * nr_rates, GFP_KERNEL);
+ if (!fp->rate_table) {
+ ret = -ENOMEM;
+ goto err_free;
+ }
+
+ fp->nr_rates = 0;
+ fp->rate_min = fp->rate_max = 0;
+
+ for (i = 0; i < nr_rates; i++) {
+ int rate = combine_quad(&data[2 + 12 * i]);
+
+ fp->rate_table[fp->nr_rates] = rate;
+ if (!fp->rate_min || rate < fp->rate_min)
+ fp->rate_min = rate;
+ if (!fp->rate_max || rate > fp->rate_max)
+ fp->rate_max = rate;
+ fp->rates |= snd_pcm_rate_to_rate_bit(rate);
+ fp->nr_rates++;
+ }
+
+err_free:
+ kfree(data);
+err:
+ return ret;
+}
+
+/*
+ * parse the format type I and III descriptors (audio class v1)
*/
-static int parse_audio_format_i(struct snd_usb_audio *chip, struct audioformat *fp,
- int format, void *fmt_raw)
+static int parse_audio_format_i(struct snd_usb_audio *chip,
+ struct audioformat *fp,
+ int format, void *fmt_raw,
+ struct usb_host_interface *iface)
{
- int pcm_format;
+ struct usb_interface_descriptor *altsd = get_iface_desc(iface);
struct usb_format_type_i *fmt = fmt_raw;
+ int protocol = altsd->bInterfaceProtocol;
+ int pcm_format, ret;
if (fmt->bFormatType == USB_FORMAT_TYPE_III) {
/* FIXME: the format type is really IECxxx
@@ -2589,30 +2697,49 @@ static int parse_audio_format_i(struct snd_usb_audio *chip, struct audioformat *
pcm_format = SNDRV_PCM_FORMAT_S16_LE;
}
} else {
- pcm_format = parse_audio_format_i_type(chip, fp, format, fmt);
+ pcm_format = parse_audio_format_i_type(chip, fp, format, fmt, protocol);
if (pcm_format < 0)
return -1;
}
fp->format = pcm_format;
- fp->channels = fmt->bNrChannels;
+
+ /* gather possible sample rates */
+ /* audio class v1 reports possible sample rates as part of the
+ * proprietary class specific descriptor.
+ * audio class v2 uses class specific EP0 range requests for that.
+ */
+ switch (protocol) {
+ case USB_AC_VERSION_1:
+ fp->channels = fmt->bNrChannels;
+ ret = parse_audio_format_rates_v1(chip, fp, fmt_raw, 7);
+ break;
+ case USB_AC_VERSION_2:
+ /* fp->channels is already set in this case */
+ ret = parse_audio_format_rates_v2(chip, fp, iface);
+ break;
+ }
if (fp->channels < 1) {
snd_printk(KERN_ERR "%d:%u:%d : invalid channels %d\n",
chip->dev->devnum, fp->iface, fp->altsetting, fp->channels);
return -1;
}
- return parse_audio_format_rates(chip, fp, fmt_raw, 7);
+
+ return ret;
}
/*
- * parse the format type II descriptor
+ * parse the format type II descriptor (audio class v1)
*/
-static int parse_audio_format_ii(struct snd_usb_audio *chip, struct audioformat *fp,
- int format, void *fmt_raw)
+static int parse_audio_format_ii(struct snd_usb_audio *chip,
+ struct audioformat *fp,
+ int format, void *fmt_raw,
+ struct usb_host_interface *iface)
{
- int brate, framesize;
- struct usb_format_type_ii *fmt = fmt_raw;
+ int brate, framesize, ret;
+ struct usb_interface_descriptor *altsd = get_iface_desc(iface);
+ int protocol = altsd->bInterfaceProtocol;
switch (format) {
case USB_AUDIO_FORMAT_AC3:
@@ -2632,15 +2759,33 @@ static int parse_audio_format_ii(struct snd_usb_audio *chip, struct audioformat
fp->channels = 1;
- brate = le16_to_cpu(fmt->wMaxBitRate);
- framesize = le16_to_cpu(fmt->wSamplesPerFrame);
- snd_printd(KERN_INFO "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
- fp->frame_size = framesize;
- return parse_audio_format_rates(chip, fp, fmt_raw, 8); /* fmt[8..] sample rates */
+ switch (protocol) {
+ case USB_AC_VERSION_1: {
+ struct usb_format_type_ii *fmt = fmt_raw;
+ brate = le16_to_cpu(fmt->wMaxBitRate);
+ framesize = le16_to_cpu(fmt->wSamplesPerFrame);
+ snd_printd(KERN_INFO "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
+ fp->frame_size = framesize;
+ ret = parse_audio_format_rates_v1(chip, fp, fmt_raw, 8); /* fmt[8..] sample rates */
+ break;
+ }
+ case USB_AC_VERSION_2: {
+ struct usb_format_type_ii_ext *fmt = fmt_raw;
+ brate = le16_to_cpu(fmt->wMaxBitRate);
+ framesize = le16_to_cpu(fmt->wSamplesPerFrame);
+ snd_printd(KERN_INFO "found format II with max.bitrate = %d, frame size=%d\n", brate, framesize);
+ fp->frame_size = framesize;
+ ret = parse_audio_format_rates_v2(chip, fp, iface);
+ break;
+ }
+ }
+
+ return ret;
}
static int parse_audio_format(struct snd_usb_audio *chip, struct audioformat *fp,
- int format, void *fmt_raw, int stream)
+ int format, void *fmt_raw, int stream,
+ struct usb_host_interface *iface)
{
int err;
/* we only parse the common header of all format types here,
@@ -2650,10 +2795,10 @@ static int parse_audio_format(struct snd_usb_audio *chip, struct audioformat *fp
switch (fmt->bFormatType) {
case USB_FORMAT_TYPE_I:
case USB_FORMAT_TYPE_III:
- err = parse_audio_format_i(chip, fp, format, fmt);
+ err = parse_audio_format_i(chip, fp, format, fmt, iface);
break;
case USB_FORMAT_TYPE_II:
- err = parse_audio_format_ii(chip, fp, format, fmt);
+ err = parse_audio_format_ii(chip, fp, format, fmt, iface);
break;
default:
snd_printd(KERN_INFO "%d:%u:%d : format type %d is not supported yet\n",
@@ -2700,10 +2845,10 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
struct usb_host_interface *alts;
struct usb_interface_descriptor *altsd;
int i, altno, err, stream;
- int format;
+ int format = 0, num_channels = 0;
struct audioformat *fp = NULL;
unsigned char *fmt, *csep;
- int num;
+ int num, protocol;
dev = chip->dev;
@@ -2720,10 +2865,9 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
num = 4;
for (i = 0; i < num; i++) {
- struct usb_as_interface_v1 *as;
-
alts = &iface->altsetting[i];
altsd = get_iface_desc(alts);
+ protocol = altsd->bInterfaceProtocol;
/* skip invalid one */
if ((altsd->bInterfaceClass != USB_CLASS_AUDIO &&
altsd->bInterfaceClass != USB_CLASS_VENDOR_SPEC) ||
@@ -2748,21 +2892,54 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
continue;
/* get audio formats */
- as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, AS_GENERAL);
+ switch (protocol) {
+ case USB_AC_VERSION_1: {
+ struct usb_as_interface_v1 *as =
+ snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, AS_GENERAL);
+
+ if (!as) {
+ snd_printk(KERN_ERR "%d:%u:%d : AS_GENERAL descriptor not found\n",
+ dev->devnum, iface_no, altno);
+ continue;
+ }
- if (!as) {
- snd_printk(KERN_ERR "%d:%u:%d : AS_GENERAL descriptor not found\n",
- dev->devnum, iface_no, altno);
- continue;
+ if (as->bLength < sizeof(*as)) {
+ snd_printk(KERN_ERR "%d:%u:%d : invalid AS_GENERAL desc\n",
+ dev->devnum, iface_no, altno);
+ continue;
+ }
+
+ format = le16_to_cpu(as->wFormatTag); /* remember the format value */
+ break;
}
- if (as->bLength < sizeof(*as)) {
- snd_printk(KERN_ERR "%d:%u:%d : invalid AS_GENERAL desc\n",
- dev->devnum, iface_no, altno);
- continue;
+ case USB_AC_VERSION_2: {
+ struct usb_as_interface_v2 *as =
+ snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, AS_GENERAL);
+
+ if (!as) {
+ snd_printk(KERN_ERR "%d:%u:%d : AS_GENERAL descriptor not found\n",
+ dev->devnum, iface_no, altno);
+ continue;
+ }
+
+ if (as->bLength < sizeof(*as)) {
+ snd_printk(KERN_ERR "%d:%u:%d : invalid AS_GENERAL desc\n",
+ dev->devnum, iface_no, altno);
+ continue;
+ }
+
+ num_channels = as->bNrChannels;
+ format = le32_to_cpu(as->bmFormats);
+
+ break;
}
- format = le16_to_cpu(as->wFormatTag); /* remember the format value */
+ default:
+ snd_printk(KERN_ERR "%d:%u:%d : unknown interface protocol %04x\n",
+ dev->devnum, iface_no, altno, protocol);
+ continue;
+ }
/* get format type */
fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, FORMAT_TYPE);
@@ -2771,7 +2948,8 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
dev->devnum, iface_no, altno);
continue;
}
- if (fmt[0] < 8) {
+ if (((protocol == USB_AC_VERSION_1) && (fmt[0] < 8)) ||
+ ((protocol == USB_AC_VERSION_2) && (fmt[0] < 6))) {
snd_printk(KERN_ERR "%d:%u:%d : invalid FORMAT_TYPE desc\n",
dev->devnum, iface_no, altno);
continue;
@@ -2785,6 +2963,7 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
if (fmt[4] == 1 && fmt[5] == 2 && altno == 2 && num == 3 &&
fp && fp->altsetting == 1 && fp->channels == 1 &&
fp->format == SNDRV_PCM_FORMAT_S16_LE &&
+ protocol == USB_AC_VERSION_1 &&
le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize) ==
fp->maxpacksize * 2)
continue;
@@ -2813,6 +2992,8 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
fp->ep_attr = get_endpoint(alts, 0)->bmAttributes;
fp->datainterval = parse_datainterval(chip, alts);
fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize);
+ /* num_channels is only set for v2 interfaces */
+ fp->channels = num_channels;
if (snd_usb_get_speed(dev) == USB_SPEED_HIGH)
fp->maxpacksize = (((fp->maxpacksize >> 11) & 3) + 1)
* (fp->maxpacksize & 0x7ff);
@@ -2848,7 +3029,7 @@ static int parse_audio_endpoints(struct snd_usb_audio *chip, int iface_no)
}
/* ok, let's parse further... */
- if (parse_audio_format(chip, fp, format, fmt, stream) < 0) {
+ if (parse_audio_format(chip, fp, format, fmt, stream, alts) < 0) {
kfree(fp->rate_table);
kfree(fp);
continue;
@@ -2956,35 +3137,82 @@ static int snd_usb_create_streams(struct snd_usb_audio *chip, int ctrlif)
{
struct usb_device *dev = chip->dev;
struct usb_host_interface *host_iface;
- struct usb_ac_intf_header_v1 *h1;
+ struct usb_interface_descriptor *altsd;
void *control_header;
- int i;
+ int i, protocol;
/* find audiocontrol interface */
host_iface = &usb_ifnum_to_if(dev, ctrlif)->altsetting[0];
control_header = snd_usb_find_csint_desc(host_iface->extra,
host_iface->extralen,
NULL, HEADER);
+ altsd = get_iface_desc(host_iface);
+ protocol = altsd->bInterfaceProtocol;
if (!control_header) {
snd_printk(KERN_ERR "cannot find HEADER\n");
return -EINVAL;
}
- h1 = control_header;
+ switch (protocol) {
+ case USB_AC_VERSION_1: {
+ struct usb_ac_intf_header_v1 *h1 = control_header;
- if (!h1->bInCollection) {
- snd_printk(KERN_INFO "skipping empty audio interface (v1)\n");
- return -EINVAL;
+ if (!h1->bInCollection) {
+ snd_printk(KERN_INFO "skipping empty audio interface (v1)\n");
+ return -EINVAL;
+ }
+
+ if (h1->bLength < sizeof(*h1) + h1->bInCollection) {
+ snd_printk(KERN_ERR "invalid HEADER (v1)\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < h1->bInCollection; i++)
+ snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]);
+
+ break;
}
- if (h1->bLength < sizeof(*h1) + h1->bInCollection) {
- snd_printk(KERN_ERR "invalid HEADER (v1)\n");
- return -EINVAL;
+ case USB_AC_VERSION_2: {
+ struct usb_clock_source_descriptor *cs;
+ struct usb_interface_assoc_descriptor *assoc =
+ usb_ifnum_to_if(dev, ctrlif)->intf_assoc;
+
+ if (!assoc) {
+ snd_printk(KERN_ERR "Audio class v2 interfaces need an interface association\n");
+ return -EINVAL;
+ }
+
+ /* FIXME: for now, we expect there is at least one clock source
+ * descriptor and we always take the first one.
+ * We should properly support devices with multiple clock sources,
+ * clock selectors and sample rate conversion units. */
+
+ cs = snd_usb_find_csint_desc(host_iface->extra, host_iface->extralen,
+ NULL, CLOCK_SOURCE);
+
+ if (!cs) {
+ snd_printk(KERN_ERR "CLOCK_SOURCE descriptor not found\n");
+ return -EINVAL;
+ }
+
+ chip->clock_id = cs->bClockID;
+
+ for (i = 0; i < assoc->bInterfaceCount; i++) {
+ int intf = assoc->bFirstInterface + i;
+
+ if (intf != ctrlif)
+ snd_usb_create_stream(chip, ctrlif, intf);
+ }
+
+ break;
}
- for (i = 0; i < h1->bInCollection; i++)
- snd_usb_create_stream(chip, ctrlif, h1->baInterfaceNr[i]);
+ default:
+ snd_printk(KERN_ERR "unknown protocol version 0x%02x\n", protocol);
+ return -EINVAL;
+ }
return 0;
}
diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h
index d08f347..d1f5ca7 100644
--- a/sound/usb/usbaudio.h
+++ b/sound/usb/usbaudio.h
@@ -304,6 +304,9 @@ struct snd_usb_audio {
int num_interfaces;
int num_suspended_intf;
+ /* for audio class v2 */
+ int clock_id;
+
struct list_head pcm_list; /* list of pcm streams */
int pcm_devs;
--
1.6.6.2
More information about the Alsa-devel
mailing list