]>
Commit | Line | Data |
---|---|---|
b870472d PA |
1 | /* |
2 | * QEMU USB audio device | |
3 | * | |
4 | * written by: | |
5 | * H. Peter Anvin <hpa@linux.intel.com> | |
6 | * Gerd Hoffmann <kraxel@redhat.com> | |
7 | * | |
8 | * lousely based on usb net device code which is: | |
9 | * | |
10 | * Copyright (c) 2006 Thomas Sailer | |
11 | * Copyright (c) 2008 Andrzej Zaborowski | |
12 | * | |
13 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
14 | * of this software and associated documentation files (the "Software"), to deal | |
15 | * in the Software without restriction, including without limitation the rights | |
16 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
17 | * copies of the Software, and to permit persons to whom the Software is | |
18 | * furnished to do so, subject to the following conditions: | |
19 | * | |
20 | * The above copyright notice and this permission notice shall be included in | |
21 | * all copies or substantial portions of the Software. | |
22 | * | |
23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
24 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
25 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
26 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
27 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
28 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
29 | * THE SOFTWARE. | |
30 | */ | |
31 | ||
e532b2e0 | 32 | #include "qemu/osdep.h" |
0b8fa32f | 33 | #include "qemu/module.h" |
a27bd6c7 | 34 | #include "hw/qdev-properties.h" |
f1ae32a1 | 35 | #include "hw/usb.h" |
d6454270 | 36 | #include "migration/vmstate.h" |
463581a8 | 37 | #include "desc.h" |
b870472d | 38 | #include "audio/audio.h" |
db1015e9 | 39 | #include "qom/object.h" |
b870472d | 40 | |
3e44607e KZ |
41 | static void usb_audio_reinit(USBDevice *dev, unsigned channels); |
42 | ||
b870472d PA |
43 | #define USBAUDIO_VENDOR_NUM 0x46f4 /* CRC16() of "QEMU" */ |
44 | #define USBAUDIO_PRODUCT_NUM 0x0002 | |
45 | ||
46 | #define DEV_CONFIG_VALUE 1 /* The one and only */ | |
47 | ||
3e44607e KZ |
48 | #define USBAUDIO_MAX_CHANNELS(s) (s->multi ? 8 : 2) |
49 | ||
b870472d PA |
50 | /* Descriptor subtypes for AC interfaces */ |
51 | #define DST_AC_HEADER 1 | |
52 | #define DST_AC_INPUT_TERMINAL 2 | |
53 | #define DST_AC_OUTPUT_TERMINAL 3 | |
54 | #define DST_AC_FEATURE_UNIT 6 | |
55 | /* Descriptor subtypes for AS interfaces */ | |
56 | #define DST_AS_GENERAL 1 | |
57 | #define DST_AS_FORMAT_TYPE 2 | |
58 | /* Descriptor subtypes for endpoints */ | |
59 | #define DST_EP_GENERAL 1 | |
60 | ||
61 | enum usb_audio_strings { | |
62 | STRING_NULL, | |
63 | STRING_MANUFACTURER, | |
64 | STRING_PRODUCT, | |
65 | STRING_SERIALNUMBER, | |
66 | STRING_CONFIG, | |
67 | STRING_USBAUDIO_CONTROL, | |
68 | STRING_INPUT_TERMINAL, | |
69 | STRING_FEATURE_UNIT, | |
70 | STRING_OUTPUT_TERMINAL, | |
71 | STRING_NULL_STREAM, | |
72 | STRING_REAL_STREAM, | |
73 | }; | |
74 | ||
75 | static const USBDescStrings usb_audio_stringtable = { | |
76 | [STRING_MANUFACTURER] = "QEMU", | |
77 | [STRING_PRODUCT] = "QEMU USB Audio", | |
78 | [STRING_SERIALNUMBER] = "1", | |
79 | [STRING_CONFIG] = "Audio Configuration", | |
80 | [STRING_USBAUDIO_CONTROL] = "Audio Device", | |
81 | [STRING_INPUT_TERMINAL] = "Audio Output Pipe", | |
82 | [STRING_FEATURE_UNIT] = "Audio Output Volume Control", | |
83 | [STRING_OUTPUT_TERMINAL] = "Audio Output Terminal", | |
84 | [STRING_NULL_STREAM] = "Audio Output - Disabled", | |
85 | [STRING_REAL_STREAM] = "Audio Output - 48 kHz Stereo", | |
86 | }; | |
87 | ||
3e44607e KZ |
88 | /* |
89 | * A USB audio device supports an arbitrary number of alternate | |
90 | * interface settings for each interface. Each corresponds to a block | |
91 | * diagram of parameterized blocks. This can thus refer to things like | |
92 | * number of channels, data rates, or in fact completely different | |
93 | * block diagrams. Alternative setting 0 is always the null block diagram, | |
94 | * which is used by a disabled device. | |
95 | */ | |
96 | enum usb_audio_altset { | |
97 | ALTSET_OFF = 0x00, /* No endpoint */ | |
98 | ALTSET_STEREO = 0x01, /* Single endpoint */ | |
99 | ALTSET_51 = 0x02, | |
100 | ALTSET_71 = 0x03, | |
101 | }; | |
102 | ||
103 | static unsigned altset_channels[] = { | |
104 | [ALTSET_STEREO] = 2, | |
105 | [ALTSET_51] = 6, | |
106 | [ALTSET_71] = 8, | |
107 | }; | |
108 | ||
b870472d PA |
109 | #define U16(x) ((x) & 0xff), (((x) >> 8) & 0xff) |
110 | #define U24(x) U16(x), (((x) >> 16) & 0xff) | |
111 | #define U32(x) U24(x), (((x) >> 24) & 0xff) | |
112 | ||
113 | /* | |
114 | * A Basic Audio Device uses these specific values | |
115 | */ | |
3e44607e KZ |
116 | #define USBAUDIO_PACKET_SIZE_BASE 96 |
117 | #define USBAUDIO_PACKET_SIZE(channels) (USBAUDIO_PACKET_SIZE_BASE * channels) | |
b870472d PA |
118 | #define USBAUDIO_SAMPLE_RATE 48000 |
119 | #define USBAUDIO_PACKET_INTERVAL 1 | |
120 | ||
121 | static const USBDescIface desc_iface[] = { | |
122 | { | |
123 | .bInterfaceNumber = 0, | |
124 | .bNumEndpoints = 0, | |
125 | .bInterfaceClass = USB_CLASS_AUDIO, | |
126 | .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL, | |
b870472d PA |
127 | .iInterface = STRING_USBAUDIO_CONTROL, |
128 | .ndesc = 4, | |
129 | .descs = (USBDescOther[]) { | |
130 | { | |
131 | /* Headphone Class-Specific AC Interface Header Descriptor */ | |
132 | .data = (uint8_t[]) { | |
133 | 0x09, /* u8 bLength */ | |
134 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
135 | DST_AC_HEADER, /* u8 bDescriptorSubtype */ | |
136 | U16(0x0100), /* u16 bcdADC */ | |
137 | U16(0x2b), /* u16 wTotalLength */ | |
138 | 0x01, /* u8 bInCollection */ | |
139 | 0x01, /* u8 baInterfaceNr */ | |
140 | } | |
141 | },{ | |
142 | /* Generic Stereo Input Terminal ID1 Descriptor */ | |
143 | .data = (uint8_t[]) { | |
144 | 0x0c, /* u8 bLength */ | |
145 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
146 | DST_AC_INPUT_TERMINAL, /* u8 bDescriptorSubtype */ | |
147 | 0x01, /* u8 bTerminalID */ | |
148 | U16(0x0101), /* u16 wTerminalType */ | |
149 | 0x00, /* u8 bAssocTerminal */ | |
3e44607e | 150 | 0x02, /* u8 bNrChannels */ |
b870472d PA |
151 | U16(0x0003), /* u16 wChannelConfig */ |
152 | 0x00, /* u8 iChannelNames */ | |
153 | STRING_INPUT_TERMINAL, /* u8 iTerminal */ | |
154 | } | |
155 | },{ | |
156 | /* Generic Stereo Feature Unit ID2 Descriptor */ | |
157 | .data = (uint8_t[]) { | |
158 | 0x0d, /* u8 bLength */ | |
159 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
160 | DST_AC_FEATURE_UNIT, /* u8 bDescriptorSubtype */ | |
161 | 0x02, /* u8 bUnitID */ | |
162 | 0x01, /* u8 bSourceID */ | |
163 | 0x02, /* u8 bControlSize */ | |
164 | U16(0x0001), /* u16 bmaControls(0) */ | |
165 | U16(0x0002), /* u16 bmaControls(1) */ | |
166 | U16(0x0002), /* u16 bmaControls(2) */ | |
167 | STRING_FEATURE_UNIT, /* u8 iFeature */ | |
168 | } | |
169 | },{ | |
ae420c95 | 170 | /* Headphone Output Terminal ID3 Descriptor */ |
b870472d PA |
171 | .data = (uint8_t[]) { |
172 | 0x09, /* u8 bLength */ | |
173 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
174 | DST_AC_OUTPUT_TERMINAL, /* u8 bDescriptorSubtype */ | |
175 | 0x03, /* u8 bUnitID */ | |
176 | U16(0x0301), /* u16 wTerminalType (SPK) */ | |
177 | 0x00, /* u8 bAssocTerminal */ | |
178 | 0x02, /* u8 bSourceID */ | |
179 | STRING_OUTPUT_TERMINAL, /* u8 iTerminal */ | |
180 | } | |
181 | } | |
182 | }, | |
183 | },{ | |
184 | .bInterfaceNumber = 1, | |
3e44607e | 185 | .bAlternateSetting = ALTSET_OFF, |
b870472d PA |
186 | .bNumEndpoints = 0, |
187 | .bInterfaceClass = USB_CLASS_AUDIO, | |
188 | .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, | |
189 | .iInterface = STRING_NULL_STREAM, | |
190 | },{ | |
191 | .bInterfaceNumber = 1, | |
3e44607e | 192 | .bAlternateSetting = ALTSET_STEREO, |
b870472d PA |
193 | .bNumEndpoints = 1, |
194 | .bInterfaceClass = USB_CLASS_AUDIO, | |
195 | .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, | |
196 | .iInterface = STRING_REAL_STREAM, | |
197 | .ndesc = 2, | |
198 | .descs = (USBDescOther[]) { | |
199 | { | |
200 | /* Headphone Class-specific AS General Interface Descriptor */ | |
201 | .data = (uint8_t[]) { | |
202 | 0x07, /* u8 bLength */ | |
203 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
204 | DST_AS_GENERAL, /* u8 bDescriptorSubtype */ | |
205 | 0x01, /* u8 bTerminalLink */ | |
206 | 0x00, /* u8 bDelay */ | |
207 | 0x01, 0x00, /* u16 wFormatTag */ | |
208 | } | |
209 | },{ | |
210 | /* Headphone Type I Format Type Descriptor */ | |
211 | .data = (uint8_t[]) { | |
212 | 0x0b, /* u8 bLength */ | |
213 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
214 | DST_AS_FORMAT_TYPE, /* u8 bDescriptorSubtype */ | |
215 | 0x01, /* u8 bFormatType */ | |
216 | 0x02, /* u8 bNrChannels */ | |
217 | 0x02, /* u8 bSubFrameSize */ | |
218 | 0x10, /* u8 bBitResolution */ | |
219 | 0x01, /* u8 bSamFreqType */ | |
220 | U24(USBAUDIO_SAMPLE_RATE), /* u24 tSamFreq */ | |
221 | } | |
222 | } | |
223 | }, | |
224 | .eps = (USBDescEndpoint[]) { | |
225 | { | |
226 | .bEndpointAddress = USB_DIR_OUT | 0x01, | |
227 | .bmAttributes = 0x0d, | |
3e44607e | 228 | .wMaxPacketSize = USBAUDIO_PACKET_SIZE(2), |
b870472d PA |
229 | .bInterval = 1, |
230 | .is_audio = 1, | |
231 | /* Stereo Headphone Class-specific | |
232 | AS Audio Data Endpoint Descriptor */ | |
233 | .extra = (uint8_t[]) { | |
234 | 0x07, /* u8 bLength */ | |
235 | USB_DT_CS_ENDPOINT, /* u8 bDescriptorType */ | |
236 | DST_EP_GENERAL, /* u8 bDescriptorSubtype */ | |
237 | 0x00, /* u8 bmAttributes */ | |
238 | 0x00, /* u8 bLockDelayUnits */ | |
239 | U16(0x0000), /* u16 wLockDelay */ | |
240 | }, | |
241 | }, | |
242 | } | |
243 | } | |
244 | }; | |
245 | ||
246 | static const USBDescDevice desc_device = { | |
2bbd086c | 247 | .bcdUSB = 0x0100, |
b870472d PA |
248 | .bMaxPacketSize0 = 64, |
249 | .bNumConfigurations = 1, | |
250 | .confs = (USBDescConfig[]) { | |
251 | { | |
252 | .bNumInterfaces = 2, | |
253 | .bConfigurationValue = DEV_CONFIG_VALUE, | |
254 | .iConfiguration = STRING_CONFIG, | |
bd93976a | 255 | .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, |
b870472d PA |
256 | .bMaxPower = 0x32, |
257 | .nif = ARRAY_SIZE(desc_iface), | |
258 | .ifs = desc_iface, | |
259 | }, | |
260 | }, | |
261 | }; | |
262 | ||
263 | static const USBDesc desc_audio = { | |
264 | .id = { | |
265 | .idVendor = USBAUDIO_VENDOR_NUM, | |
266 | .idProduct = USBAUDIO_PRODUCT_NUM, | |
267 | .bcdDevice = 0, | |
268 | .iManufacturer = STRING_MANUFACTURER, | |
269 | .iProduct = STRING_PRODUCT, | |
270 | .iSerialNumber = STRING_SERIALNUMBER, | |
271 | }, | |
272 | .full = &desc_device, | |
273 | .str = usb_audio_stringtable, | |
274 | }; | |
275 | ||
3e44607e KZ |
276 | /* multi channel compatible desc */ |
277 | ||
278 | static const USBDescIface desc_iface_multi[] = { | |
279 | { | |
280 | .bInterfaceNumber = 0, | |
281 | .bNumEndpoints = 0, | |
282 | .bInterfaceClass = USB_CLASS_AUDIO, | |
283 | .bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL, | |
3e44607e KZ |
284 | .iInterface = STRING_USBAUDIO_CONTROL, |
285 | .ndesc = 4, | |
286 | .descs = (USBDescOther[]) { | |
287 | { | |
288 | /* Headphone Class-Specific AC Interface Header Descriptor */ | |
289 | .data = (uint8_t[]) { | |
290 | 0x09, /* u8 bLength */ | |
291 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
292 | DST_AC_HEADER, /* u8 bDescriptorSubtype */ | |
293 | U16(0x0100), /* u16 bcdADC */ | |
8e0cd23f | 294 | U16(0x37), /* u16 wTotalLength */ |
3e44607e KZ |
295 | 0x01, /* u8 bInCollection */ |
296 | 0x01, /* u8 baInterfaceNr */ | |
297 | } | |
298 | },{ | |
299 | /* Generic Stereo Input Terminal ID1 Descriptor */ | |
300 | .data = (uint8_t[]) { | |
301 | 0x0c, /* u8 bLength */ | |
302 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
303 | DST_AC_INPUT_TERMINAL, /* u8 bDescriptorSubtype */ | |
304 | 0x01, /* u8 bTerminalID */ | |
305 | U16(0x0101), /* u16 wTerminalType */ | |
306 | 0x00, /* u8 bAssocTerminal */ | |
307 | 0x08, /* u8 bNrChannels */ | |
308 | U16(0x063f), /* u16 wChannelConfig */ | |
309 | 0x00, /* u8 iChannelNames */ | |
310 | STRING_INPUT_TERMINAL, /* u8 iTerminal */ | |
311 | } | |
312 | },{ | |
313 | /* Generic Stereo Feature Unit ID2 Descriptor */ | |
314 | .data = (uint8_t[]) { | |
315 | 0x19, /* u8 bLength */ | |
316 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
317 | DST_AC_FEATURE_UNIT, /* u8 bDescriptorSubtype */ | |
318 | 0x02, /* u8 bUnitID */ | |
319 | 0x01, /* u8 bSourceID */ | |
320 | 0x02, /* u8 bControlSize */ | |
321 | U16(0x0001), /* u16 bmaControls(0) */ | |
322 | U16(0x0002), /* u16 bmaControls(1) */ | |
323 | U16(0x0002), /* u16 bmaControls(2) */ | |
324 | U16(0x0002), /* u16 bmaControls(3) */ | |
325 | U16(0x0002), /* u16 bmaControls(4) */ | |
326 | U16(0x0002), /* u16 bmaControls(5) */ | |
327 | U16(0x0002), /* u16 bmaControls(6) */ | |
328 | U16(0x0002), /* u16 bmaControls(7) */ | |
329 | U16(0x0002), /* u16 bmaControls(8) */ | |
330 | STRING_FEATURE_UNIT, /* u8 iFeature */ | |
331 | } | |
332 | },{ | |
ae420c95 | 333 | /* Headphone Output Terminal ID3 Descriptor */ |
3e44607e KZ |
334 | .data = (uint8_t[]) { |
335 | 0x09, /* u8 bLength */ | |
336 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
337 | DST_AC_OUTPUT_TERMINAL, /* u8 bDescriptorSubtype */ | |
338 | 0x03, /* u8 bUnitID */ | |
339 | U16(0x0301), /* u16 wTerminalType (SPK) */ | |
340 | 0x00, /* u8 bAssocTerminal */ | |
341 | 0x02, /* u8 bSourceID */ | |
342 | STRING_OUTPUT_TERMINAL, /* u8 iTerminal */ | |
343 | } | |
344 | } | |
345 | }, | |
346 | },{ | |
347 | .bInterfaceNumber = 1, | |
348 | .bAlternateSetting = ALTSET_OFF, | |
349 | .bNumEndpoints = 0, | |
350 | .bInterfaceClass = USB_CLASS_AUDIO, | |
351 | .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, | |
352 | .iInterface = STRING_NULL_STREAM, | |
353 | },{ | |
354 | .bInterfaceNumber = 1, | |
355 | .bAlternateSetting = ALTSET_STEREO, | |
356 | .bNumEndpoints = 1, | |
357 | .bInterfaceClass = USB_CLASS_AUDIO, | |
358 | .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, | |
359 | .iInterface = STRING_REAL_STREAM, | |
360 | .ndesc = 2, | |
361 | .descs = (USBDescOther[]) { | |
362 | { | |
363 | /* Headphone Class-specific AS General Interface Descriptor */ | |
364 | .data = (uint8_t[]) { | |
365 | 0x07, /* u8 bLength */ | |
366 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
367 | DST_AS_GENERAL, /* u8 bDescriptorSubtype */ | |
368 | 0x01, /* u8 bTerminalLink */ | |
369 | 0x00, /* u8 bDelay */ | |
370 | 0x01, 0x00, /* u16 wFormatTag */ | |
371 | } | |
372 | },{ | |
373 | /* Headphone Type I Format Type Descriptor */ | |
374 | .data = (uint8_t[]) { | |
375 | 0x0b, /* u8 bLength */ | |
376 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
377 | DST_AS_FORMAT_TYPE, /* u8 bDescriptorSubtype */ | |
378 | 0x01, /* u8 bFormatType */ | |
379 | 0x02, /* u8 bNrChannels */ | |
380 | 0x02, /* u8 bSubFrameSize */ | |
381 | 0x10, /* u8 bBitResolution */ | |
382 | 0x01, /* u8 bSamFreqType */ | |
383 | U24(USBAUDIO_SAMPLE_RATE), /* u24 tSamFreq */ | |
384 | } | |
385 | } | |
386 | }, | |
387 | .eps = (USBDescEndpoint[]) { | |
388 | { | |
389 | .bEndpointAddress = USB_DIR_OUT | 0x01, | |
390 | .bmAttributes = 0x0d, | |
391 | .wMaxPacketSize = USBAUDIO_PACKET_SIZE(2), | |
392 | .bInterval = 1, | |
393 | .is_audio = 1, | |
394 | /* Stereo Headphone Class-specific | |
395 | AS Audio Data Endpoint Descriptor */ | |
396 | .extra = (uint8_t[]) { | |
397 | 0x07, /* u8 bLength */ | |
398 | USB_DT_CS_ENDPOINT, /* u8 bDescriptorType */ | |
399 | DST_EP_GENERAL, /* u8 bDescriptorSubtype */ | |
400 | 0x00, /* u8 bmAttributes */ | |
401 | 0x00, /* u8 bLockDelayUnits */ | |
402 | U16(0x0000), /* u16 wLockDelay */ | |
403 | }, | |
404 | }, | |
405 | } | |
406 | },{ | |
407 | .bInterfaceNumber = 1, | |
408 | .bAlternateSetting = ALTSET_51, | |
409 | .bNumEndpoints = 1, | |
410 | .bInterfaceClass = USB_CLASS_AUDIO, | |
411 | .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, | |
412 | .iInterface = STRING_REAL_STREAM, | |
413 | .ndesc = 2, | |
414 | .descs = (USBDescOther[]) { | |
415 | { | |
416 | /* Headphone Class-specific AS General Interface Descriptor */ | |
417 | .data = (uint8_t[]) { | |
418 | 0x07, /* u8 bLength */ | |
419 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
420 | DST_AS_GENERAL, /* u8 bDescriptorSubtype */ | |
421 | 0x01, /* u8 bTerminalLink */ | |
422 | 0x00, /* u8 bDelay */ | |
423 | 0x01, 0x00, /* u16 wFormatTag */ | |
424 | } | |
425 | },{ | |
426 | /* Headphone Type I Format Type Descriptor */ | |
427 | .data = (uint8_t[]) { | |
428 | 0x0b, /* u8 bLength */ | |
429 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
430 | DST_AS_FORMAT_TYPE, /* u8 bDescriptorSubtype */ | |
431 | 0x01, /* u8 bFormatType */ | |
432 | 0x06, /* u8 bNrChannels */ | |
433 | 0x02, /* u8 bSubFrameSize */ | |
434 | 0x10, /* u8 bBitResolution */ | |
435 | 0x01, /* u8 bSamFreqType */ | |
436 | U24(USBAUDIO_SAMPLE_RATE), /* u24 tSamFreq */ | |
437 | } | |
438 | } | |
439 | }, | |
440 | .eps = (USBDescEndpoint[]) { | |
441 | { | |
442 | .bEndpointAddress = USB_DIR_OUT | 0x01, | |
443 | .bmAttributes = 0x0d, | |
444 | .wMaxPacketSize = USBAUDIO_PACKET_SIZE(6), | |
445 | .bInterval = 1, | |
446 | .is_audio = 1, | |
447 | /* Stereo Headphone Class-specific | |
448 | AS Audio Data Endpoint Descriptor */ | |
449 | .extra = (uint8_t[]) { | |
450 | 0x07, /* u8 bLength */ | |
451 | USB_DT_CS_ENDPOINT, /* u8 bDescriptorType */ | |
452 | DST_EP_GENERAL, /* u8 bDescriptorSubtype */ | |
453 | 0x00, /* u8 bmAttributes */ | |
454 | 0x00, /* u8 bLockDelayUnits */ | |
455 | U16(0x0000), /* u16 wLockDelay */ | |
456 | }, | |
457 | }, | |
458 | } | |
459 | },{ | |
460 | .bInterfaceNumber = 1, | |
461 | .bAlternateSetting = ALTSET_71, | |
462 | .bNumEndpoints = 1, | |
463 | .bInterfaceClass = USB_CLASS_AUDIO, | |
464 | .bInterfaceSubClass = USB_SUBCLASS_AUDIO_STREAMING, | |
465 | .iInterface = STRING_REAL_STREAM, | |
466 | .ndesc = 2, | |
467 | .descs = (USBDescOther[]) { | |
468 | { | |
469 | /* Headphone Class-specific AS General Interface Descriptor */ | |
470 | .data = (uint8_t[]) { | |
471 | 0x07, /* u8 bLength */ | |
472 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
473 | DST_AS_GENERAL, /* u8 bDescriptorSubtype */ | |
474 | 0x01, /* u8 bTerminalLink */ | |
475 | 0x00, /* u8 bDelay */ | |
476 | 0x01, 0x00, /* u16 wFormatTag */ | |
477 | } | |
478 | },{ | |
479 | /* Headphone Type I Format Type Descriptor */ | |
480 | .data = (uint8_t[]) { | |
481 | 0x0b, /* u8 bLength */ | |
482 | USB_DT_CS_INTERFACE, /* u8 bDescriptorType */ | |
483 | DST_AS_FORMAT_TYPE, /* u8 bDescriptorSubtype */ | |
484 | 0x01, /* u8 bFormatType */ | |
485 | 0x08, /* u8 bNrChannels */ | |
486 | 0x02, /* u8 bSubFrameSize */ | |
487 | 0x10, /* u8 bBitResolution */ | |
488 | 0x01, /* u8 bSamFreqType */ | |
489 | U24(USBAUDIO_SAMPLE_RATE), /* u24 tSamFreq */ | |
490 | } | |
491 | } | |
492 | }, | |
493 | .eps = (USBDescEndpoint[]) { | |
494 | { | |
495 | .bEndpointAddress = USB_DIR_OUT | 0x01, | |
496 | .bmAttributes = 0x0d, | |
497 | .wMaxPacketSize = USBAUDIO_PACKET_SIZE(8), | |
498 | .bInterval = 1, | |
499 | .is_audio = 1, | |
500 | /* Stereo Headphone Class-specific | |
501 | AS Audio Data Endpoint Descriptor */ | |
502 | .extra = (uint8_t[]) { | |
503 | 0x07, /* u8 bLength */ | |
504 | USB_DT_CS_ENDPOINT, /* u8 bDescriptorType */ | |
505 | DST_EP_GENERAL, /* u8 bDescriptorSubtype */ | |
506 | 0x00, /* u8 bmAttributes */ | |
507 | 0x00, /* u8 bLockDelayUnits */ | |
508 | U16(0x0000), /* u16 wLockDelay */ | |
509 | }, | |
510 | }, | |
511 | } | |
512 | } | |
513 | }; | |
514 | ||
515 | static const USBDescDevice desc_device_multi = { | |
516 | .bcdUSB = 0x0100, | |
517 | .bMaxPacketSize0 = 64, | |
518 | .bNumConfigurations = 1, | |
519 | .confs = (USBDescConfig[]) { | |
520 | { | |
521 | .bNumInterfaces = 2, | |
522 | .bConfigurationValue = DEV_CONFIG_VALUE, | |
523 | .iConfiguration = STRING_CONFIG, | |
524 | .bmAttributes = USB_CFG_ATT_ONE | USB_CFG_ATT_SELFPOWER, | |
525 | .bMaxPower = 0x32, | |
526 | .nif = ARRAY_SIZE(desc_iface_multi), | |
527 | .ifs = desc_iface_multi, | |
528 | } | |
529 | }, | |
530 | }; | |
531 | ||
532 | static const USBDesc desc_audio_multi = { | |
533 | .id = { | |
534 | .idVendor = USBAUDIO_VENDOR_NUM, | |
535 | .idProduct = USBAUDIO_PRODUCT_NUM, | |
536 | .bcdDevice = 0, | |
537 | .iManufacturer = STRING_MANUFACTURER, | |
538 | .iProduct = STRING_PRODUCT, | |
539 | .iSerialNumber = STRING_SERIALNUMBER, | |
540 | }, | |
541 | .full = &desc_device_multi, | |
542 | .str = usb_audio_stringtable, | |
b870472d PA |
543 | }; |
544 | ||
545 | /* | |
546 | * Class-specific control requests | |
547 | */ | |
548 | #define CR_SET_CUR 0x01 | |
549 | #define CR_GET_CUR 0x81 | |
550 | #define CR_SET_MIN 0x02 | |
551 | #define CR_GET_MIN 0x82 | |
552 | #define CR_SET_MAX 0x03 | |
553 | #define CR_GET_MAX 0x83 | |
554 | #define CR_SET_RES 0x04 | |
555 | #define CR_GET_RES 0x84 | |
556 | #define CR_SET_MEM 0x05 | |
557 | #define CR_GET_MEM 0x85 | |
558 | #define CR_GET_STAT 0xff | |
559 | ||
560 | /* | |
561 | * Feature Unit Control Selectors | |
562 | */ | |
563 | #define MUTE_CONTROL 0x01 | |
564 | #define VOLUME_CONTROL 0x02 | |
565 | #define BASS_CONTROL 0x03 | |
566 | #define MID_CONTROL 0x04 | |
567 | #define TREBLE_CONTROL 0x05 | |
568 | #define GRAPHIC_EQUALIZER_CONTROL 0x06 | |
569 | #define AUTOMATIC_GAIN_CONTROL 0x07 | |
570 | #define DELAY_CONTROL 0x08 | |
571 | #define BASS_BOOST_CONTROL 0x09 | |
572 | #define LOUDNESS_CONTROL 0x0a | |
573 | ||
574 | /* | |
575 | * buffering | |
576 | */ | |
577 | ||
578 | struct streambuf { | |
579 | uint8_t *data; | |
670777a9 KZ |
580 | size_t size; |
581 | uint64_t prod; | |
582 | uint64_t cons; | |
b870472d PA |
583 | }; |
584 | ||
3e44607e KZ |
585 | static void streambuf_init(struct streambuf *buf, uint32_t size, |
586 | uint32_t channels) | |
b870472d PA |
587 | { |
588 | g_free(buf->data); | |
3e44607e | 589 | buf->size = size - (size % USBAUDIO_PACKET_SIZE(channels)); |
b870472d PA |
590 | buf->data = g_malloc(buf->size); |
591 | buf->prod = 0; | |
592 | buf->cons = 0; | |
593 | } | |
594 | ||
595 | static void streambuf_fini(struct streambuf *buf) | |
596 | { | |
597 | g_free(buf->data); | |
598 | buf->data = NULL; | |
599 | } | |
600 | ||
3e44607e | 601 | static int streambuf_put(struct streambuf *buf, USBPacket *p, uint32_t channels) |
b870472d | 602 | { |
670777a9 | 603 | int64_t free = buf->size - (buf->prod - buf->cons); |
b870472d | 604 | |
3e44607e | 605 | if (free < USBAUDIO_PACKET_SIZE(channels)) { |
b870472d PA |
606 | return 0; |
607 | } | |
3e44607e | 608 | if (p->iov.size != USBAUDIO_PACKET_SIZE(channels)) { |
a7fde1c1 GH |
609 | return 0; |
610 | } | |
2c6a740f | 611 | |
670777a9 KZ |
612 | /* can happen if prod overflows */ |
613 | assert(buf->prod % USBAUDIO_PACKET_SIZE(channels) == 0); | |
b870472d | 614 | usb_packet_copy(p, buf->data + (buf->prod % buf->size), |
3e44607e KZ |
615 | USBAUDIO_PACKET_SIZE(channels)); |
616 | buf->prod += USBAUDIO_PACKET_SIZE(channels); | |
617 | return USBAUDIO_PACKET_SIZE(channels); | |
b870472d PA |
618 | } |
619 | ||
2c6a740f | 620 | static uint8_t *streambuf_get(struct streambuf *buf, size_t *len) |
b870472d | 621 | { |
670777a9 | 622 | int64_t used = buf->prod - buf->cons; |
b870472d PA |
623 | uint8_t *data; |
624 | ||
670777a9 | 625 | if (used <= 0) { |
2c6a740f | 626 | *len = 0; |
b870472d PA |
627 | return NULL; |
628 | } | |
b870472d | 629 | data = buf->data + (buf->cons % buf->size); |
2c6a740f KZ |
630 | *len = MIN(buf->prod - buf->cons, |
631 | buf->size - (buf->cons % buf->size)); | |
b870472d PA |
632 | return data; |
633 | } | |
634 | ||
db1015e9 | 635 | struct USBAudioState { |
b870472d PA |
636 | /* qemu interfaces */ |
637 | USBDevice dev; | |
638 | QEMUSoundCard card; | |
639 | ||
640 | /* state */ | |
641 | struct { | |
642 | enum usb_audio_altset altset; | |
643 | struct audsettings as; | |
644 | SWVoiceOut *voice; | |
3e44607e | 645 | Volume vol; |
b870472d | 646 | struct streambuf buf; |
3e44607e | 647 | uint32_t channels; |
b870472d PA |
648 | } out; |
649 | ||
650 | /* properties */ | |
651 | uint32_t debug; | |
3e44607e KZ |
652 | uint32_t buffer_user, buffer; |
653 | bool multi; | |
db1015e9 | 654 | }; |
b870472d | 655 | |
0389a0b1 | 656 | #define TYPE_USB_AUDIO "usb-audio" |
8063396b | 657 | OBJECT_DECLARE_SIMPLE_TYPE(USBAudioState, USB_AUDIO) |
0389a0b1 | 658 | |
b870472d PA |
659 | static void output_callback(void *opaque, int avail) |
660 | { | |
661 | USBAudioState *s = opaque; | |
662 | uint8_t *data; | |
663 | ||
2c6a740f KZ |
664 | while (avail) { |
665 | size_t written, len; | |
666 | ||
667 | data = streambuf_get(&s->out.buf, &len); | |
668 | if (!data) { | |
b870472d PA |
669 | return; |
670 | } | |
2c6a740f KZ |
671 | |
672 | written = AUD_write(s->out.voice, data, len); | |
673 | avail -= written; | |
674 | s->out.buf.cons += written; | |
675 | ||
676 | if (written < len) { | |
b870472d PA |
677 | return; |
678 | } | |
b870472d PA |
679 | } |
680 | } | |
681 | ||
682 | static int usb_audio_set_output_altset(USBAudioState *s, int altset) | |
683 | { | |
684 | switch (altset) { | |
685 | case ALTSET_OFF: | |
b870472d PA |
686 | AUD_set_active_out(s->out.voice, false); |
687 | break; | |
3e44607e KZ |
688 | case ALTSET_STEREO: |
689 | case ALTSET_51: | |
690 | case ALTSET_71: | |
691 | if (s->out.channels != altset_channels[altset]) { | |
692 | usb_audio_reinit(USB_DEVICE(s), altset_channels[altset]); | |
693 | } | |
694 | streambuf_init(&s->out.buf, s->buffer, s->out.channels); | |
b870472d PA |
695 | AUD_set_active_out(s->out.voice, true); |
696 | break; | |
697 | default: | |
698 | return -1; | |
699 | } | |
700 | ||
701 | if (s->debug) { | |
702 | fprintf(stderr, "usb-audio: set interface %d\n", altset); | |
703 | } | |
704 | s->out.altset = altset; | |
705 | return 0; | |
706 | } | |
707 | ||
708 | /* | |
709 | * Note: we arbitrarily map the volume control range onto -inf..+8 dB | |
710 | */ | |
711 | #define ATTRIB_ID(cs, attrib, idif) \ | |
712 | (((cs) << 24) | ((attrib) << 16) | (idif)) | |
713 | ||
714 | static int usb_audio_get_control(USBAudioState *s, uint8_t attrib, | |
715 | uint16_t cscn, uint16_t idif, | |
716 | int length, uint8_t *data) | |
717 | { | |
718 | uint8_t cs = cscn >> 8; | |
719 | uint8_t cn = cscn - 1; /* -1 for the non-present master control */ | |
720 | uint32_t aid = ATTRIB_ID(cs, attrib, idif); | |
721 | int ret = USB_RET_STALL; | |
722 | ||
723 | switch (aid) { | |
724 | case ATTRIB_ID(MUTE_CONTROL, CR_GET_CUR, 0x0200): | |
3e44607e | 725 | data[0] = s->out.vol.mute; |
b870472d PA |
726 | ret = 1; |
727 | break; | |
728 | case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200): | |
3e44607e KZ |
729 | if (cn < USBAUDIO_MAX_CHANNELS(s)) { |
730 | uint16_t vol = (s->out.vol.vol[cn] * 0x8800 + 127) / 255 + 0x8000; | |
b870472d PA |
731 | data[0] = vol; |
732 | data[1] = vol >> 8; | |
733 | ret = 2; | |
734 | } | |
735 | break; | |
736 | case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MIN, 0x0200): | |
3e44607e | 737 | if (cn < USBAUDIO_MAX_CHANNELS(s)) { |
b870472d PA |
738 | data[0] = 0x01; |
739 | data[1] = 0x80; | |
740 | ret = 2; | |
741 | } | |
742 | break; | |
743 | case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MAX, 0x0200): | |
3e44607e | 744 | if (cn < USBAUDIO_MAX_CHANNELS(s)) { |
b870472d PA |
745 | data[0] = 0x00; |
746 | data[1] = 0x08; | |
747 | ret = 2; | |
748 | } | |
749 | break; | |
750 | case ATTRIB_ID(VOLUME_CONTROL, CR_GET_RES, 0x0200): | |
3e44607e | 751 | if (cn < USBAUDIO_MAX_CHANNELS(s)) { |
b870472d PA |
752 | data[0] = 0x88; |
753 | data[1] = 0x00; | |
754 | ret = 2; | |
755 | } | |
756 | break; | |
757 | } | |
758 | ||
759 | return ret; | |
760 | } | |
761 | static int usb_audio_set_control(USBAudioState *s, uint8_t attrib, | |
762 | uint16_t cscn, uint16_t idif, | |
763 | int length, uint8_t *data) | |
764 | { | |
765 | uint8_t cs = cscn >> 8; | |
766 | uint8_t cn = cscn - 1; /* -1 for the non-present master control */ | |
767 | uint32_t aid = ATTRIB_ID(cs, attrib, idif); | |
768 | int ret = USB_RET_STALL; | |
769 | bool set_vol = false; | |
770 | ||
771 | switch (aid) { | |
772 | case ATTRIB_ID(MUTE_CONTROL, CR_SET_CUR, 0x0200): | |
3e44607e | 773 | s->out.vol.mute = data[0] & 1; |
b870472d PA |
774 | set_vol = true; |
775 | ret = 0; | |
776 | break; | |
777 | case ATTRIB_ID(VOLUME_CONTROL, CR_SET_CUR, 0x0200): | |
3e44607e | 778 | if (cn < USBAUDIO_MAX_CHANNELS(s)) { |
b870472d PA |
779 | uint16_t vol = data[0] + (data[1] << 8); |
780 | ||
781 | if (s->debug) { | |
3e44607e KZ |
782 | fprintf(stderr, "usb-audio: cn %d vol %04x\n", cn, |
783 | (uint16_t)vol); | |
b870472d PA |
784 | } |
785 | ||
786 | vol -= 0x8000; | |
787 | vol = (vol * 255 + 0x4400) / 0x8800; | |
788 | if (vol > 255) { | |
789 | vol = 255; | |
790 | } | |
791 | ||
3e44607e | 792 | s->out.vol.vol[cn] = vol; |
b870472d PA |
793 | set_vol = true; |
794 | ret = 0; | |
795 | } | |
796 | break; | |
797 | } | |
798 | ||
799 | if (set_vol) { | |
800 | if (s->debug) { | |
3e44607e KZ |
801 | int i; |
802 | fprintf(stderr, "usb-audio: mute %d", s->out.vol.mute); | |
803 | for (i = 0; i < USBAUDIO_MAX_CHANNELS(s); ++i) { | |
804 | fprintf(stderr, ", vol[%d] %3d", i, s->out.vol.vol[i]); | |
805 | } | |
806 | fprintf(stderr, "\n"); | |
b870472d | 807 | } |
3e44607e | 808 | audio_set_volume_out(s->out.voice, &s->out.vol); |
b870472d PA |
809 | } |
810 | ||
811 | return ret; | |
812 | } | |
813 | ||
9a77a0f5 | 814 | static void usb_audio_handle_control(USBDevice *dev, USBPacket *p, |
b870472d PA |
815 | int request, int value, int index, |
816 | int length, uint8_t *data) | |
817 | { | |
0389a0b1 | 818 | USBAudioState *s = USB_AUDIO(dev); |
b870472d PA |
819 | int ret = 0; |
820 | ||
821 | if (s->debug) { | |
822 | fprintf(stderr, "usb-audio: control transaction: " | |
823 | "request 0x%04x value 0x%04x index 0x%04x length 0x%04x\n", | |
824 | request, value, index, length); | |
825 | } | |
826 | ||
827 | ret = usb_desc_handle_control(dev, p, request, value, index, length, data); | |
828 | if (ret >= 0) { | |
9a77a0f5 | 829 | return; |
b870472d PA |
830 | } |
831 | ||
832 | switch (request) { | |
833 | case ClassInterfaceRequest | CR_GET_CUR: | |
834 | case ClassInterfaceRequest | CR_GET_MIN: | |
835 | case ClassInterfaceRequest | CR_GET_MAX: | |
836 | case ClassInterfaceRequest | CR_GET_RES: | |
837 | ret = usb_audio_get_control(s, request & 0xff, value, index, | |
838 | length, data); | |
839 | if (ret < 0) { | |
840 | if (s->debug) { | |
841 | fprintf(stderr, "usb-audio: fail: get control\n"); | |
842 | } | |
843 | goto fail; | |
844 | } | |
9a77a0f5 | 845 | p->actual_length = ret; |
b870472d PA |
846 | break; |
847 | ||
848 | case ClassInterfaceOutRequest | CR_SET_CUR: | |
849 | case ClassInterfaceOutRequest | CR_SET_MIN: | |
850 | case ClassInterfaceOutRequest | CR_SET_MAX: | |
851 | case ClassInterfaceOutRequest | CR_SET_RES: | |
852 | ret = usb_audio_set_control(s, request & 0xff, value, index, | |
853 | length, data); | |
854 | if (ret < 0) { | |
855 | if (s->debug) { | |
856 | fprintf(stderr, "usb-audio: fail: set control\n"); | |
857 | } | |
858 | goto fail; | |
859 | } | |
860 | break; | |
861 | ||
862 | default: | |
863 | fail: | |
864 | if (s->debug) { | |
865 | fprintf(stderr, "usb-audio: failed control transaction: " | |
866 | "request 0x%04x value 0x%04x index 0x%04x length 0x%04x\n", | |
867 | request, value, index, length); | |
868 | } | |
9a77a0f5 | 869 | p->status = USB_RET_STALL; |
b870472d PA |
870 | break; |
871 | } | |
b870472d PA |
872 | } |
873 | ||
874 | static void usb_audio_set_interface(USBDevice *dev, int iface, | |
875 | int old, int value) | |
876 | { | |
0389a0b1 | 877 | USBAudioState *s = USB_AUDIO(dev); |
b870472d PA |
878 | |
879 | if (iface == 1) { | |
880 | usb_audio_set_output_altset(s, value); | |
881 | } | |
882 | } | |
883 | ||
884 | static void usb_audio_handle_reset(USBDevice *dev) | |
885 | { | |
0389a0b1 | 886 | USBAudioState *s = USB_AUDIO(dev); |
b870472d PA |
887 | |
888 | if (s->debug) { | |
889 | fprintf(stderr, "usb-audio: reset\n"); | |
890 | } | |
891 | usb_audio_set_output_altset(s, ALTSET_OFF); | |
892 | } | |
893 | ||
9a77a0f5 | 894 | static void usb_audio_handle_dataout(USBAudioState *s, USBPacket *p) |
b870472d | 895 | { |
b870472d | 896 | if (s->out.altset == ALTSET_OFF) { |
9a77a0f5 HG |
897 | p->status = USB_RET_STALL; |
898 | return; | |
b870472d PA |
899 | } |
900 | ||
3e44607e | 901 | streambuf_put(&s->out.buf, p, s->out.channels); |
9a77a0f5 | 902 | if (p->actual_length < p->iov.size && s->debug > 1) { |
b870472d | 903 | fprintf(stderr, "usb-audio: output overrun (%zd bytes)\n", |
9a77a0f5 | 904 | p->iov.size - p->actual_length); |
b870472d | 905 | } |
b870472d PA |
906 | } |
907 | ||
9a77a0f5 | 908 | static void usb_audio_handle_data(USBDevice *dev, USBPacket *p) |
b870472d PA |
909 | { |
910 | USBAudioState *s = (USBAudioState *) dev; | |
b870472d | 911 | |
9a77a0f5 HG |
912 | if (p->pid == USB_TOKEN_OUT && p->ep->nr == 1) { |
913 | usb_audio_handle_dataout(s, p); | |
914 | return; | |
b870472d | 915 | } |
9a77a0f5 HG |
916 | |
917 | p->status = USB_RET_STALL; | |
918 | if (s->debug) { | |
b870472d PA |
919 | fprintf(stderr, "usb-audio: failed data transaction: " |
920 | "pid 0x%x ep 0x%x len 0x%zx\n", | |
079d0b7f | 921 | p->pid, p->ep->nr, p->iov.size); |
b870472d | 922 | } |
b870472d PA |
923 | } |
924 | ||
b69c3c21 | 925 | static void usb_audio_unrealize(USBDevice *dev) |
b870472d | 926 | { |
0389a0b1 | 927 | USBAudioState *s = USB_AUDIO(dev); |
b870472d PA |
928 | |
929 | if (s->debug) { | |
930 | fprintf(stderr, "usb-audio: destroy\n"); | |
931 | } | |
932 | ||
933 | usb_audio_set_output_altset(s, ALTSET_OFF); | |
934 | AUD_close_out(&s->card, s->out.voice); | |
935 | AUD_remove_card(&s->card); | |
936 | ||
937 | streambuf_fini(&s->out.buf); | |
938 | } | |
939 | ||
5450eeaa | 940 | static void usb_audio_realize(USBDevice *dev, Error **errp) |
b870472d | 941 | { |
0389a0b1 | 942 | USBAudioState *s = USB_AUDIO(dev); |
3e44607e KZ |
943 | int i; |
944 | ||
cb94ff5f MK |
945 | if (!AUD_register_card(TYPE_USB_AUDIO, &s->card, errp)) { |
946 | return; | |
947 | } | |
948 | ||
3e44607e | 949 | dev->usb_desc = s->multi ? &desc_audio_multi : &desc_audio; |
b870472d | 950 | |
9d55d1ad | 951 | usb_desc_create_serial(dev); |
b870472d PA |
952 | usb_desc_init(dev); |
953 | s->dev.opaque = s; | |
b870472d PA |
954 | |
955 | s->out.altset = ALTSET_OFF; | |
3e44607e KZ |
956 | s->out.vol.mute = false; |
957 | for (i = 0; i < USBAUDIO_MAX_CHANNELS(s); ++i) { | |
958 | s->out.vol.vol[i] = 240; /* 0 dB */ | |
959 | } | |
960 | ||
961 | usb_audio_reinit(dev, 2); | |
962 | } | |
963 | ||
964 | static void usb_audio_reinit(USBDevice *dev, unsigned channels) | |
965 | { | |
966 | USBAudioState *s = USB_AUDIO(dev); | |
967 | ||
968 | s->out.channels = channels; | |
969 | if (!s->buffer_user) { | |
970 | s->buffer = 32 * USBAUDIO_PACKET_SIZE(s->out.channels); | |
971 | } else { | |
972 | s->buffer = s->buffer_user; | |
973 | } | |
974 | ||
975 | s->out.vol.channels = s->out.channels; | |
b870472d | 976 | s->out.as.freq = USBAUDIO_SAMPLE_RATE; |
3e44607e | 977 | s->out.as.nchannels = s->out.channels; |
85bc5852 | 978 | s->out.as.fmt = AUDIO_FORMAT_S16; |
b870472d | 979 | s->out.as.endianness = 0; |
3e44607e | 980 | streambuf_init(&s->out.buf, s->buffer, s->out.channels); |
b870472d | 981 | |
0389a0b1 | 982 | s->out.voice = AUD_open_out(&s->card, s->out.voice, TYPE_USB_AUDIO, |
b870472d | 983 | s, output_callback, &s->out.as); |
3e44607e | 984 | audio_set_volume_out(s->out.voice, &s->out.vol); |
b870472d | 985 | AUD_set_active_out(s->out.voice, 0); |
b870472d PA |
986 | } |
987 | ||
988 | static const VMStateDescription vmstate_usb_audio = { | |
0389a0b1 | 989 | .name = TYPE_USB_AUDIO, |
b870472d PA |
990 | .unmigratable = 1, |
991 | }; | |
992 | ||
39bffca2 | 993 | static Property usb_audio_properties[] = { |
88e47b9a | 994 | DEFINE_AUDIO_PROPERTIES(USBAudioState, card), |
39bffca2 | 995 | DEFINE_PROP_UINT32("debug", USBAudioState, debug, 0), |
3e44607e KZ |
996 | DEFINE_PROP_UINT32("buffer", USBAudioState, buffer_user, 0), |
997 | DEFINE_PROP_BOOL("multi", USBAudioState, multi, false), | |
39bffca2 AL |
998 | DEFINE_PROP_END_OF_LIST(), |
999 | }; | |
1000 | ||
62aed765 AL |
1001 | static void usb_audio_class_init(ObjectClass *klass, void *data) |
1002 | { | |
39bffca2 | 1003 | DeviceClass *dc = DEVICE_CLASS(klass); |
62aed765 AL |
1004 | USBDeviceClass *k = USB_DEVICE_CLASS(klass); |
1005 | ||
39bffca2 | 1006 | dc->vmsd = &vmstate_usb_audio; |
4f67d30b | 1007 | device_class_set_props(dc, usb_audio_properties); |
125ee0ed | 1008 | set_bit(DEVICE_CATEGORY_SOUND, dc->categories); |
62aed765 | 1009 | k->product_desc = "QEMU USB Audio Interface"; |
5450eeaa | 1010 | k->realize = usb_audio_realize; |
62aed765 AL |
1011 | k->handle_reset = usb_audio_handle_reset; |
1012 | k->handle_control = usb_audio_handle_control; | |
1013 | k->handle_data = usb_audio_handle_data; | |
c4fe9700 | 1014 | k->unrealize = usb_audio_unrealize; |
62aed765 AL |
1015 | k->set_interface = usb_audio_set_interface; |
1016 | } | |
1017 | ||
8c43a6f0 | 1018 | static const TypeInfo usb_audio_info = { |
0389a0b1 | 1019 | .name = TYPE_USB_AUDIO, |
39bffca2 AL |
1020 | .parent = TYPE_USB_DEVICE, |
1021 | .instance_size = sizeof(USBAudioState), | |
1022 | .class_init = usb_audio_class_init, | |
b870472d PA |
1023 | }; |
1024 | ||
83f7d43a | 1025 | static void usb_audio_register_types(void) |
b870472d | 1026 | { |
39bffca2 | 1027 | type_register_static(&usb_audio_info); |
b870472d PA |
1028 | } |
1029 | ||
83f7d43a | 1030 | type_init(usb_audio_register_types) |