[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH 5/6] Add support for a USB audio device model
From: |
Gerd Hoffmann |
Subject: |
[Qemu-devel] [PATCH 5/6] Add support for a USB audio device model |
Date: |
Wed, 27 Oct 2010 18:04:34 +0200 |
From: H. Peter Anvin <address@hidden>
This brings a usb audio device to qemu. Output only, fixed at
16bit stereo @ 480000 Hz. Based on a patch from
H. Peter Anvin <address@hidden>
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 <address@hidden>
---
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
[Qemu-devel] [PATCH 6/6] Add Intel HD Audio support to qemu., Gerd Hoffmann, 2010/10/27
Re: [Qemu-devel] [PATCH 0/6] The windows 7 audio support patch series., malc, 2010/10/27