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 <h...@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