All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] [RFC] Add support for a USB audio device model
@ 2010-09-10 21:47 ` H. Peter Anvin
  0 siblings, 0 replies; 36+ messages in thread
From: H. Peter Anvin @ 2010-09-10 21:47 UTC (permalink / raw)
  To: qemu-devel, av1474, kvm; +Cc: H. Peter Anvin, H. Peter Anvin

I discovered that none of the audio device models supported by current
Qemu/KVM appear to be supported out of the box on Win7 64 bit (AC97
works fine on 32 bit).  The most logical ways to fix that would be to
add a long-term supportable audio device model.  Intel HD Audio and
USB Audio seemed like the most reasonable options, but I opted for USB
Audio for a few reasons:

a) as an external plugin device, it is more likely that it will be
   supported across a large number of guest operating systems for a
   very long time.
b) as a vendor-independent class device, it is supported by the OS
   developers directly without any of the potential strange issues
   involved with relying on a vendor driver.
c) USB Audio is highly parameterizable, which would make it possible
   to export all kinds of different audio models to the guest.  Note
   that due to the software-scheduled nature of [UOE]HCI, however,
   surround sound support (more than 4 channels) would require a High
   Speed device model with EHCI, or the guest OS will reject it as
   unschedulable (since it will exceed the schedulable bandwidth of a
   Full Speed USB bus.)

This patch adds support for a USB audio device model.  It is based
mostly on the "USB Basic Audio Device Specification", which consists
of a set of profiles for the much more general "USB Audio
Specification".  In accordance with the above profile, it supports
48 kHz, 16-bit stereo only.

It does work, "so far, so good".  However, it doesn't really provide a
fully acceptable audio quality, because it suffers from a fundamental
problem -- the rate of data provided by UHCI may not match the
consumption rate of the sink.  I don't understand the Qemu audio
subsystem well enough to know how to deal with that, which is why I
would like comments and/or help (I really have spent way more time
than I should on this already).  The full USB Audio specification has
an optional concept of synchronization packets (a reverse channel
which carry data rate information to be used for rate matching); this
is specified in section 5.12.4.2 of the USB 2.0 specification (or
5.10.4.2 of USB 1.1).  However, it isn't very clear to me how to get
the relevant information out of the Qemu audio system.

>From a device model point of view, a data packet will arrive once per
1 ms of USB time; this can be the time basis.  Presumably this needs
to be used to compare against the number of samples actually consumed
to construct the functional sample rate on the USB clock, as long as
that information can be acquired.

Not-yet-signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
---
 Makefile.objs  |    1 +
 arch_init.c    |   24 ++-
 configure      |    6 +-
 create_config  |    4 +-
 hw/audiodev.h  |    4 +
 hw/pc.c        |   14 +-
 hw/pc_piix.c   |    4 +-
 hw/usb-audio.c |  702 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/usb-net.c   |    3 -
 hw/usb.h       |   13 +-
 qemu-common.h  |    5 +
 sysemu.h       |   11 +-
 12 files changed, 764 insertions(+), 27 deletions(-)
 create mode 100644 hw/usb-audio.c

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;
 };
 
-- 
1.7.2.1


^ permalink raw reply related	[flat|nested] 36+ messages in thread

end of thread, other threads:[~2010-10-14 17:44 UTC | newest]

Thread overview: 36+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-09-10 21:47 [PATCH] [RFC] Add support for a USB audio device model H. Peter Anvin
2010-09-10 21:47 ` [Qemu-devel] " H. Peter Anvin
2010-09-11  0:28 ` malc
2010-09-11  0:28   ` [Qemu-devel] " malc
2010-09-11  1:08   ` H. Peter Anvin
2010-09-11  1:08     ` [Qemu-devel] " H. Peter Anvin
2010-09-11  2:47     ` H. Peter Anvin
2010-09-11  2:47       ` H. Peter Anvin
2010-09-11  3:05       ` H. Peter Anvin
2010-09-11  3:05         ` H. Peter Anvin
2010-09-11 13:53         ` malc
2010-09-11 17:09           ` H. Peter Anvin
2010-09-11 13:14     ` Alexander Graf
2010-09-11 13:14       ` [Qemu-devel] " Alexander Graf
2010-09-11 18:29       ` H. Peter Anvin
2010-09-11 18:29         ` [Qemu-devel] " H. Peter Anvin
2010-09-11  7:41 ` Stefan Hajnoczi
2010-09-11  7:41   ` [Qemu-devel] " Stefan Hajnoczi
2010-09-12  5:20   ` H. Peter Anvin
2010-09-12  5:20     ` [Qemu-devel] " H. Peter Anvin
2010-09-13 20:53 ` Amos Kong
2010-09-13 20:53   ` [Qemu-devel] " Amos Kong
2010-09-13 21:04   ` H. Peter Anvin
2010-09-13 21:04     ` [Qemu-devel] " H. Peter Anvin
2010-09-14  1:37     ` Amos Kong
2010-09-14  1:37       ` [Qemu-devel] " Amos Kong
2010-09-14  5:56       ` H. Peter Anvin
2010-09-14  5:56         ` [Qemu-devel] " H. Peter Anvin
2010-10-14 13:51         ` Mike Snitzer
2010-10-14 13:51           ` [Qemu-devel] " Mike Snitzer
2010-10-14 15:40           ` H. Peter Anvin
2010-10-14 15:40             ` [Qemu-devel] " H. Peter Anvin
2010-10-14 16:18             ` Alon Levy
2010-10-14 17:43               ` H. Peter Anvin
2010-09-13 21:07   ` H. Peter Anvin
2010-09-13 21:07     ` [Qemu-devel] " H. Peter Anvin

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.