qemu-devel
[Top][All Lists]
Advanced

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

[Qemu-devel] [PATCH] [RFC] Add support for a USB audio device model


From: H. Peter Anvin
Subject: [Qemu-devel] [PATCH] [RFC] Add support for a USB audio device model
Date: Fri, 10 Sep 2010 14:47:56 -0700

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 <address@hidden>
---
 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




reply via email to

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