Merge pull request #3324 from Cacodemon345/usb-work

USB: Finish work on OHCI
This commit is contained in:
Miran Grča
2023-05-10 13:34:02 +02:00
committed by GitHub
3 changed files with 424 additions and 71 deletions

View File

@@ -30,7 +30,6 @@
#include <86box/pit.h>
#include <86box/device.h>
#include <86box/port_92.h>
#include <86box/usb.h>
#include <86box/hdc.h>
#include <86box/hdc_ide.h>
#include <86box/chipset.h>

View File

@@ -23,6 +23,29 @@ extern "C" {
#endif
typedef struct usb_t usb_t;
typedef struct usb_device_t usb_device_t;
enum usb_pid
{
USB_PID_OUT = 0xE1,
USB_PID_IN = 0x69,
USB_PID_SETUP = 0x2D
};
enum usb_errors
{
USB_ERROR_NO_ERROR = 0,
USB_ERROR_NAK = 1,
USB_ERROR_OVERRUN = 2,
USB_ERROR_UNDERRUN = 3
};
enum usb_bus_types
{
USB_BUS_OHCI = 0,
USB_BUS_UHCI,
USB_BUS_MAX
};
/* USB device creation parameters struct */
typedef struct
@@ -52,6 +75,10 @@ typedef struct usb_t
pc_timer_t ohci_frame_timer;
pc_timer_t ohci_port_reset_timer[2];
uint8_t ohci_interrupt_counter : 3;
usb_device_t* ohci_devices[2];
usb_device_t* uhci_devices[2];
uint8_t ohci_usb_buf[4096];
uint8_t ohci_initial_start;
usb_params_t* usb_params;
} usb_t;
@@ -65,6 +92,66 @@ typedef struct
uint8_t bDescriptorType;
} usb_desc_base_t;
enum usb_desc_setup_req_types
{
USB_SETUP_TYPE_DEVICE = 0x0,
USB_SETUP_TYPE_INTERFACE = 0x1,
USB_SETUP_TYPE_ENDPOING = 0x2,
USB_SETUP_TYPE_OTHER = 0x3,
};
#define USB_SETUP_TYPE_MAX 0x1F
#define USB_SETUP_DEV_TO_HOST 0x80
typedef struct
{
uint8_t bmRequestType;
uint8_t bRequest;
uint16_t wValue;
uint16_t wIndex;
uint16_t wLength;
} usb_desc_setup_t;
typedef struct
{
usb_desc_base_t base;
uint8_t bEndpointAddress;
uint8_t bmAttributes;
uint16_t wMaxPacketSize;
uint8_t bInterval;
} usb_desc_endpoint_t;
typedef struct
{
usb_desc_base_t base;
uint16_t bcdHID;
uint8_t bCountryCode;
uint8_t bNumDescriptors;
uint8_t bDescriptorType;
uint16_t wDescriptorLength;
} usb_desc_hid_t;
typedef struct
{
usb_desc_base_t base;
uint8_t bInterfaceNumber;
uint8_t bAlternateSetting;
uint8_t bNumEndpoints;
uint8_t bInterfaceClass;
uint8_t bInterfaceSubClass;
uint8_t bInterfaceProtocol;
uint8_t iInterface;
} usb_desc_interface_t;
typedef struct
{
usb_desc_base_t base;
uint16_t bString[];
} usb_desc_string_t;
typedef struct
{
usb_desc_base_t base;
@@ -77,34 +164,48 @@ typedef struct
uint8_t bMaxPower;
} usb_desc_conf_t;
typedef struct
{
usb_desc_base_t base;
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize;
uint16_t idVendor;
uint16_t idProduct;
uint16_t bcdDevice;
uint8_t iManufacturer;
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
} usb_desc_device_t;
#pragma pack(pop)
/* USB endpoint device struct. Incomplete and unused. */
typedef struct
typedef struct usb_device_t
{
uint16_t vendor_id;
uint16_t device_id;
usb_desc_device_t device_desc;
struct {
usb_desc_conf_t conf_desc;
usb_desc_base_t* other_descs[16];
} conf_desc_items;
/* Reads from endpoint. Non-zero value indicates error. */
uint8_t (*device_in)(void* priv, uint8_t* data, uint32_t len);
/* Writes to endpoint. Non-zero value indicates error. */
uint8_t (*device_out)(void* priv, uint8_t* data, uint32_t len);
/* Process setup packets. */
uint8_t (*device_setup)(void* priv, uint8_t* data);
/* Device reset */
/* General-purpose function for I/O. Non-zero value indicates error. */
uint8_t (*device_process)(void* priv, uint8_t* data, uint32_t *len, uint8_t pid_token, uint8_t endpoint, uint8_t underrun_not_allowed);
/* Device reset. */
void (*device_reset)(void* priv);
/* Get address. */
uint8_t (*device_get_address)(void* priv);
void* priv;
} usb_device_t;
enum usb_bus_types
{
USB_BUS_OHCI = 0,
USB_BUS_UHCI = 1
};
/* Global variables. */
extern const device_t usb_device;
extern usb_t* usb_device_inst;
/* Functions. */
extern void uhci_update_io_mapping(usb_t *dev, uint8_t base_l, uint8_t base_h, int enable);

359
src/usb.c
View File

@@ -22,6 +22,7 @@
#include <string.h>
#include <stdbool.h>
#include <wchar.h>
#include <assert.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/device.h>
@@ -128,6 +129,8 @@ enum
OHCI_HcControl_BulkListEnable = 1 << 4
};
usb_t* usb_device_inst = NULL;
static void
usb_interrupt_ohci(usb_t *dev, uint32_t level)
{
@@ -238,20 +241,7 @@ typedef struct
/* Transfer descriptors */
typedef struct
{
union
{
uint32_t Control;
struct
{
uint32_t Reserved : 18;
uint8_t bufferRounding : 1;
uint8_t Direction : 2;
uint8_t DelayInterrupt : 3;
uint8_t DataToggle : 2;
uint8_t ErrorCount : 2;
uint8_t ConditionCode : 4;
} flags;
};
uint32_t Control;
uint32_t CBP;
uint32_t NextTD;
uint32_t BE;
@@ -260,31 +250,9 @@ typedef struct
/* Endpoint descriptors */
typedef struct
{
union
{
uint32_t Control;
struct
{
uint8_t FunctionAddress : 7;
uint8_t EndpointNumber : 4;
uint8_t Direction : 2;
bool Speed : 1;
bool Skip : 1;
bool Format : 1;
uint16_t MaximumPacketSize : 11;
uint8_t Reserved : 5;
} flags;
};
uint32_t Control;
uint32_t TailP;
union
{
uint32_t HeadP;
struct
{
bool Halted : 1;
bool toggleCarry : 1;
} flags_2;
};
uint32_t HeadP;
uint32_t NextED;
} usb_ed_t;
@@ -368,22 +336,228 @@ ohci_set_interrupt(usb_t *dev, uint8_t bit)
dev->ohci_mmio[OHCI_HcInterruptStatus].b[0] |= bit;
/* TODO: Does setting UnrecoverableError also assert PERR# on any emulated USB chipsets? */
ohci_update_irq(dev);
}
/* Next two functions ported over from QEMU. */
static int ohci_copy_td_input(usb_t* dev, usb_td_t *td,
uint8_t *buf, int len)
{
uint32_t ptr, n;
ptr = td->CBP;
n = 0x1000 - (ptr & 0xfff);
if (n > len) {
n = len;
}
dma_bm_write(ptr, buf, n, 1);
if (n == len) {
return 0;
}
ptr = td->BE & ~0xfffu;
buf += n;
dma_bm_write(ptr, buf, len - n, 1);
return 0;
}
static int ohci_copy_td_output(usb_t* dev, usb_td_t *td,
uint8_t *buf, int len)
{
uint32_t ptr, n;
ptr = td->CBP;
n = 0x1000 - (ptr & 0xfff);
if (n > len) {
n = len;
}
dma_bm_read(ptr, buf, n, 1);
if (n == len) {
return 0;
}
ptr = td->BE & ~0xfffu;
buf += n;
dma_bm_read(ptr, buf, len - n, 1);
return 0;
}
#define OHCI_TD_DIR(val) ((val >> 19) & 3)
#define OHCI_ED_DIR(val) ((val >> 11) & 3)
uint8_t
ohci_service_transfer_desc(usb_t* dev, usb_ed_t* endpoint_desc)
{
uint32_t td_addr = endpoint_desc->HeadP & ~(0xf);
usb_td_t td;
uint8_t dir, pid_token = 255;
uint32_t len = 0, pktlen = 0;
uint32_t actual_length = 0;
uint32_t i = 0;
uint8_t device_result = 0;
usb_device_t* target = NULL;
dma_bm_read(td_addr, (uint8_t*)&td, sizeof(usb_td_t), 4);
switch (dir = OHCI_ED_DIR(endpoint_desc->Control)) {
case 1:
case 2:
break;
default:
dir = OHCI_TD_DIR(td.Control);
break;
}
switch (dir) {
case 0: /* Setup */
pid_token = USB_PID_SETUP;
break;
case 1: /* OUT */
pid_token = USB_PID_OUT;
break;
case 2: /* IN */
pid_token = USB_PID_IN;
break;
default:
return 1;
}
if (td.CBP && td.BE) {
if ((td.CBP & 0xfffff000) != (td.BE & 0xfffff000)) {
len = (td.BE & 0xfff) + 0x1001 - (td.CBP & 0xfff);
} else {
if (td.CBP > td.BE) {
ohci_set_interrupt(dev, OHCI_HcInterruptEnable_UE);
return 1;
}
len = (td.BE - td.CBP) + 1;
}
if (len > sizeof(dev->ohci_usb_buf)) {
len = sizeof(dev->ohci_usb_buf);
}
pktlen = len;
if (len && pid_token != USB_PID_IN) {
pktlen = (endpoint_desc->Control >> 16) & 0xFFF;
if (pktlen > len) {
pktlen = len;
}
ohci_copy_td_output(dev, &td, dev->ohci_usb_buf, pktlen);
}
}
for (i = 0; i < 2; i++) {
if (!dev->ohci_devices[i])
continue;
assert(dev->ohci_devices[i]->device_get_address != NULL);
if (dev->ohci_devices[i]->device_get_address(dev->ohci_devices[i]->priv) != (endpoint_desc->Control & 0x7f))
continue;
target = dev->ohci_devices[i];
break;
}
if (!target)
return 1;
device_result = target->device_process(target->priv, dev->ohci_usb_buf, &actual_length, pid_token, (endpoint_desc->Control & 0x780) >> 7, !(endpoint_desc->Control & (1 << 18)));
if ((actual_length == pktlen) || (pid_token == USB_PID_IN && (endpoint_desc->Control & (1 << 18)) && device_result == USB_ERROR_NO_ERROR)) {
if (len == actual_length) {
td.CBP = 0;
} else {
if ((td.CBP & 0xfff) + actual_length > 0xfff) {
td.CBP = (td.BE & ~0xfff) + ((td.CBP + actual_length) & 0xfff);
} else {
td.CBP += actual_length;
}
}
td.Control |= (1 << 25); /* dataToggle[1] */
td.Control ^= (1 << 24); /* dataToggle[0] */
td.Control &= ~0xFC000000; /* Set both ErrorCount and ConditionCode to 0. */
if (pid_token != USB_PID_IN && len != actual_length) {
goto exit_no_retire;
}
endpoint_desc->HeadP &= ~0x2;
if (td.Control & (1 << 24)) {
endpoint_desc->HeadP |= 0x2;
}
} else {
if (actual_length != 0xFFFFFFFF && actual_length >= 0) {
td.Control &= ~0xF0000000;
td.Control |= 0x90000000;
} else {
switch (device_result) {
case USB_ERROR_NAK:
return 1;
}
dev->ohci_interrupt_counter = 0;
}
endpoint_desc->HeadP |= 0x1;
}
endpoint_desc->HeadP &= 0xf;
endpoint_desc->HeadP |= td.NextTD & ~0xf;
td.NextTD = dev->ohci_mmio[OHCI_HcDoneHead].l;
dev->ohci_mmio[OHCI_HcDoneHead].l = td_addr;
i = (td.Control >> 21) & 7;
if (i < dev->ohci_interrupt_counter) {
dev->ohci_interrupt_counter = i;
}
exit_no_retire:
dma_bm_write(td_addr, (uint8_t*)&td, sizeof(usb_td_t), 4);
return !(td.Control & 0xF0000000);
}
uint8_t
ohci_service_endpoint_desc(usb_t* dev, uint32_t head)
{
usb_ed_t endpoint_desc;
uint8_t active = 0;
uint32_t next = 0;
uint32_t cur = 0;
uint32_t limit_counter = 0;
if (head == 0)
return 0;
return 0;
for (cur = head; cur && limit_counter++ < ENDPOINT_DESC_LIMIT; cur = next) {
dma_bm_read(cur, (uint8_t*)&endpoint_desc, sizeof(usb_ed_t), 4);
next = endpoint_desc.NextED & ~(0xFu);
if ((endpoint_desc.Control & (1 << 13)) || (endpoint_desc.HeadP & (1 << 0)))
continue;
if (endpoint_desc.Control & 0x8000) {
fatal("OHCI: Isochronous transfers not implemented!\n");
}
active = 1;
while ((endpoint_desc.HeadP & ~(0xFu)) != endpoint_desc.TailP) {
ohci_service_transfer_desc(dev, &endpoint_desc);
}
dma_bm_write(cur, (uint8_t*)&endpoint_desc, sizeof(usb_ed_t), 4);
}
return active;
}
void
ohci_end_of_frame(usb_t* dev)
{
usb_hcca_t hcca;
/* TODO: Put endpoint and transfer descriptor processing here. */
if (dev->ohci_initial_start)
return;
dma_bm_read(dev->ohci_mmio[OHCI_HcHCCA].l, (uint8_t*)&hcca, sizeof(usb_hcca_t), 4);
if (dev->ohci_mmio[OHCI_HcControl].l & OHCI_HcControl_PeriodicListEnable) {
@@ -436,6 +610,7 @@ ohci_end_of_frame(usb_t* dev)
void
ohci_start_of_frame(usb_t* dev)
{
dev->ohci_initial_start = 0;
ohci_set_interrupt(dev, OHCI_HcInterruptEnable_SO);
}
@@ -476,6 +651,23 @@ ohci_port_reset_callback_2(void* priv)
dev->ohci_mmio[OHCI_HcRhPortStatus2].b[2] |= 0x10;
}
static void
ohci_soft_reset(usb_t* dev)
{
uint32_t old_HcControl = (dev->ohci_mmio[OHCI_HcControl].l & 0x100) | 0xc0;
memset(dev->ohci_mmio, 0x00, 4096);
dev->ohci_mmio[OHCI_HcRevision].b[0] = 0x10;
dev->ohci_mmio[OHCI_HcRevision].b[1] = 0x01;
dev->ohci_mmio[OHCI_HcRhDescriptorA].b[0] = 0x02;
dev->ohci_mmio[OHCI_HcRhDescriptorA].b[1] = 0x02;
dev->ohci_mmio[OHCI_HcFmInterval].l = 0x27782edf; /* FrameInterval = 11999, FSLargestDataPacket = 10104 */
dev->ohci_mmio[OHCI_HcLSThreshold].l = 0x628;
dev->ohci_mmio[OHCI_HcInterruptEnable].l |= (1 << 31);
dev->ohci_mmio[OHCI_HcControl].l = old_HcControl;
dev->ohci_interrupt_counter = 7;
ohci_update_irq(dev);
}
static void
ohci_mmio_write(uint32_t addr, uint8_t val, void *p)
{
@@ -490,25 +682,36 @@ ohci_mmio_write(uint32_t addr, uint8_t val, void *p)
switch (addr) {
case OHCI_aHcControl:
old = dev->ohci_mmio[OHCI_HcControl].b[0];
#ifdef ENABLE_USB_LOG
usb_log("OHCI: OHCI state 0x%X\n", (val & 0xc0));
#endif
if ((val & 0xc0) == 0x00) {
/* UsbReset */
dev->ohci_mmio[OHCI_HcRhPortStatus1].b[2] = dev->ohci_mmio[OHCI_HcRhPortStatus2].b[2] = 0x16;
for (int i = 0; i < 2; i++) {
if (dev->ohci_devices[i]) {
dev->ohci_devices[i]->device_reset(dev->ohci_devices[i]->priv);
}
}
} else if ((val & 0xc0) == 0x80 && (old & 0xc0) != (val & 0xc0)) {
dev->ohci_mmio[OHCI_HcFmRemaining].l = 0;
dev->ohci_initial_start = 1;
timer_on_auto(&dev->ohci_frame_timer, 1000.);
}
break;
case OHCI_aHcCommandStatus:
/* bit OwnershipChangeRequest triggers an ownership change (SMM <-> OS) */
if (val & 0x08) {
dev->ohci_mmio[OHCI_HcInterruptStatus].b[3] = 0x40;
if ((dev->ohci_mmio[OHCI_HcInterruptEnable].b[3] & 0xc0) == 0xc0)
if ((dev->ohci_mmio[OHCI_HcInterruptEnable].b[3] & 0x40) == 0x40) {
smi_raise();
}
}
/* bit HostControllerReset must be cleared for the controller to be seen as initialized */
if (val & 0x01) {
memset(dev->ohci_mmio, 0x00, 4096);
dev->ohci_mmio[OHCI_HcRevision].b[0] = 0x10;
dev->ohci_mmio[OHCI_HcRevision].b[1] = 0x01;
dev->ohci_mmio[OHCI_HcRhDescriptorA].b[0] = 0x02;
ohci_soft_reset(dev);
val &= ~0x01;
}
break;
@@ -649,13 +852,19 @@ ohci_mmio_write(uint32_t addr, uint8_t val, void *p)
if (old & 0x01) {
dev->ohci_mmio[addr >> 2].b[addr & 3] |= 0x10;
timer_on_auto(&dev->ohci_port_reset_timer[(addr - OHCI_aHcRhPortStatus1) / 4], 10000.);
if (dev->ohci_devices[(addr - OHCI_aHcRhPortStatus1) >> 2])
dev->ohci_devices[(addr - OHCI_aHcRhPortStatus1) >> 2]->device_reset(dev->ohci_devices[(addr - OHCI_aHcRhPortStatus1) >> 2]->priv);
} else
dev->ohci_mmio[(addr + 2) >> 2].b[(addr + 2) & 3] |= 0x01;
}
if (val & 0x08)
dev->ohci_mmio[addr >> 2].b[addr & 3] &= ~0x04;
if (val & 0x04)
dev->ohci_mmio[addr >> 2].b[addr & 3] |= 0x04;
if (val & 0x04) {
if (old & 0x01)
dev->ohci_mmio[addr >> 2].b[addr & 3] |= 0x04;
else
dev->ohci_mmio[(addr + 2) >> 2].b[(addr + 2) & 3] |= 0x01;
}
if (val & 0x02) {
if (old & 0x01)
dev->ohci_mmio[addr >> 2].b[addr & 3] |= 0x02;
@@ -744,13 +953,58 @@ ohci_update_mem_mapping(usb_t *dev, uint8_t base1, uint8_t base2, uint8_t base3,
uint8_t
usb_attach_device(usb_t *dev, usb_device_t* device, uint8_t bus_type)
{
switch (bus_type) {
case USB_BUS_OHCI:
{
for (int i = 0; i < 2; i++) {
if (!dev->ohci_devices[i]) {
uint32_t old = dev->ohci_mmio[OHCI_HcRhPortStatus1 + (4 * i)].l;
dev->ohci_devices[i] = device;
dev->ohci_mmio[OHCI_HcRhPortStatus1 + (4 * i)].b[0] |= 0x1;
if ((dev->ohci_mmio[OHCI_HcControl].b[0] & 0xc0) == 0xc0) {
ohci_set_interrupt(dev, OHCI_HcInterruptEnable_RD);
}
if (old != dev->ohci_mmio[OHCI_HcRhPortStatus1 + (4 * i)].l) {
dev->ohci_mmio[OHCI_HcRhPortStatus1 + (4 * i)].b[2] |= 0x1;
ohci_set_interrupt(dev, OHCI_HcInterruptEnable_RHSC);
}
return i;
}
}
}
break;
}
return 255;
}
void
usb_detach_device(usb_t *dev, uint8_t port, uint8_t bus_type)
{
/* Unused. */
switch (bus_type) {
case USB_BUS_OHCI:
{
if (port > 2)
return;
if (dev->ohci_devices[port]) {
uint32_t old = dev->ohci_mmio[OHCI_HcRhPortStatus1 + (4 * port)].l;
dev->ohci_devices[port] = NULL;
if (dev->ohci_mmio[OHCI_HcRhPortStatus1 + (4 * port)].b[0] & 0x1) {
dev->ohci_mmio[OHCI_HcRhPortStatus1 + (4 * port)].b[0] &= ~0x1;
dev->ohci_mmio[OHCI_HcRhPortStatus1 + (4 * port)].b[2] |= 0x1;
}
if (dev->ohci_mmio[OHCI_HcRhPortStatus1 + (4 * port)].b[0] & 0x2) {
dev->ohci_mmio[OHCI_HcRhPortStatus1 + (4 * port)].b[0] &= ~0x2;
dev->ohci_mmio[OHCI_HcRhPortStatus1 + (4 * port)].b[2] |= 0x2;
}
if (old != dev->ohci_mmio[OHCI_HcRhPortStatus1 + (4 * port)].l)
ohci_set_interrupt(dev, OHCI_HcInterruptEnable_RHSC);
return;
}
}
break;
}
return;
}
static void
@@ -762,11 +1016,8 @@ usb_reset(void *priv)
dev->uhci_io[0x0c] = 0x40;
dev->uhci_io[0x10] = dev->uhci_io[0x12] = 0x80;
memset(dev->ohci_mmio, 0x00, sizeof(dev->ohci_mmio));
dev->ohci_mmio[OHCI_HcRevision].b[0] = 0x10;
dev->ohci_mmio[OHCI_HcRevision].b[1] = 0x01;
dev->ohci_mmio[OHCI_HcRhDescriptorA].b[0] = 0x02;
dev->ohci_mmio[OHCI_HcRhDescriptorA].b[1] = 0x02;
ohci_soft_reset(dev);
dev->ohci_mmio[OHCI_HcControl].l = 0x00;
io_removehandler(dev->uhci_io_base, 0x20, uhci_reg_read, NULL, NULL, uhci_reg_write, uhci_reg_writew, NULL, dev);
dev->uhci_enable = 0;
@@ -811,6 +1062,8 @@ usb_init_ext(const device_t *info, void *params)
usb_reset(dev);
usb_device_inst = dev;
return dev;
}