From patchwork Fri Sep 10 21:47:56 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "H. Peter Anvin" X-Patchwork-Id: 170452 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter1.kernel.org (8.14.4/8.14.3) with ESMTP id o8ALmFRk012469 for ; Fri, 10 Sep 2010 21:48:15 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751438Ab0IJVsL (ORCPT ); Fri, 10 Sep 2010 17:48:11 -0400 Received: from mga14.intel.com ([143.182.124.37]:16205 "EHLO mga14.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751011Ab0IJVsJ (ORCPT ); Fri, 10 Sep 2010 17:48:09 -0400 Received: from azsmga001.ch.intel.com ([10.2.17.19]) by azsmga102.ch.intel.com with ESMTP; 10 Sep 2010 14:48:00 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="4.56,348,1280732400"; d="scan'208";a="323332436" Received: from anacreon.sc.intel.com ([143.183.85.170]) by azsmga001.ch.intel.com with ESMTP; 10 Sep 2010 14:48:00 -0700 From: "H. Peter Anvin" To: qemu-devel@nongnu.org, av1474@comtv.ru, kvm@vger.kernel.org Cc: "H. Peter Anvin" , "H. Peter Anvin" Subject: [PATCH] [RFC] Add support for a USB audio device model Date: Fri, 10 Sep 2010 14:47:56 -0700 Message-Id: <1284155276-14959-1-git-send-email-hpa@linux.intel.com> X-Mailer: git-send-email 1.7.2.1 Sender: kvm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: kvm@vger.kernel.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.3 (demeter1.kernel.org [140.211.167.41]); Fri, 10 Sep 2010 21:48:15 +0000 (UTC) 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; };