qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 5/6] Add support for a USB audio device model


From: Gerd Hoffmann
Subject: [Qemu-devel] [PATCH 5/6] Add support for a USB audio device model
Date: Wed, 27 Oct 2010 18:04:34 +0200

From: H. Peter Anvin <address@hidden>

This brings a usb audio device to qemu.  Output only, fixed at
16bit stereo @ 480000 Hz.  Based on a patch from
H. Peter Anvin <address@hidden>

Usage: add '-device usb-audio' to your qemu command line.

Works sorta ok on a idle machine.  Known issues:

 * Is *very* sensitive to latencies: when the uhci emulation misses one
   of the usb frame rate wakeups (1k/sec!) you'll loose/delay data from
   the audio stream, resulting in dropouts.
 * Also seems to not play very well with the usb tablet (and/or usb hub,
   to be investigated).  Best try this as the only device on a usb bus.
 * Burns quite some CPU due to usb polling.

In short:  It brings the qemu usb emulation to its limits.  Enjoy!

Signed-off-by: Gerd Hoffmann <address@hidden>
---
 Makefile.objs  |    2 +-
 hw/usb-audio.c |  758 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/usb-net.c   |    3 -
 hw/usb.h       |    7 +
 4 files changed, 766 insertions(+), 4 deletions(-)
 create mode 100644 hw/usb-audio.c

diff --git a/Makefile.objs b/Makefile.objs
index f07fb01..06dfcd0 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -73,7 +73,7 @@ common-obj-y += eeprom93xx.o
 common-obj-y += scsi-disk.o cdrom.o
 common-obj-y += scsi-generic.o scsi-bus.o
 common-obj-y += usb.o usb-hub.o usb-$(HOST_USB).o usb-hid.o usb-msd.o 
usb-wacom.o
-common-obj-y += usb-serial.o usb-net.o usb-bus.o
+common-obj-y += usb-serial.o usb-net.o usb-bus.o usb-audio.o
 common-obj-$(CONFIG_SSI) += ssi.o
 common-obj-$(CONFIG_SSI_SD) += ssi-sd.o
 common-obj-$(CONFIG_SD) += sd.o
diff --git a/hw/usb-audio.c b/hw/usb-audio.c
new file mode 100644
index 0000000..8fb00b4
--- /dev/null
+++ b/hw/usb-audio.c
@@ -0,0 +1,758 @@
+/*
+ * QEMU USB Net devices
+ *
+ * Copyright (c) 2006 Thomas Sailer
+ * Copyright (c) 2008 Andrzej Zaborowski
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to 
deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu-common.h"
+#include "usb.h"
+#include "hw.h"
+#include "audiodev.h"
+#include "audio/audio.h"
+
+#define USBAUDIO_VENDOR_NUM    0xabcd
+#define USBAUDIO_PRODUCT_NUM   0x1234
+
+#define DEV_CONFIG_VALUE       1 /* The one and only */
+
+/* Descriptor subtypes for AC interfaces */
+#define DST_AC_HEADER          1
+#define DST_AC_INPUT_TERMINAL  2
+#define DST_AC_OUTPUT_TERMINAL 3
+#define DST_AC_FEATURE_UNIT    6
+/* Descriptor subtypes for AS interfaces */
+#define DST_AS_GENERAL         1
+#define DST_AS_FORMAT_TYPE     2
+/* Descriptor subtypes for endpoints */
+#define DST_EP_GENERAL         1
+
+enum usb_audio_strings {
+    STRING_NULL,
+    STRING_MANUFACTURER,
+    STRING_PRODUCT,
+    STRING_SERIALNUMBER,
+    STRING_CONFIG,
+    STRING_USBAUDIO_CONTROL,
+    STRING_INPUT_TERMINAL,
+    STRING_FEATURE_UNIT,
+    STRING_OUTPUT_TERMINAL,
+    STRING_NULL_STREAM,
+    STRING_REAL_STREAM,
+};
+
+static const char * const usb_audio_stringtable[256] = {
+    [STRING_MANUFACTURER]      = "QEMU",
+    [STRING_PRODUCT]           = "QEMU USB Audio",
+    [STRING_SERIALNUMBER]      = "1",
+    [STRING_CONFIG]             = "QEMU USB Audio Configuration",
+    [STRING_USBAUDIO_CONTROL]  = "QEMU USB Audio Device",
+    [STRING_INPUT_TERMINAL]    = "QEMU USB Audio Output Pipe",
+    [STRING_FEATURE_UNIT]       = "QEMU USB Audio Output Volume Control",
+    [STRING_OUTPUT_TERMINAL]    = "QEMU USB Audio Output Terminal",
+    [STRING_NULL_STREAM]        = "QEMU USB Audio Output - Disabled",
+    [STRING_REAL_STREAM]        = "QEMU USB Audio Output - 48 kHz Stereo",
+};
+
+#define U16(x) ((x) & 0xff), (((x) >> 8) & 0xff)
+#define U24(x) U16(x), (((x) >> 16) & 0xff)
+#define U32(x) U24(x), (((x) >> 24) & 0xff)
+
+static const uint8_t qemu_usb_audio_dev_descriptor[] = {
+    0x12,                      /*  u8 bLength; */
+    USB_DT_DEVICE,             /*  u8 bDescriptorType; Device */
+    0x00, 0x02,                        /*  u16 bcdUSB; v2.0 */
+    0x00,                      /*  u8  bDeviceClass; [ interface level ] */
+    0x00,                      /*  u8  bDeviceSubClass; */
+    0x00,                      /*  u8  bDeviceProtocol; [ low/full only ] */
+    0x40,                      /*  u8  bMaxPacketSize0 */
+    U16(USBAUDIO_VENDOR_NUM),  /*  u16 idVendor; */
+    U16(USBAUDIO_PRODUCT_NUM), /*  u16 idProduct; */
+    0x00, 0x00,                        /*  u16 bcdDevice */
+    STRING_MANUFACTURER,       /*  u8  iManufacturer; */
+    STRING_PRODUCT,            /*  u8  iProduct; */
+    STRING_SERIALNUMBER,       /*  u8  iSerialNumber; */
+    0x01,                      /*  u8  bNumConfigurations; */
+};
+
+/*
+ * A Basic Audio Device uses these specific values
+ */
+#define USBAUDIO_PACKET_SIZE    192
+#define USBAUDIO_SAMPLE_RATE    48000
+#define USBAUDIO_PACKET_INTERVAL 1
+
+/*
+ * This basically follows a "Basic Audio Device Headphone Type 1",
+ * except the Terminal Type is set to SPEAKER (0x0301) instead
+ * of HEADPHONE (0x0302)
+ */
+static const uint8_t qemu_usb_audio_config_descriptor[] = {
+    /* Configuration Descriptor */
+    0x09,                      /*  u8  bLength */
+    USB_DT_CONFIG,             /*  u8  bDescriptorType */
+    U16(0),                    /*  le16 wTotalLength, filled at runtime */
+    0x02,                      /*  u8  bNumInterfaces */
+    DEV_CONFIG_VALUE,          /*  u8  bConfigurationValue */
+    STRING_CONFIG,             /*  u8  iConfiguration */
+    0xc0,                      /*  u8  bmAttributes */
+    0x32,                      /*  u8  bMaxPower */
+    /* USB Basic Headphone AC Interface */
+    0x09,                      /*  u8  bLength */
+    USB_DT_INTERFACE,          /*  u8  bDescriptorType */
+    0x00,                      /*  u8  bInterfaceNumber */
+    0x00,                      /*  u8  bAlternateSetting */
+    0x00,                      /*  u8  bNumEndpoints */
+    USB_CLASS_AUDIO,           /*  u8  bInterfaceClass */
+    USB_SUBCLASS_AUDIO_CONTROL,        /*  u8  bInterfaceSubClass */
+    0x04,                      /*  u8  bInterfaceProtocol */
+    STRING_USBAUDIO_CONTROL,   /*  u8  iInterface */
+    /* Headphone Class-Specific AC Interface Header Descriptor */
+    0x09,                      /*  u8  bLength */
+    USB_DT_CS_INTERFACE,       /*  u8  bDescriptorType */
+    DST_AC_HEADER,             /*  u8  bDescriptorSubtype */
+    U16(0x0100),               /* u16  bcdADC */
+    U16(0x2b),                 /* u16  wTotalLength */
+    0x01,                      /*  u8  bInCollection */
+    0x01,                      /*  u8  baInterfaceNr */
+    /* Generic Stereo Input Terminal ID1 Descriptor */
+    0x0c,                      /*  u8  bLength */
+    USB_DT_CS_INTERFACE,       /*  u8  bDescriptorType */
+    DST_AC_INPUT_TERMINAL,     /*  u8  bDescriptorSubtype */
+    0x01,                      /*  u8  bTerminalID */
+    U16(0x0101),               /* u16  wTerminalType */
+    0x00,                      /*  u8  bAssocTerminal */
+    0x02,                      /* u16  bNrChannels */
+    U16(0x0003),               /* u16  wChannelConfig */
+    0x00,                      /*  u8  iChannelNames */
+    STRING_INPUT_TERMINAL,     /*  u8  iTerminal */
+    /* Generic Stereo Feature Unit ID2 Descriptor */
+    0x0d,                      /*  u8  bLength */
+    USB_DT_CS_INTERFACE,       /*  u8  bDescriptorType */
+    DST_AC_FEATURE_UNIT,       /*  u8  bDescriptorSubtype */
+    0x02,                      /*  u8  bUnitID */
+    0x01,                      /*  u8  bSourceID */
+    0x02,                      /*  u8  bControlSize */
+    U16(0x0001),               /* u16  bmaControls(0) */
+    U16(0x0002),               /* u16  bmaControls(1) */
+    U16(0x0002),               /* u16  bmaControls(2) */
+    STRING_FEATURE_UNIT,       /*  u8  iFeature */
+    /* Headphone Ouptut Terminal ID3 Descriptor */
+    0x09,                      /*  u8  bLength */
+    USB_DT_CS_INTERFACE,       /*  u8  bDescriptorType */
+    DST_AC_OUTPUT_TERMINAL,    /*  u8  bDescriptorSubtype */
+    0x03,                      /*  u8  bUnitID */
+    U16(0x0301),               /* u16  wTerminalType (SPEAKER) */
+    0x00,                      /*  u8  bAssocTerminal */
+    0x02,                      /*  u8  bSourceID */
+    STRING_OUTPUT_TERMINAL,    /*  u8  iTerminal */
+    /* Headphone Standard AS Interface Descriptor (Alt Set 0) */
+    0x09,                      /*  u8  bLength */
+    USB_DT_INTERFACE,          /*  u8  bDescriptorType */
+    0x01,                      /*  u8  bInterfaceNumber */
+    0x00,                      /*  u8  bAlternateSetting */
+    0x00,                      /*  u8  bNumEndpoints */
+    USB_CLASS_AUDIO,           /*  u8  bInterfaceClass */
+    USB_SUBCLASS_AUDIO_STREAMING, /* u8 bInterfaceSubclass */
+    0x00,                      /*  u8  bInterfaceProtocol */
+    STRING_NULL_STREAM,                /*  u8  iInterface */
+    /* Headphone Standard AS Interface Descriptor (Alt Set 1) */
+    0x09,                      /*  u8  bLength */
+    USB_DT_INTERFACE,          /*  u8  bDescriptorType */
+    0x01,                      /*  u8  bInterfaceNumber */
+    0x01,                      /*  u8  bAlternateSetting */
+    0x01,                      /*  u8  bNumEndpoins */
+    USB_CLASS_AUDIO,           /*  u8  bInterfaceClass */
+    USB_SUBCLASS_AUDIO_STREAMING, /* u8 bInterfaceSubclass */
+    0x00,                      /*  u8  bInterfaceProtocol */
+    STRING_REAL_STREAM,                /*  u8  iInterface */
+    /* Headphone Class-specific AS General Interface Descriptor */
+    0x07,                      /*  u8  bLength */
+    USB_DT_CS_INTERFACE,       /*  u8  bDescriptorType */
+    DST_AS_GENERAL,            /*  u8  bDescriptorSubtype */
+    0x01,                      /*  u8  bTerminalLink */
+    0x00,                      /*  u8  bDelay */
+    0x01, 0x00,                        /* u16  wFormatTag */
+    /* Headphone Type I Format Type Descriptor */
+    0x0b,                      /*  u8  bLength */
+    USB_DT_CS_INTERFACE,       /*  u8  bDescriptorType */
+    DST_AS_FORMAT_TYPE,                /*  u8  bDescriptorSubtype */
+    0x01,                      /*  u8  bFormatType */
+    0x02,                      /*  u8  bNrChannels */
+    0x02,                      /*  u8  bSubFrameSize */
+    0x10,                      /*  u8  bBitResolution */
+    0x01,                      /*  u8  bSamFreqType */
+    U24(USBAUDIO_SAMPLE_RATE), /* u24  tSamFreq */
+    /* Stereo Headphone Standard AS Audio Data Endpoint Descriptor */
+    0x09,                      /*  u8  bLength */
+    USB_DT_ENDPOINT,           /*  u8  bDescriptorType */
+    0x01,                      /*  u8  bEndpointAddress */
+    0x0d,                      /*  u8  bmAttributes */
+    U16(USBAUDIO_PACKET_SIZE), /* u16  wMaxPacketSize */
+    USBAUDIO_PACKET_INTERVAL,  /*  u8  bInterval */
+    0x00,                      /*  u8  bRefresh */
+    0x00,                      /*  u8  bSynchAddress */
+    /* Stereo Headphone Class-specific AS Audio Data Endpoint Descriptor */
+    0x07,                      /*  u8  bLength */
+    USB_DT_CS_ENDPOINT,                /*  u8  bDescriptorType */
+    DST_EP_GENERAL,            /*  u8  bDescriptorSubtype */
+    0x00,                      /*  u8  bmAttributes */
+    0x00,                      /*  u8  bLockDelayUnits */
+    U16(0x0000),               /* u16  wLockDelay */
+};
+
+/*
+ * A USB audio device supports an arbitrary number of alternate
+ * interface settings for each interface.  Each corresponds to a block
+ * diagram of parameterized blocks.  This can thus refer to things like
+ * number of channels, data rates, or in fact completely different
+ * block diagrams.  Alternative setting 0 is always the null block diagram,
+ * which is used by a disabled device.
+ */
+enum usb_audio_altset {
+    ALTSET_OFF = 0x00,         /* No endpoint */
+    ALTSET_ON  = 0x01,         /* Single endpoint */
+};
+
+/*
+ * Class-specific control requests
+ */
+#define CR_SET_CUR     0x01
+#define CR_GET_CUR     0x81
+#define CR_SET_MIN     0x02
+#define CR_GET_MIN     0x82
+#define CR_SET_MAX     0x03
+#define CR_GET_MAX     0x83
+#define CR_SET_RES     0x04
+#define CR_GET_RES     0x84
+#define CR_SET_MEM     0x05
+#define CR_GET_MEM     0x85
+#define CR_GET_STAT    0xff
+
+/*
+ * Feature Unit Control Selectors
+ */
+#define MUTE_CONTROL                   0x01
+#define VOLUME_CONTROL                 0x02
+#define BASS_CONTROL                   0x03
+#define MID_CONTROL                    0x04
+#define TREBLE_CONTROL                 0x05
+#define GRAPHIC_EQUALIZER_CONTROL      0x06
+#define AUTOMATIC_GAIN_CONTROL         0x07
+#define DELAY_CONTROL                  0x08
+#define BASS_BOOST_CONTROL             0x09
+#define LOUDNESS_CONTROL               0x0a
+
+/*
+ * buffering
+ */
+
+struct streambuf {
+    uint8_t *data;
+    uint32_t size;
+    uint32_t prod;
+    uint32_t cons;
+};
+
+static void streambuf_init(struct streambuf *buf, uint32_t size)
+{
+    qemu_free(buf->data);
+    buf->size = size - (size % USBAUDIO_PACKET_SIZE);
+    buf->data = qemu_malloc(buf->size);
+    buf->prod = 0;
+    buf->cons = 0;
+}
+
+static void streambuf_fini(struct streambuf *buf)
+{
+    qemu_free(buf->data);
+    buf->data = NULL;
+}
+
+static int streambuf_put(struct streambuf *buf, uint8_t *data)
+{
+    uint32_t free = buf->size - (buf->prod - buf->cons);
+
+    if (!free)
+        return 0;
+    assert(free >= USBAUDIO_PACKET_SIZE);
+    memcpy(buf->data + (buf->prod % buf->size), data, USBAUDIO_PACKET_SIZE);
+    buf->prod += USBAUDIO_PACKET_SIZE;
+    return USBAUDIO_PACKET_SIZE;
+}
+
+static uint8_t *streambuf_get(struct streambuf *buf)
+{
+    uint32_t used = buf->prod - buf->cons;
+    uint8_t *data;
+
+    if (!used)
+        return NULL;
+    assert(used >= USBAUDIO_PACKET_SIZE);
+    data = buf->data + (buf->cons % buf->size);
+    buf->cons += USBAUDIO_PACKET_SIZE;
+    return data;
+}
+
+typedef struct USBAudioState {
+    /* qemu interfaces */
+    USBDevice dev;
+    QEMUSoundCard card;
+
+    /* state */
+    struct {
+        enum usb_audio_altset altset;
+        struct audsettings as;
+        SWVoiceOut *voice;
+        bool mute;
+        uint8_t vol[2];
+        struct streambuf buf;
+    } out;
+
+    /* properties */
+    uint32_t debug;
+    uint32_t buffer;
+} USBAudioState;
+
+static void output_callback(void *opaque, int avail)
+{
+    USBAudioState *s = opaque;
+    uint8_t *data;
+
+    for (;;) {
+        if (avail < USBAUDIO_PACKET_SIZE)
+            return;
+        data = streambuf_get(&s->out.buf);
+        if (NULL == data) {
+            return;
+        }
+        AUD_write(s->out.voice, data, USBAUDIO_PACKET_SIZE);
+        avail -= USBAUDIO_PACKET_SIZE;
+    }
+}
+
+static int usb_audio_set_output_altset(USBAudioState *s, int altset)
+{
+    switch (altset) {
+    case ALTSET_OFF:
+        streambuf_init(&s->out.buf, s->buffer);
+        AUD_set_active_out(s->out.voice, false);
+       break;
+    case ALTSET_ON:
+        AUD_set_active_out(s->out.voice, true);
+       break;
+    default:
+       return -1;
+    }
+
+    if (s->debug)
+        fprintf(stderr, "usb-audio: set interface %d\n", altset);
+    s->out.altset = altset;
+    return 0;
+}
+
+/*
+ * Note: we arbitrarily map the volume control range onto -inf..+8 dB
+ */
+#define ATTRIB_ID(cs, attrib, idif)    \
+    (((cs) << 24) | ((attrib) << 16) | (idif))
+
+static int usb_audio_get_control(USBAudioState *s, uint8_t attrib,
+                                uint16_t cscn, uint16_t idif,
+                                int length, uint8_t *data)
+{
+    uint8_t cs = cscn >> 8;
+    uint8_t cn = cscn - 1;     /* -1 for the non-present master control */
+    uint32_t aid = ATTRIB_ID(cs, attrib, idif);
+    int ret = USB_RET_STALL;
+
+    switch (aid) {
+    case ATTRIB_ID(MUTE_CONTROL, CR_GET_CUR, 0x0200):
+       data[0] = s->out.mute;
+       ret = 1;
+       break;
+    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200):
+       if (cn < 2) {
+           uint16_t vol = (s->out.vol[cn] * 0x8800 + 127) / 255 + 0x8000;
+           data[0] = vol;
+           data[1] = vol >> 8;
+           ret = 2;
+       }
+       break;
+    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MIN, 0x0200):
+       if (cn < 2) {
+           data[0] = 0x01;
+           data[1] = 0x80;
+           ret = 2;
+       }
+       break;
+    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MAX, 0x0200):
+       if (cn < 2) {
+           data[0] = 0x00;
+           data[1] = 0x08;
+           ret = 2;
+       }
+       break;
+    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_RES, 0x0200):
+       if (cn < 2) {
+           data[0] = 0x88;
+           data[1] = 0x00;
+           ret = 2;
+       }
+       break;
+    }
+
+    return ret;
+}
+static int usb_audio_set_control(USBAudioState *s, uint8_t attrib,
+                                uint16_t cscn, uint16_t idif,
+                                int length, uint8_t *data)
+{
+    uint8_t cs = cscn >> 8;
+    uint8_t cn = cscn - 1;     /* -1 for the non-present master control */
+    uint32_t aid = ATTRIB_ID(cs, attrib, idif);
+    int ret = USB_RET_STALL;
+    bool set_vol = false;
+
+    switch (aid) {
+    case ATTRIB_ID(MUTE_CONTROL, CR_SET_CUR, 0x0200):
+       s->out.mute = data[0] & 1;
+       set_vol = true;
+       ret = 0;
+       break;
+    case ATTRIB_ID(VOLUME_CONTROL, CR_SET_CUR, 0x0200):
+       if (cn < 2) {
+           uint16_t vol = data[0] + (data[1] << 8);
+
+            if (s->debug)
+                fprintf(stderr, "usb-audio: vol %04x\n", (uint16_t)vol);
+
+           vol -= 0x8000;
+           vol = (vol * 255 + 0x4400) / 0x8800;
+           if (vol > 255)
+               vol = 255;
+
+           s->out.vol[cn] = vol;
+           set_vol = true;
+           ret = 0;
+       }
+       break;
+    }
+
+    if (set_vol) {
+        if (s->debug)
+            fprintf(stderr, "usb-audio: mute %d, lvol %3d, rvol %3d\n",
+                    s->out.mute, s->out.vol[0], s->out.vol[1]);
+       AUD_set_volume_out(s->out.voice, s->out.mute, s->out.vol[0], 
s->out.vol[1]);
+    }
+
+    return ret;
+}
+
+static int usb_audio_handle_control(USBDevice *dev, int request, int value,
+                int index, int length, uint8_t *data)
+{
+    USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
+    int ret = 0;
+
+    if (s->debug)
+        fprintf(stderr, "usb-audio: control transaction: "
+                "request 0x%04x value 0x%04x index 0x%04x length 0x%04x\n",
+                request, value, index, length);
+
+    switch(request) {
+    case DeviceRequest | USB_REQ_GET_STATUS:
+        data[0] = (1 << USB_DEVICE_SELF_POWERED) |
+                (dev->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP);
+        data[1] = 0x00;
+        ret = 2;
+        break;
+
+    case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
+        if (value == USB_DEVICE_REMOTE_WAKEUP) {
+            dev->remote_wakeup = 0;
+        } else {
+            goto fail;
+        }
+        ret = 0;
+        break;
+
+    case DeviceOutRequest | USB_REQ_SET_FEATURE:
+        if (value == USB_DEVICE_REMOTE_WAKEUP) {
+            dev->remote_wakeup = 1;
+        } else {
+            goto fail;
+        }
+        ret = 0;
+        break;
+
+    case DeviceOutRequest | USB_REQ_SET_ADDRESS:
+        dev->addr = value;
+        ret = 0;
+        break;
+
+    case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
+        switch(value >> 8) {
+        case USB_DT_DEVICE:
+            ret = sizeof(qemu_usb_audio_dev_descriptor);
+            memcpy(data, qemu_usb_audio_dev_descriptor, ret);
+            break;
+
+        case USB_DT_CONFIG:
+            switch (value & 0xff) {
+            case 0:
+                ret = sizeof(qemu_usb_audio_config_descriptor);
+                memcpy(data, qemu_usb_audio_config_descriptor, ret);
+                data[2] = ret & 0xff;
+                data[3] = ret >> 8;
+                break;
+            default:
+                goto fail;
+            }
+            break;
+
+        case USB_DT_STRING:
+            switch (value & 0xff) {
+            case 0:
+                /* language ids */
+                data[0] = 4;
+                data[1] = 3;
+                data[2] = 0x09;
+                data[3] = 0x04;
+                ret = 4;
+                break;
+
+            default:
+                if (usb_audio_stringtable[value & 0xff]) {
+                    ret = set_usb_string(data,
+                                    usb_audio_stringtable[value & 0xff]);
+                    break;
+                }
+                if (s->debug)
+                    fprintf(stderr, "usb-audio: fail: string 0x%x doesn't 
exist\n",
+                            value & 0xff);
+                goto fail;
+            }
+            break;
+        default:
+            if (s->debug)
+                fprintf(stderr, "usb-audio: fail: unknown descriptor 0x%x\n",
+                        value >> 8);
+            goto fail;
+        }
+        break;
+
+    case DeviceRequest | USB_REQ_GET_CONFIGURATION:
+        data[0] = DEV_CONFIG_VALUE;
+        ret = 1;
+        break;
+
+    case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
+        switch (value & 0xff) {
+        case DEV_CONFIG_VALUE:
+            break;
+        default:
+            goto fail;
+        }
+        ret = 0;
+        break;
+
+    case DeviceRequest | USB_REQ_GET_INTERFACE:
+        data[0] = 0;
+        ret = 1;
+        break;
+
+    case DeviceOutRequest | USB_REQ_SET_INTERFACE:
+        ret = 0;
+        break;
+
+    case InterfaceRequest | USB_REQ_GET_INTERFACE:
+       if (index != 0x01) {
+           goto fail;
+        }
+        data[0] = s->out.altset;
+        ret = 1;
+        break;
+
+    case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
+       if (index != 0x01) {
+           goto fail;
+        }
+       if (usb_audio_set_output_altset(s, value)) {
+           goto fail;
+        }
+        ret = 0;
+        break;
+
+    case ClassInterfaceRequest | CR_GET_CUR:
+    case ClassInterfaceRequest | CR_GET_MIN:
+    case ClassInterfaceRequest | CR_GET_MAX:
+    case ClassInterfaceRequest | CR_GET_RES:
+       ret = usb_audio_get_control(s, request & 0xff, value, index,
+                                   length, data);
+       if (ret < 0) {
+            if (s->debug)
+                fprintf(stderr, "usb-audio: fail: get control\n");
+           goto fail;
+        }
+       break;
+
+    case ClassInterfaceOutRequest | CR_SET_CUR:
+    case ClassInterfaceOutRequest | CR_SET_MIN:
+    case ClassInterfaceOutRequest | CR_SET_MAX:
+    case ClassInterfaceOutRequest | CR_SET_RES:
+       ret = usb_audio_set_control(s, request & 0xff, value, index,
+                                   length, data);
+       if (ret < 0) {
+            if (s->debug)
+                fprintf(stderr, "usb-audio: fail: set control\n");
+           goto fail;
+        }
+       break;
+
+    default:
+    fail:
+        if (s->debug)
+            fprintf(stderr, "usb-audio: failed control transaction: "
+                    "request 0x%04x value 0x%04x index 0x%04x length 0x%04x\n",
+                    request, value, index, length);
+        ret = USB_RET_STALL;
+        break;
+    }
+    return ret;
+}
+
+
+static void usb_audio_handle_reset(USBDevice *dev)
+{
+    USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
+
+    if (s->debug)
+        fprintf(stderr, "usb-audio: reset\n");
+    usb_audio_set_output_altset(s, ALTSET_OFF);
+}
+
+static int usb_audio_handle_dataout(USBAudioState *s, USBPacket *p)
+{
+    int rc;
+
+    if (s->out.altset == ALTSET_OFF)
+        return USB_RET_STALL;
+
+    rc = streambuf_put(&s->out.buf, p->data);
+    if (rc < p->len && s->debug > 1) {
+        fprintf(stderr, "usb-audio: output overrun (%d bytes)\n", p->len - rc);
+    }
+
+    return 0;
+}
+
+static int usb_audio_handle_data(USBDevice *dev, USBPacket *p)
+{
+    USBAudioState *s = (USBAudioState *) dev;
+    int ret = 0;
+
+    switch(p->pid) {
+    case USB_TOKEN_OUT:
+        switch (p->devep) {
+        case 1:
+            ret = usb_audio_handle_dataout(s, p);
+            break;
+        default:
+            goto fail;
+        }
+        break;
+
+    default:
+    fail:
+        ret = USB_RET_STALL;
+       break;
+    }
+    if (ret == USB_RET_STALL && s->debug)
+        fprintf(stderr, "usb-audio: failed data transaction: "
+                        "pid 0x%x ep 0x%x len 0x%x\n",
+                        p->pid, p->devep, p->len);
+    return ret;
+}
+
+static void usb_audio_handle_destroy(USBDevice *dev)
+{
+    USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
+
+    if (s->debug)
+        fprintf(stderr, "usb-audio: destroy\n");
+
+    usb_audio_set_output_altset(s, ALTSET_OFF);
+    AUD_close_out(&s->card, s->out.voice);
+    AUD_remove_card(&s->card);
+
+    streambuf_fini(&s->out.buf);
+}
+
+static int usb_audio_initfn(USBDevice *dev)
+{
+    USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
+
+    s->dev.speed  = USB_SPEED_FULL;
+    s->dev.opaque = s;
+    AUD_register_card("usb-audio", &s->card);
+
+    s->out.altset        = ALTSET_OFF;
+    s->out.mute          = false;
+    s->out.vol[0]        = 240;        /* 0 dB */
+    s->out.vol[1]        = 240;        /* 0 dB */
+    s->out.as.freq       = USBAUDIO_SAMPLE_RATE;
+    s->out.as.nchannels  = 2;
+    s->out.as.fmt        = AUD_FMT_S16;
+    s->out.as.endianness = 0;
+    streambuf_init(&s->out.buf, s->buffer);
+
+    s->out.voice = AUD_open_out(&s->card, s->out.voice, "usb-audio",
+                                s, output_callback, &s->out.as);
+    AUD_set_volume_out(s->out.voice, s->out.mute, s->out.vol[0], 
s->out.vol[1]);
+    AUD_set_active_out(s->out.voice, 0);
+    return 0;
+}
+
+static struct USBDeviceInfo usb_audio_info = {
+    .product_desc   = "QEMU USB Audio Interface",
+    .usbdevice_name = "audio",
+    .qdev.name      = "usb-audio",
+    .qdev.size      = sizeof(USBAudioState),
+    .init           = usb_audio_initfn,
+    .handle_packet  = usb_generic_handle_packet,
+    .handle_reset   = usb_audio_handle_reset,
+    .handle_control = usb_audio_handle_control,
+    .handle_data    = usb_audio_handle_data,
+    .handle_destroy = usb_audio_handle_destroy,
+    .qdev.props = (Property[]) {
+        DEFINE_PROP_UINT32("debug", USBAudioState, debug, 0),
+        DEFINE_PROP_UINT32("buffer", USBAudioState, buffer, 8 * 
USBAUDIO_PACKET_SIZE),
+        DEFINE_PROP_END_OF_LIST(),
+    }
+};
+
+static void usb_audio_register_devices(void)
+{
+    usb_qdev_register(&usb_audio_info);
+}
+
+device_init(usb_audio_register_devices)
diff --git a/hw/usb-net.c b/hw/usb-net.c
index 70f9263..61469f3 100644
--- a/hw/usb-net.c
+++ b/hw/usb-net.c
@@ -68,9 +68,6 @@ enum usbstring_idx {
 #define USB_CDC_UNION_TYPE             0x06    /* union_desc */
 #define USB_CDC_ETHERNET_TYPE          0x0f    /* ether_desc */
 
-#define USB_DT_CS_INTERFACE            0x24
-#define USB_DT_CS_ENDPOINT             0x25
-
 #define USB_CDC_SEND_ENCAPSULATED_COMMAND      0x00
 #define USB_CDC_GET_ENCAPSULATED_RESPONSE      0x01
 #define USB_CDC_REQ_SET_LINE_CODING            0x20
diff --git a/hw/usb.h b/hw/usb.h
index 00d2802..111aa39 100644
--- a/hw/usb.h
+++ b/hw/usb.h
@@ -67,6 +67,11 @@
 #define USB_CLASS_APP_SPEC             0xfe
 #define USB_CLASS_VENDOR_SPEC          0xff
 
+#define USB_SUBCLASS_UNDEFINED         0
+#define USB_SUBCLASS_AUDIO_CONTROL     1
+#define USB_SUBCLASS_AUDIO_STREAMING   2
+#define USB_SUBCLASS_AUDIO_MIDISTREAMING 3
+
 #define USB_DIR_OUT                    0
 #define USB_DIR_IN                     0x80
 
@@ -116,6 +121,8 @@
 #define USB_DT_STRING                  0x03
 #define USB_DT_INTERFACE               0x04
 #define USB_DT_ENDPOINT                        0x05
+#define USB_DT_CS_INTERFACE            0x24
+#define USB_DT_CS_ENDPOINT             0x25
 
 #define USB_ENDPOINT_XFER_CONTROL      0
 #define USB_ENDPOINT_XFER_ISOC         1
-- 
1.7.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]