From 141e67b39c6d7e500a1c21048afc841ddefd8f27 Mon Sep 17 00:00:00 2001 From: Cacodemon345 Date: Sat, 14 Dec 2024 18:18:16 +0600 Subject: [PATCH] Completely untested USB Audio device --- src/include/86box/sound.h | 2 + src/sound/sound.c | 1 + src/usb/CMakeLists.txt | 2 +- src/usb/usb_audio.c | 494 ++++++++++++++++++++++++++++++++++++++ src/usb/usb_common.c | 14 +- src/usb/usb_common.h | 11 +- 6 files changed, 514 insertions(+), 10 deletions(-) create mode 100644 src/usb/usb_audio.c diff --git a/src/include/86box/sound.h b/src/include/86box/sound.h index 2a41b98f89..946f1d9505 100644 --- a/src/include/86box/sound.h +++ b/src/include/86box/sound.h @@ -210,6 +210,8 @@ extern const device_t tndy_device; extern const device_t wss_device; extern const device_t ncr_business_audio_device; +extern const device_t usb_audio_device; + #ifdef USE_LIBSERIALPORT /* External Audio device OPL2Board (Host Connected hardware)*/ extern const device_t opl2board_device; diff --git a/src/sound/sound.c b/src/sound/sound.c index d2d3dc3133..06c0137dce 100644 --- a/src/sound/sound.c +++ b/src/sound/sound.c @@ -154,6 +154,7 @@ static const SOUND_CARD sound_cards[] = { { &ct5880_device }, { &ad1881_device }, { &cs4297a_device }, + { &usb_audio_device }, #ifdef USE_LIBSERIALPORT /*The following devices required LIBSERIALPORT*/ { &opl2board_device }, #endif diff --git a/src/usb/CMakeLists.txt b/src/usb/CMakeLists.txt index fadf24bf50..4938551b79 100644 --- a/src/usb/CMakeLists.txt +++ b/src/usb/CMakeLists.txt @@ -1 +1 @@ -add_library(usb OBJECT usb_hid_mouse.c usb_hid_keyboard.c usb_uhci_bochs.c usb_ohci_bochs.c usb_common.c) \ No newline at end of file +add_library(usb OBJECT usb_hid_mouse.c usb_hid_keyboard.c usb_uhci_bochs.c usb_ohci_bochs.c usb_common.c usb_audio.c) \ No newline at end of file diff --git a/src/usb/usb_audio.c b/src/usb/usb_audio.c new file mode 100644 index 0000000000..a53a903037 --- /dev/null +++ b/src/usb/usb_audio.c @@ -0,0 +1,494 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/device.h> +#include <86box/io.h> +#include <86box/mem.h> +#include <86box/usb.h> +#include <86box/dma.h> +#include "cpu.h" +#include <86box/pci.h> +#include <86box/timer.h> +#include <86box/sound.h> +#include <86box/fifo8.h> + +#include "usb_common.h" + +// Speaker +static const uint8_t bx_audio_dev_descriptor[] = { + 0x12, /* u8 bLength; */ + 0x01, /* u8 bDescriptorType; Device */ + 0x01, 0x01, /* u16 bcdUSB; v1.1 */ + + 0x00, /* u8 bDeviceClass; */ + 0x00, /* u8 bDeviceSubClass; */ + 0x00, /* u8 bDeviceProtocol; */ + 0x40, /* u8 bMaxPacketSize; 64 Bytes */ + + 0x27, 0x06, /* u16 idVendor; */ + 0x01, 0x05, /* u16 idProduct; */ + 0x00, 0x00, /* u16 bcdDevice */ + + 0x01, /* u8 iManufacturer; */ + 0x02, /* u8 iProduct; */ + 0x03, /* u8 iSerialNumber; */ + 0x01 /* u8 bNumConfigurations; */ +}; + +static const uint8_t bx_audio_config_descriptor[] = +{ + 0x09, /* u8 bLength; */ + 0x02, /* u8 bDescriptorType; Configuration */ + 108, 0x00, /* u16 wTotalLength; */ + 0x02, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x04, /* u8 iConfiguration; */ + 0b10100000, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 50, /* u8 MaxPower; */ + + /* Standard AudioControl Interface Descriptor */ + 0x09, /* u8 bLength; */ + 0x04, /* u8 bDescriptorType; */ + 0x00, /* u8 bInterfaceNumber; */ + 0x00, /* u8 bAlternateSetting; */ + 0x00, /* u8 bNumEndpoints; */ + 0x01, /* u8 bInterfaceClass; */ + 0x01, /* u8 bInterfaceSubclass; */ + 0x00, /* u8 bInterfaceProtocol; */ + 0x00, /* u8 iInterface; */ + + /* Class-specific Interface Descriptor */ + 0x09, /* u8 bLength; */ + 0x24, /* u8 bDescriptor Type (CS_INTERFACE); */ + 0x01, /* u8 bDescriptorSubType (HEADER); */ + 0x00, 0x01, /* u16 bcdADC; */ + 0x09 + 0x0c + 0x0d + 0x09, 0x00, /* u16 wTotalLength; */ + 0x01, /* u8 bInCollection; */ + 0x01, /* u8 baInterfaceNr; */ + + /* Input Terminal ID1 Descriptor */ + 0x0c, /* u8 bLength; */ + 0x24, /* u8 bDescriptor Type (CS_INTERFACE); */ + 0x02, /* u8 bDescriptorSubType (INPUT_TERMINAL); */ + 0x01, /* u8 bTerminalID; */ + 0x01, 0x01, /* u16 wTerminalType; */ + 0x00, /* u8 bAssocTerminal; */ + 0x02, /* u8 bNrChannels; */ + 0x03, 0x00, /* u16 wChannelConfig; */ + 0x00, /* u8 iChannelNames; */ + 0x00, /* u8 iTerminal; */ + + /* Feature Unit ID2 Descriptor */ + 0x09, /* u8 bLength; */ + 0x24, /* u8 bDescriptorType; */ + 0x06, /* u8 bDescriptorSubtype; */ + 0x02, /* u8 bUnitID; */ + 0x01, /* u8 bSourceID; */ + 0x02, /* u8 bControlSize; */ + 0x02, 0x00, /* u16 bmaControls(0); */ + 0x00, /* u8 iFeature; */ + + /* Output Terminal ID3 Descriptor */ + 0x09, /* u8 bLength; */ + 0x24, /* u8 bDescriptorType; */ + 0x03, /* u8 bDescriptorSubType; */ + 0x03, /* u8 bUnitID */ + 0x04, 0x03, /* u16 wTerminalType; */ + 0x00, /* u8 bAssocTerminal; */ + 0x02, /* u8 bSourceID; */ + 0x00, /* u8 iTerminal; */ + + /* Standard AudioStreaming Interface Descriptor */ + 0x09, /* u8 bLength; */ + 0x04, /* u8 bDescriptorType; */ + 0x01, /* u8 bInterfaceNumber; */ + 0x00, /* u8 bAlternateSetting; */ + 0x00, /* u8 bNumEndpoints; */ + 0x01, /* u8 bInterfaceClass; */ + 0x02, /* u8 bInterfaceSubclass; */ + 0x00, /* u8 bInterfaceProtocol; */ + 0x00, /* u8 iInterface; */ + + /* Standard AudioStreaming Interface Descriptor (non-zero-bandwidth) */ + 0x09, /* u8 bLength; */ + 0x04, /* u8 bDescriptorType; */ + 0x01, /* u8 bInterfaceNumber; */ + 0x01, /* u8 bAlternateSetting; */ + 0x01, /* u8 bNumEndpoints; */ + 0x01, /* u8 bInterfaceClass; */ + 0x02, /* u8 bInterfaceSubclass; */ + 0x00, /* u8 bInterfaceProtocol; */ + 0x00, /* u8 iInterface; */ + + /* Class-specific Interface Descriptor */ + 0x07, /* u8 bLength; */ + 0x24, /* u8 bDescriptorType; */ + 0x01, /* u8 bDescriptorSubtype; */ + 0x01, /* u8 bTerminalLink; */ + 0x00, /* u8 bDelay; */ + 0x01, 0x00, /* u16 wFormatTag; */ + + /* Type-I Format Descriptor */ + 0x0b, /* u8 bLength; */ + 0x24, /* u8 bDescriptorType; */ + 0x02, /* u8 bDescriptorSubtype; */ + 0x01, /* u8 bFormatType; */ + 0x02, /* u8 bNrChannels; */ + 0x02, /* u8 bSubFrameSize; */ + 16, /* u8 bBitResolution; */ + 1, /* u8 bSamFreqType; */ + 0x80, 0xbb, 0x00, /* u24 tSamFreq */ + + /* Endpoint Descriptor */ + 0x09, /* u8 bLength; */ + 0x05, /* u8 bDescriptorType; */ + 0x01, /* u8 bEndpointAddress; */ + 0x0d, /* u8 bmAttributes; */ + 48 * 2 * 2, 0, /* u16 wMaxPacketSize; */ + 0x01, /* u8 bInterval; */ + 0x00, /* u8 bRefresh; */ + 0x00, /* u8 bSynchAddress; */ + + /* Class-specific Endpoint Descriptor */ + 0x07, /* u8 bLength; */ + 0x25, /* u8 bDescriptorType; */ + 0x01, /* u8 bDescriptorSubtype; */ + // Reminder to ask the host to pad packets if this is not sufficient enough. + 0x00, /* u8 bmAttributes; */ + 0x00, /* u8 bLockDelayUnits; */ + 0x00, 0x00 /* u16 wLockDelay; */ +}; + +/* + * 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 usb_device_audio +{ + usb_device_c device; + + Fifo8 audio_buf; + int16_t buffer[SOUNDBUFLEN * 2]; + + int16_t vol; + + int alt_iface_enabled; +} usb_device_audio; + +int +usb_device_audio_handle_data(usb_device_c *device, USBPacket *p) +{ + int ret = 0; + usb_device_audio* usb_audio = (usb_device_audio*)device; + + // check that the length is <= the max packet size of the device + if (p->len > usb_device_get_mps(device, p->devep)) { + //BX_DEBUG(("EP%d transfer length (%d) is greater than Max Packet Size (%d).", p->devep, p->len, usb_device_get_mps(device, (p->devep)))); + } + + switch (p->pid) + { + case USB_TOKEN_OUT: + { + if (p->devep == 1 && usb_audio->alt_iface_enabled) + { + // Null packets must be accepted as well. + if (p->len > 0) + fifo8_push_all(&usb_audio->audio_buf, p->data, p->len); + } + else + { + goto fail; + } + } + break; + default: +fail: + device->stall = 1; + ret = USB_RET_STALL; + break; + } + + return ret; +} + +void +usb_device_audio_handle_reset(usb_device_c *device) +{ + usb_device_audio* usb_audio = (usb_device_audio*) device->priv; + + usb_audio->alt_iface_enabled = 0; +} + +void +usb_device_audio_handle_iface_change(usb_device_c* device, int iface) +{ + usb_device_audio* usb_audio = (usb_device_audio*) device->priv; + + usb_audio->alt_iface_enabled = !!iface; +} + +/* + * 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_device_audio_get_control(usb_device_audio *device, uint8_t attrib, + uint16_t cscn, uint16_t idif, + int length, uint8_t *data) +{ + uint8_t val = cscn >> 8; + uint8_t cn = cscn - 1; /* -1 for the non-present master control */ + uint32_t aid = ATTRIB_ID(val, attrib, idif); + int ret = USB_RET_STALL; + + switch (aid) + { + case ATTRIB_ID(VOLUME_CONTROL, CR_GET_CUR, 0x0200): + if (cn == 255) { + uint16_t vol = (uint16_t)device->vol; + data[0] = vol; + data[1] = vol >> 8; + ret = 2; + } + break; + case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MIN, 0x0200): + if (cn == 255) { + data[0] = 0x01; + data[1] = 0x80; + ret = 2; + } + break; + case ATTRIB_ID(VOLUME_CONTROL, CR_GET_MAX, 0x0200): + if (cn == 255) { + data[0] = 0x00; + data[1] = 0x00; + ret = 2; + } + break; + case ATTRIB_ID(VOLUME_CONTROL, CR_GET_RES, 0x0200): + if (cn == 255) { + data[0] = 0x01; + data[1] = 0x00; + ret = 2; + } + break; + } + + return ret; +} + +static int usb_device_audio_set_control(usb_device_audio *device, uint8_t attrib, + uint16_t cscn, uint16_t idif, + int length, uint8_t *data) +{ + uint8_t val = cscn >> 8; + uint8_t cn = cscn - 1; /* -1 for the non-present master control */ + uint32_t aid = ATTRIB_ID(val, attrib, idif); + int ret = USB_RET_STALL; + + if (aid == ATTRIB_ID(VOLUME_CONTROL, CR_SET_CUR, 0x0200) && cn == 255) + { + int16_t vol = data[0] + (data[1] << 8); + device->vol = vol; + ret = 0; + } + + return ret; +} + +int +usb_device_audio_handle_control(usb_device_c *device, int request, int value, int index, int length, uint8_t *data) +{ + int ret = 0; + usb_device_audio* usb_audio = (usb_device_audio*) device->priv; + + ret = usb_device_handle_control_common(device, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + switch (request) + { + case DeviceOutRequest | USB_REQ_CLEAR_FEATURE: + goto fail; + break; + case DeviceOutRequest | USB_REQ_SET_FEATURE: + goto fail; + break; + case EndpointRequest | USB_REQ_GET_STATUS: + // if the endpoint is currently halted, return bit 0 = 1 + if (value == USB_ENDPOINT_HALT) { + if (index == 0x81) { + data[0] = 0x00 | (usb_device_get_halted(device, index) ? 1 : 0); + data[1] = 0x00; + ret = 2; + } else { + goto fail; + } + } else { + goto fail; + } + break; + /* Class-specific requests. */ + case DeviceClassInRequest | CR_GET_CUR: + case DeviceClassInRequest | CR_GET_MIN: + case DeviceClassInRequest | CR_GET_MAX: + case DeviceClassInRequest | CR_GET_RES: + ret = usb_device_audio_get_control(usb_audio, request & 0xff, value, index, + length, data); + break; + + + case DeviceOutClassRequest | CR_SET_CUR: + case DeviceOutClassRequest | CR_SET_MIN: + case DeviceOutClassRequest | CR_SET_MAX: + case DeviceOutClassRequest | CR_SET_RES: + ret = usb_device_audio_set_control(usb_audio, request & 0xff, value, index, + length, data); + break; + + default: + fail: + device->stall = 1; + ret = USB_RET_STALL; + break; + } + return ret; +} + +static void +usb_audio_get_buffer(int32_t *buffer, int len, void *priv) +{ + usb_device_audio* usb_audio = (usb_device_audio*)priv; + + if (usb_audio->alt_iface_enabled == 0) + return; + + if (fifo8_num_used(&usb_audio->audio_buf) < (SOUNDBUFLEN * 2 * 2)) + { + return; + } + + fifo8_pop_buf(&usb_audio->audio_buf, (uint8_t*)usb_audio->buffer, sizeof (usb_audio->buffer)); + if (usb_audio->vol == ((int16_t)0x8000)) + return; // Utter silence. + + double decibels = (usb_audio->vol / -32768.0) * 128.0; + double gain = (double)pow(10, (double)-decibels / 20.0); + + for (int c = 0; c < len * 2; c++) + buffer[c] = usb_audio->buffer[c] * gain; +} + +void * +usb_audio_device_create(const device_t *info) +{ + usb_device_audio* usb_audio = (usb_device_audio*) calloc(1, sizeof(usb_device_audio)); + usb_port_t* port = usb_search_for_ports(); + + if (!port) { + free(usb_audio); + return NULL; + } + + usb_device_create(&usb_audio->device); + usb_audio->device.type = 0; + usb_audio->device.minspeed = USB_SPEED_LOW; + usb_audio->device.maxspeed = USB_SPEED_FULL; + usb_audio->device.speed = usb_audio->device.minspeed; + usb_audio->device.priv = usb_audio; + + usb_audio->device.vendor_desc = "86Box"; + usb_audio->device.product_desc = "USB Audio"; + usb_audio->device.serial_num = "1"; + + usb_audio->device.config_descriptor = bx_audio_config_descriptor; + usb_audio->device.config_desc_size = sizeof(bx_audio_config_descriptor); + usb_audio->device.dev_descriptor = bx_audio_dev_descriptor; + usb_audio->device.device_desc_size = sizeof(bx_audio_dev_descriptor); + + usb_audio->device.endpoint_info[USB_CONTROL_EP].max_packet_size = 64; // Control ep0 + usb_audio->device.endpoint_info[USB_CONTROL_EP].max_burst_size = 0; + usb_audio->device.endpoint_info[1].max_packet_size = 192; // Out ep1 + usb_audio->device.endpoint_info[1].max_burst_size = 0; + usb_audio->device.connected = true; + usb_audio->device.iface_alt = 1; + + usb_audio->device.handle_iface_change = usb_device_audio_handle_iface_change; + usb_audio->device.handle_control = usb_device_audio_handle_control; + usb_audio->device.handle_data = usb_device_audio_handle_data; + usb_audio->device.handle_reset = usb_device_audio_handle_reset; + + if (!port->connect(port, &usb_audio->device)) { + free(usb_audio); + return NULL; + } + + fifo8_create(&usb_audio->audio_buf, 65536); + + sound_add_handler(usb_audio_get_buffer, usb_audio); + return usb_audio; +} + +void +usb_device_audio_destroy(void* priv) +{ + usb_device_audio* usb_audio = (usb_device_audio*) priv; + + fifo8_destroy(&usb_audio->audio_buf); + free(usb_audio); +} + +const device_t usb_audio_device = { + .name = "USB Audio", + .internal_name = "usb_audio", + .flags = DEVICE_USB, + .local = 0, + .init = usb_audio_device_create, + .close = usb_device_audio_destroy, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; diff --git a/src/usb/usb_common.c b/src/usb/usb_common.c index 7b9b3d5fa0..eaf8b7906a 100644 --- a/src/usb/usb_common.c +++ b/src/usb/usb_common.c @@ -139,6 +139,7 @@ int usb_device_hc_event(usb_device_c* host, int event, usb_device_c *device) { bool usb_device_init(usb_device_c* device) { + device->connected = 1; return device->connected; } @@ -150,6 +151,7 @@ void usb_device_create(usb_device_c* device) device->async_mode = 1; device->speed = USB_SPEED_LOW; device->first8 = 0; + device->iface_alt = 0; for (i = 0; i < USB_MAX_ENDPOINTS; i++) device->endpoint_info[i].toggle = 0; @@ -360,12 +362,12 @@ int usb_device_handle_control_common(usb_device_c* device, int request, int valu // Ben: TODO: If the device is not in the configured state, this request should stall BX_DEBUG(("USB_REQ_GET_INTERFACE:")); // with InterfaceRequest, the wValue field must be zero and wLength field must be 1 - if ((value != 0) || (length != 1)) { - BX_ERROR(("USB_REQ_GET_INTERFACE: This type of request requires the wValue field to be zero and wLength field to be one.")); + if ((value != device->iface_alt) || (length != 1)) { + //BX_ERROR(("USB_REQ_GET_INTERFACE: This type of request requires the wValue field to be zero and wLength field to be one.")); } // all our devices only have one interface, and that value must be zero // if we ever add a device that has more than one interface (a video cam ?), we will need to modify this - if (index == 0) { + if (index == device->iface_alt) { data[0] = device->alt_iface; ret = 1; } @@ -374,12 +376,12 @@ int usb_device_handle_control_common(usb_device_c* device, int request, int valu // Ben: TODO: If the device is not in the configured state, this request should stall BX_DEBUG(("USB_REQ_SET_INTERFACE: value=%d", value)); // with InterfaceRequest, the wIndex and wLength fields must be zero - if ((index != 0) || (length != 0)) { - BX_ERROR(("USB_REQ_SET_INTERFACE: This type of request requires the wIndex and wLength fields to be zero.")); + if ((index != device->iface_alt) || (length != 0)) { + //BX_ERROR(("USB_REQ_SET_INTERFACE: This type of request requires the wIndex and wLength fields to be zero.")); } // all our devices only have one interface, and that value must be zero // if we ever add a device that has more than one interface (a video cam ?), we will need to modify this - if ((index == 0) && (value <= device->alt_iface_max)) { + if ((index == device->iface_alt) && (value <= device->alt_iface_max)) { device->alt_iface = value; // alternate interface device->handle_iface_change(device, value); // let the device know we changed the interface number ret = 0; diff --git a/src/usb/usb_common.h b/src/usb/usb_common.h index 55f320d511..c592779231 100644 --- a/src/usb/usb_common.h +++ b/src/usb/usb_common.h @@ -206,6 +206,8 @@ struct usb_device_c { uint8_t alt_iface; /* Maximum alternate interface usable. */ uint8_t alt_iface_max; + /* Interface with alternate settings. */ + uint8_t iface_alt; // This naming is terrible... /* Name of device. */ char devname[32]; /* Endpoints. */ @@ -227,8 +229,8 @@ struct usb_device_c { /* Setup packets' state information. */ int state; - uint8_t setup_buf[8]; - uint8_t data_buf[1024]; + uint8_t setup_buf[64]; + uint8_t data_buf[1280]; int remote_wakeup; int setup_state; int setup_len; @@ -259,6 +261,9 @@ struct usb_device_c { void (*handle_iface_change)(usb_device_c* device, int iface); /* Initializes the device. Returns true on success. */ bool (*init)(usb_device_c *init); + + /* Opaque data. */ + void* priv; }; /* Default inits the device. */ @@ -292,7 +297,7 @@ bool usb_device_get_halted(usb_device_c* device, int ep); int usb_set_usb_string(uint8_t *buf, const char *str); /* Get maximum packet size the device can handle for an endpoint. */ int usb_device_get_mps(usb_device_c* device, const int ep); -/* Events to send to the host controller. */ +/* Events to send to the host controller. Send USB_EVENT_WAKEUP to wake up the host. */ extern int usb_device_hc_event(usb_device_c* host, int event, usb_device_c *device); #endif \ No newline at end of file