From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from [140.186.70.92] (port=42475 helo=eggs.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1PB8Uf-0005XW-D2 for qemu-devel@nongnu.org; Wed, 27 Oct 2010 12:04:57 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1PB8Uc-0007Jc-Cp for qemu-devel@nongnu.org; Wed, 27 Oct 2010 12:04:53 -0400 Received: from mx1.redhat.com ([209.132.183.28]:35679) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1PB8Ub-0007JX-SM for qemu-devel@nongnu.org; Wed, 27 Oct 2010 12:04:50 -0400 From: Gerd Hoffmann Date: Wed, 27 Oct 2010 18:04:34 +0200 Message-Id: <1288195475-3807-6-git-send-email-kraxel@redhat.com> In-Reply-To: <1288195475-3807-1-git-send-email-kraxel@redhat.com> References: <1288195475-3807-1-git-send-email-kraxel@redhat.com> Subject: [Qemu-devel] [PATCH 5/6] Add support for a USB audio device model List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org Cc: "H. Peter Anvin" , Gerd Hoffmann From: H. Peter Anvin This brings a usb audio device to qemu. Output only, fixed at 16bit stereo @ 480000 Hz. Based on a patch from H. Peter Anvin Usage: add '-device usb-audio' to your qemu command line. Works sorta ok on a idle machine. Known issues: * Is *very* sensitive to latencies: when the uhci emulation misses one of the usb frame rate wakeups (1k/sec!) you'll loose/delay data from the audio stream, resulting in dropouts. * Also seems to not play very well with the usb tablet (and/or usb hub, to be investigated). Best try this as the only device on a usb bus. * Burns quite some CPU due to usb polling. In short: It brings the qemu usb emulation to its limits. Enjoy! Signed-off-by: Gerd Hoffmann --- Makefile.objs | 2 +- hw/usb-audio.c | 758 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/usb-net.c | 3 - hw/usb.h | 7 + 4 files changed, 766 insertions(+), 4 deletions(-) create mode 100644 hw/usb-audio.c diff --git a/Makefile.objs b/Makefile.objs index f07fb01..06dfcd0 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -73,7 +73,7 @@ common-obj-y += eeprom93xx.o common-obj-y += scsi-disk.o cdrom.o common-obj-y += scsi-generic.o scsi-bus.o common-obj-y += usb.o usb-hub.o usb-$(HOST_USB).o usb-hid.o usb-msd.o usb-wacom.o -common-obj-y += usb-serial.o usb-net.o usb-bus.o +common-obj-y += usb-serial.o usb-net.o usb-bus.o usb-audio.o common-obj-$(CONFIG_SSI) += ssi.o common-obj-$(CONFIG_SSI_SD) += ssi-sd.o common-obj-$(CONFIG_SD) += sd.o diff --git a/hw/usb-audio.c b/hw/usb-audio.c new file mode 100644 index 0000000..8fb00b4 --- /dev/null +++ b/hw/usb-audio.c @@ -0,0 +1,758 @@ +/* + * 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[256] = { + [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(0), /* le16 wTotalLength, filled at runtime */ + 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 + +/* + * buffering + */ + +struct streambuf { + uint8_t *data; + uint32_t size; + uint32_t prod; + uint32_t cons; +}; + +static void streambuf_init(struct streambuf *buf, uint32_t size) +{ + qemu_free(buf->data); + buf->size = size - (size % USBAUDIO_PACKET_SIZE); + buf->data = qemu_malloc(buf->size); + buf->prod = 0; + buf->cons = 0; +} + +static void streambuf_fini(struct streambuf *buf) +{ + qemu_free(buf->data); + buf->data = NULL; +} + +static int streambuf_put(struct streambuf *buf, uint8_t *data) +{ + uint32_t free = buf->size - (buf->prod - buf->cons); + + if (!free) + return 0; + assert(free >= USBAUDIO_PACKET_SIZE); + memcpy(buf->data + (buf->prod % buf->size), data, USBAUDIO_PACKET_SIZE); + buf->prod += USBAUDIO_PACKET_SIZE; + return USBAUDIO_PACKET_SIZE; +} + +static uint8_t *streambuf_get(struct streambuf *buf) +{ + uint32_t used = buf->prod - buf->cons; + uint8_t *data; + + if (!used) + return NULL; + assert(used >= USBAUDIO_PACKET_SIZE); + data = buf->data + (buf->cons % buf->size); + buf->cons += USBAUDIO_PACKET_SIZE; + return data; +} + +typedef struct USBAudioState { + /* qemu interfaces */ + USBDevice dev; + QEMUSoundCard card; + + /* state */ + struct { + enum usb_audio_altset altset; + struct audsettings as; + SWVoiceOut *voice; + bool mute; + uint8_t vol[2]; + struct streambuf buf; + } out; + + /* properties */ + uint32_t debug; + uint32_t buffer; +} USBAudioState; + +static void output_callback(void *opaque, int avail) +{ + USBAudioState *s = opaque; + uint8_t *data; + + for (;;) { + if (avail < USBAUDIO_PACKET_SIZE) + return; + data = streambuf_get(&s->out.buf); + if (NULL == data) { + return; + } + AUD_write(s->out.voice, data, USBAUDIO_PACKET_SIZE); + avail -= USBAUDIO_PACKET_SIZE; + } +} + +static int usb_audio_set_output_altset(USBAudioState *s, int altset) +{ + switch (altset) { + case ALTSET_OFF: + streambuf_init(&s->out.buf, s->buffer); + AUD_set_active_out(s->out.voice, false); + break; + case ALTSET_ON: + AUD_set_active_out(s->out.voice, true); + break; + default: + return -1; + } + + if (s->debug) + fprintf(stderr, "usb-audio: set interface %d\n", altset); + s->out.altset = 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->out.mute; + ret = 1; + break; + case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200): + if (cn < 2) { + uint16_t vol = (s->out.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->out.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); + + if (s->debug) + fprintf(stderr, "usb-audio: vol %04x\n", (uint16_t)vol); + + vol -= 0x8000; + vol = (vol * 255 + 0x4400) / 0x8800; + if (vol > 255) + vol = 255; + + s->out.vol[cn] = vol; + set_vol = true; + ret = 0; + } + break; + } + + if (set_vol) { + if (s->debug) + fprintf(stderr, "usb-audio: mute %d, lvol %3d, rvol %3d\n", + s->out.mute, s->out.vol[0], s->out.vol[1]); + AUD_set_volume_out(s->out.voice, s->out.mute, s->out.vol[0], s->out.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; + + if (s->debug) + 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); + data[2] = ret & 0xff; + data[3] = ret >> 8; + break; + default: + goto fail; + } + 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; + } + if (s->debug) + fprintf(stderr, "usb-audio: fail: string 0x%x doesn't exist\n", + value & 0xff); + goto fail; + } + break; + default: + if (s->debug) + fprintf(stderr, "usb-audio: fail: unknown descriptor 0x%x\n", + value >> 8); + 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->out.altset; + 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) { + if (s->debug) + fprintf(stderr, "usb-audio: fail: get control\n"); + 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) { + if (s->debug) + fprintf(stderr, "usb-audio: fail: set control\n"); + goto fail; + } + break; + + default: + fail: + if (s->debug) + 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); + + if (s->debug) + 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 rc; + + if (s->out.altset == ALTSET_OFF) + return USB_RET_STALL; + + rc = streambuf_put(&s->out.buf, p->data); + if (rc < p->len && s->debug > 1) { + fprintf(stderr, "usb-audio: output overrun (%d bytes)\n", p->len - rc); + } + + 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 && s->debug) + 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); + + if (s->debug) + fprintf(stderr, "usb-audio: destroy\n"); + + usb_audio_set_output_altset(s, ALTSET_OFF); + AUD_close_out(&s->card, s->out.voice); + AUD_remove_card(&s->card); + + streambuf_fini(&s->out.buf); +} + +static int usb_audio_initfn(USBDevice *dev) +{ + USBAudioState *s = DO_UPCAST(USBAudioState, dev, dev); + + s->dev.speed = USB_SPEED_FULL; + s->dev.opaque = s; + AUD_register_card("usb-audio", &s->card); + + s->out.altset = ALTSET_OFF; + s->out.mute = false; + s->out.vol[0] = 240; /* 0 dB */ + s->out.vol[1] = 240; /* 0 dB */ + s->out.as.freq = USBAUDIO_SAMPLE_RATE; + s->out.as.nchannels = 2; + s->out.as.fmt = AUD_FMT_S16; + s->out.as.endianness = 0; + streambuf_init(&s->out.buf, s->buffer); + + s->out.voice = AUD_open_out(&s->card, s->out.voice, "usb-audio", + s, output_callback, &s->out.as); + AUD_set_volume_out(s->out.voice, s->out.mute, s->out.vol[0], s->out.vol[1]); + AUD_set_active_out(s->out.voice, 0); + return 0; +} + +static struct USBDeviceInfo usb_audio_info = { + .product_desc = "QEMU USB Audio Interface", + .usbdevice_name = "audio", + .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, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("debug", USBAudioState, debug, 0), + DEFINE_PROP_UINT32("buffer", USBAudioState, buffer, 8 * USBAUDIO_PACKET_SIZE), + DEFINE_PROP_END_OF_LIST(), + } +}; + +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..111aa39 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,6 +121,8 @@ #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 -- 1.7.1