diff mbox

[RFC] Add support for a USB audio device model

Message ID 1284155276-14959-1-git-send-email-hpa@linux.intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

H. Peter Anvin Sept. 10, 2010, 9:47 p.m. UTC
None
diff mbox

Patch

diff --git a/Makefile.objs b/Makefile.objs
index dbee210..8b5b908 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -245,6 +245,7 @@  sound-obj-$(CONFIG_AC97) += ac97.o
 sound-obj-$(CONFIG_ADLIB) += fmopl.o adlib.o
 sound-obj-$(CONFIG_GUS) += gus.o gusemu_hal.o gusemu_mixer.o
 sound-obj-$(CONFIG_CS4231A) += cs4231a.o
+sound-obj-$(CONFIG_USB_AUDIO) += usb-audio.o
 
 adlib.o fmopl.o: QEMU_CFLAGS += -DBUILD_Y8950=0
 hw-obj-$(CONFIG_SOUND) += $(sound-obj-y)
diff --git a/arch_init.c b/arch_init.c
index e468c0c..68e5643 100644
--- a/arch_init.c
+++ b/arch_init.c
@@ -430,7 +430,7 @@  struct soundhw soundhw[] = {
         "pcspk",
         "PC speaker",
         0,
-        1,
+        BUS_ISA,
         { .init_isa = pcspk_audio_init }
     },
 #endif
@@ -440,7 +440,7 @@  struct soundhw soundhw[] = {
         "sb16",
         "Creative Sound Blaster 16",
         0,
-        1,
+        BUS_ISA,
         { .init_isa = SB16_init }
     },
 #endif
@@ -450,7 +450,7 @@  struct soundhw soundhw[] = {
         "cs4231a",
         "CS4231A",
         0,
-        1,
+        BUS_ISA,
         { .init_isa = cs4231a_init }
     },
 #endif
@@ -464,7 +464,7 @@  struct soundhw soundhw[] = {
         "Yamaha YM3812 (OPL2)",
 #endif
         0,
-        1,
+        BUS_ISA,
         { .init_isa = Adlib_init }
     },
 #endif
@@ -474,7 +474,7 @@  struct soundhw soundhw[] = {
         "gus",
         "Gravis Ultrasound GF1",
         0,
-        1,
+        BUS_ISA,
         { .init_isa = GUS_init }
     },
 #endif
@@ -484,7 +484,7 @@  struct soundhw soundhw[] = {
         "ac97",
         "Intel 82801AA AC97 Audio",
         0,
-        0,
+        BUS_PCI,
         { .init_pci = ac97_init }
     },
 #endif
@@ -494,11 +494,21 @@  struct soundhw soundhw[] = {
         "es1370",
         "ENSONIQ AudioPCI ES1370",
         0,
-        0,
+        BUS_PCI,
         { .init_pci = es1370_init }
     },
 #endif
 
+#ifdef CONFIG_USB_AUDIO
+    {
+        "usb",
+        "USB Audio",
+        0,
+        BUS_USB,
+        { .init_usb = usb_audio_soundhw_init }
+    },
+#endif
+
 #endif /* HAS_AUDIO_CHOICE */
 
     { NULL, NULL, 0, 0, { NULL } }
diff --git a/configure b/configure
index 8228c1c..4fcb829 100755
--- a/configure
+++ b/configure
@@ -71,8 +71,8 @@  sparc_cpu=""
 cross_prefix=""
 cc="gcc"
 audio_drv_list=""
-audio_card_list="ac97 es1370 sb16"
-audio_possible_cards="ac97 es1370 sb16 cs4231a adlib gus"
+audio_card_list="ac97 es1370 sb16 usb-audio"
+audio_possible_cards="ac97 es1370 sb16 cs4231a adlib gus usb-audio"
 block_drv_whitelist=""
 host_cc="gcc"
 ar="ar"
@@ -2414,7 +2414,7 @@  if test "$vde" = "yes" ; then
 fi
 for card in $audio_card_list; do
     def=CONFIG_`echo $card | tr '[:lower:]' '[:upper:]'`
-    echo "$def=y" >> $config_host_mak
+    echo ${def//-/_}=y >> $config_host_mak
 done
 echo "CONFIG_AUDIO_DRIVERS=$audio_drv_list" >> $config_host_mak
 for drv in $audio_drv_list; do
diff --git a/create_config b/create_config
index 0098e68..1caa25b 100755
--- a/create_config
+++ b/create_config
@@ -25,7 +25,7 @@  case $line in
  CONFIG_AUDIO_DRIVERS=*)
     drivers=${line#*=}
     echo "#define CONFIG_AUDIO_DRIVERS \\"
-    for drv in $drivers; do
+    for drv in ${drivers//-/_}; do
       echo "    &${drv}_audio_driver,\\"
     done
     echo ""
@@ -39,10 +39,12 @@  case $line in
     ;;
  CONFIG_*=y) # configuration
     name=${line%=*}
+    name=${name//-/_}
     echo "#define $name 1"
     ;;
  CONFIG_*=*) # configuration
     name=${line%=*}
+    name=${name//-/_}
     value=${line#*=}
     echo "#define $name $value"
     ;;
diff --git a/hw/audiodev.h b/hw/audiodev.h
index 39a729b..24daef4 100644
--- a/hw/audiodev.h
+++ b/hw/audiodev.h
@@ -15,3 +15,7 @@  int ac97_init(PCIBus *buf);
 
 /* cs4231a.c */
 int cs4231a_init(qemu_irq *pic);
+
+/* usb-audio.c */
+int usb_audio_soundhw_init(USBBus *bus);
+
diff --git a/hw/pc.c b/hw/pc.c
index 9c08573..c456189 100644
--- a/hw/pc.c
+++ b/hw/pc.c
@@ -756,13 +756,21 @@  void pc_audio_init (PCIBus *pci_bus, qemu_irq *pic)
 
     for (c = soundhw; c->name; ++c) {
         if (c->enabled) {
-            if (c->isa) {
+	    switch (c->bus) {
+	    case BUS_ISA:
                 c->init.init_isa(pic);
-            } else {
+		break;
+	    case BUS_PCI:
                 if (pci_bus) {
                     c->init.init_pci(pci_bus);
                 }
-            }
+		break;
+	    case BUS_USB:
+		if (usb_enabled) {
+                    c->init.init_usb(NULL /* FIXME */);
+                }
+		break;
+	    }
         }
     }
 }
diff --git a/hw/pc_piix.c b/hw/pc_piix.c
index 3d07ce5..5c98b63 100644
--- a/hw/pc_piix.c
+++ b/hw/pc_piix.c
@@ -167,8 +167,6 @@  static void pc_init1(ram_addr_t ram_size,
         }
     }
 
-    pc_audio_init(pci_enabled ? pci_bus : NULL, isa_irq);
-
     pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device,
                  idebus[0], idebus[1], floppy_controller, rtc_state);
 
@@ -176,6 +174,8 @@  static void pc_init1(ram_addr_t ram_size,
         usb_uhci_piix3_init(pci_bus, piix3_devfn + 2);
     }
 
+    pc_audio_init(pci_enabled ? pci_bus : NULL, isa_irq);
+
     if (pci_enabled && acpi_enabled) {
         uint8_t *eeprom_buf = qemu_mallocz(8 * 256); /* XXX: make this persistent */
         i2c_bus *smbus;
diff --git a/hw/usb-audio.c b/hw/usb-audio.c
new file mode 100644
index 0000000..d4cf488
--- /dev/null
+++ b/hw/usb-audio.c
@@ -0,0 +1,702 @@ 
+/*
+ * 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[] = {
+    [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(0x71),			/*  le16 wTotalLength */
+    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
+
+typedef struct USBAudioState {
+    USBDevice dev;
+    QEMUSoundCard card;
+    enum usb_audio_altset outaltset;
+    SWVoiceOut *outvoice;
+    bool mute;
+    uint8_t vol[2];
+} USBAudioState;
+
+static void output_callback(void *opaque, int avail)
+{
+    (void)opaque;
+    (void)avail;
+}
+
+static int usb_audio_set_output_altset(USBAudioState *s, int altset)
+{
+    struct audsettings as;
+
+    if (altset == s->outaltset)
+	return 0;
+
+    switch (altset) {
+    case ALTSET_OFF:
+	as.nchannels = 0;
+	break;
+
+    case ALTSET_ON:
+	as.freq = USBAUDIO_SAMPLE_RATE;
+	as.nchannels = 2;
+	as.fmt = AUD_FMT_S16;
+	as.endianness = 0;
+	break;
+
+    default:
+	return -1;
+    }
+
+    if (s->outvoice) {
+	AUD_close_out (&s->card, s->outvoice);
+	s->outvoice = NULL;
+    }
+
+    if (as.nchannels) {
+	s->outvoice = AUD_open_out (&s->card, s->outvoice, "usb-audio",
+				    s, output_callback, &as);
+	AUD_set_volume_out (s->outvoice, s->mute, s->vol[0], s->vol[1]);
+	AUD_set_active_out (s->outvoice, 1);
+    }
+
+    fprintf(stderr, "usb-audio: set interface %d\n", altset);
+
+    s->outaltset = 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->mute;
+	ret = 1;
+	break;
+    case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200):
+	if (cn < 2) {
+	    uint16_t vol = (s->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->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);
+
+	    fprintf(stderr, "usb-audio: vol %04x\n", (uint16_t)vol);
+
+	    vol -= 0x8000;
+	    vol = (vol * 255 + 0x4400) / 0x8800;
+	    if (vol > 255)
+		vol = 255;
+
+	    s->vol[cn] = vol;
+	    set_vol = true;
+	    ret = 0;
+	}
+	break;
+    }
+
+    if (set_vol) {
+	fprintf(stderr, "usb-audio: mute %d, lvol %3d, rvol %3d\n",
+		s->mute, s->vol[0], s->vol[1]);
+	AUD_set_volume_out (s->outvoice, s->mute, s->vol[0], s->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;
+
+    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);
+                break;
+
+            default:
+                goto fail;
+            }
+
+            data[2] = ret & 0xff;
+            data[3] = ret >> 8;
+            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;
+                }
+
+                goto fail;
+            }
+            break;
+
+        default:
+            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->outaltset;
+        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)
+	    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)
+	    goto fail;
+	break;
+
+    default:
+    fail:
+        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);
+
+    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 rv;
+
+    /*
+     * Note: allow for the case where the USB HCI model's notion of time
+     * is faster than the audio device, which means we'll eventually overrun
+     * the output buffer.  Drop those samples, sorry.  Can we do better?
+     */
+    rv = AUD_write (s->outvoice, p->data, p->len);
+
+    if (rv < p->len)
+	fprintf(stderr, "usb-audio: dropped %d samples\n", (p->len - rv) >> 2);
+
+    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)
+        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);
+
+    fprintf(stderr, "usb-audio: destroy\n");
+
+    usb_audio_set_output_altset(s, ALTSET_OFF);
+}
+
+static int usb_audio_initfn(USBDevice *dev)
+{
+    USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev);
+
+    s->dev.speed  = USB_SPEED_FULL;
+    s->outvoice   = NULL;
+    s->outaltset  = ALTSET_OFF;
+    s->mute       = false;
+    s->vol[0]     = 240;	/* 0 dB */
+    s->vol[1]     = 240;	/* 0 dB */
+    return 0;
+}
+
+static USBDevice *usb_audio_init(USBBus *bus)
+{
+    USBDevice *dev;
+    USBAudioState *s;
+
+    dev = usb_create_simple(bus, "usb-audio");
+    s = DO_UPCAST(struct USBAudioState, dev, dev);
+    s->dev.opaque = s;
+
+    AUD_register_card("usb-audio", &s->card);
+
+    return dev;
+}
+
+/* Called by "-soundhw usb" */
+int usb_audio_soundhw_init(USBBus *bus)
+{
+    return usb_audio_init(bus) ? 0 : -1;
+}
+
+/* Called by "-usb audio" */
+static USBDevice *usb_audio_usb_init(const char *cmdline)
+{
+    USBDevice *dev = usb_audio_init(NULL /* FIXME */);
+
+    return dev;
+}
+
+static struct USBDeviceInfo usb_audio_info = {
+    .product_desc   = "QEMU USB Audio Interface",
+    .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,
+    .usbdevice_name = "audio",
+    .usbdevice_init = usb_audio_usb_init,
+};
+
+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..40fd9bd 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,18 +121,14 @@ 
 #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
 #define USB_ENDPOINT_XFER_BULK		2
 #define USB_ENDPOINT_XFER_INT		3
 
-typedef struct USBBus USBBus;
-typedef struct USBPort USBPort;
-typedef struct USBDevice USBDevice;
-typedef struct USBDeviceInfo USBDeviceInfo;
-typedef struct USBPacket USBPacket;
-
 /* definition of a USB device */
 struct USBDevice {
     DeviceState qdev;
diff --git a/qemu-common.h b/qemu-common.h
index dfd3dc0..650ab17 100644
--- a/qemu-common.h
+++ b/qemu-common.h
@@ -229,6 +229,11 @@  typedef struct I2SCodec I2SCodec;
 typedef struct SSIBus SSIBus;
 typedef struct EventNotifier EventNotifier;
 typedef struct VirtIODevice VirtIODevice;
+typedef struct USBBus USBBus;
+typedef struct USBPort USBPort;
+typedef struct USBDevice USBDevice;
+typedef struct USBDeviceInfo USBDeviceInfo;
+typedef struct USBPacket USBPacket;
 
 typedef uint64_t pcibus_t;
 
diff --git a/sysemu.h b/sysemu.h
index 98bd47d..4bf8a20 100644
--- a/sysemu.h
+++ b/sysemu.h
@@ -172,14 +172,21 @@  extern CharDriverState *parallel_hds[MAX_PARALLEL_PORTS];
 #define TFR(expr) do { if ((expr) != -1) break; } while (errno == EINTR)
 
 #ifdef HAS_AUDIO
+enum bus_type {
+    BUS_PCI,
+    BUS_ISA,
+    BUS_USB,
+};
+
 struct soundhw {
     const char *name;
     const char *descr;
     int enabled;
-    int isa;
+    enum bus_type bus;
     union {
-        int (*init_isa) (qemu_irq *pic);
         int (*init_pci) (PCIBus *bus);
+        int (*init_isa) (qemu_irq *pic);
+        int (*init_usb) (USBBus *bus);
     } init;
 };