// SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2021-2022 Martin Whitaker. #include #include #include #include "heap.h" #include "io.h" #include "memrw32.h" #include "memsize.h" #include "pci.h" #include "usb.h" #include "string.h" #include "unistd.h" #include "uhci.h" //------------------------------------------------------------------------------ // Constants //------------------------------------------------------------------------------ // Values defined by the UHCI specification. // Basic limits #define UHCI_FL_LENGTH 1024 // Maximum number of entries in periodic frame list // Register addresses in PCI Config space #define UHCI_LEGSUP 0xc0 // Legacy Support register // Legacy Support register #define UHCI_LEGSUP_TBY60R 0x0100 // Trap By 0x60 Read Status/Clear #define UHCI_LEGSUP_TBY60W 0x0200 // Trap By 0x60 Write Status/Clear #define UHCI_LEGSUP_TBY64R 0x0400 // Trap By 0x64 Read Status/Clear #define UHCI_LEGSUP_TBY64W 0x0800 // Trap By 0x64 Write Status/Clear #define UHCI_LEGSUP_A20PTS 0x8000 // End of A20GATE Pass Through Status/Clear #define UHCI_LEGSUP_CLEAR (UHCI_LEGSUP_TBY60R | UHCI_LEGSUP_TBY60W | UHCI_LEGSUP_TBY64R | UHCI_LEGSUP_TBY64W | UHCI_LEGSUP_A20PTS) // Register addresses in I/O space #define UHCI_USBCMD (io_base+0x00) // USB Command register #define UHCI_USBSTS (io_base+0x02) // USB Status register #define UHCI_USBINTR (io_base+0x04) // USB Interrupt Enable register #define UHCI_FRNUM (io_base+0x06) // Frame Number register #define UHCI_FLBASE (io_base+0x08) // Frame List Base Address register #define UHCI_SOF (io_base+0x0c) // Start of Frame modify register #define UHCI_PORT_SC(n) (io_base+0x10+2*n) // Port n Status and Control register (n = 0..MaxPortIdx) // USB Command register #define UHCI_USBCMD_R_S 0x0001 // Run/Stop #define UHCI_USBCMD_HCR 0x0002 // Host Controller Reset #define UHCI_USBCMD_GR 0x0004 // Global Reset #define UHCI_USBCMD_MAXP 0x0080 // Max Packet // USB Status register #define UHCI_USBSTS_INT 0x0001 // Interrupt #define UHCI_USBSTS_ERR 0x0002 // Error interrupt #define UHCI_USBSTS_RESUME 0x0004 // Resume Detect #define UHCI_USBSTS_HSE 0x0008 // Host System Error #define UHCI_USBSTS_HCPE 0x0010 // Host Controller Processor Error #define UHCI_USBSTS_HCH 0x0020 // Host Controller Halted // USB Interrupt Enable register #define UHCI_USBINTR_NONE 0x0000 // No interrupts enabled // USB SOF register #define UHCI_SOF_DEFAULT 0x40 // Default timing (1ms with 12MHz clock) // Port Status and Control register #define UHCI_PORT_SC_CCS 0x0001 // Current Connect Status #define UHCI_PORT_SC_CCSC 0x0002 // Current Connect Status Change #define UHCI_PORT_SC_PED 0x0004 // Port Enable/Disable #define UHCI_PORT_SC_PEDC 0x0008 // Port Enable/Disable Change #define UHCI_PORT_SC_RESUME 0x0040 // Resume Detect #define UHCI_PORT_SC_VALID 0x0080 // Reserved bit (always set) #define UHCI_PORT_SC_LSDA 0x0100 // Low Speed Device Attached #define UHCI_PORT_SC_PR 0x0200 // Port Reset #define UHCI_PORT_SC_SUSPEND 0x1000 // Suspend // Link Pointer #define UHCI_LP_TERMINATE 0x00000001 // Terminate (T) bit #define UHCI_LP_TYPE_TD (0 << 1) // Type is Transfer Descriptor #define UHCI_LP_TYPE_QH (1 << 1) // Type is Queue Head #define UHCI_LP_BREADTH_FIRST (0 << 2) #define UHCI_LP_DEPTH_FIRST (1 << 2) // Queue Element Transfer Descriptor data structure // - control_status member (32 bits) #define UHCI_TD_BS_ERR 0x00020000 // Bitstuff Error #define UHCI_TD_CRC_TO 0x00040000 // CRC or Time Out Error #define UHCI_TD_NAK_RX 0x00080000 // NAK received #define UHCI_TD_BABBLE 0x00100000 // Babble Detected #define UHCI_TD_DB_ERR 0x00200000 // Data Buffer Error #define UHCI_TD_STALLED 0x00400000 // Halted #define UHCI_TD_ACTIVE 0x00800000 // Active #define UHCI_TD_IOC_N (0 << 24) // Interrupt On Completion is off #define UHCI_TD_IOC_Y (1 << 24) // Interrupt On Completion is on #define UHCI_TD_ISO (1 << 25) // Isochronous Select #define UHCI_TD_FULL_SPEED (0 << 26) // Full Speed #define UHCI_TD_LOW_SPEED (1 << 26) // Low Speed #define UHCI_TD_CERR(n) ((n) << 27) // Error Counter = n (n = 1..3) #define UHCI_TD_SPD (1 << 29) // Short Packet Detect // - token member (32 bits) #define UHCI_TD_PID_OUT 0x000000e1 // PID Code is OUT #define UHCI_TD_PID_IN 0x00000069 // PID Code is IN #define UHCI_TD_PID_SETUP 0x0000002d // PID Code is SETUP #define UHCI_TD_DEVICE_ADDR(n) ((n) << 8) // Device Address = n (n = 0..127) #define UHCI_TD_ENDPOINT(n) ((n) << 15) // Endpoint = n (n = 0..15) #define UHCI_TD_DT(n) ((n) << 19) // Data Toggle = n (n = 0,1) #define UHCI_TD_LENGTH(n) ((((n) - 1) & 0x7ff) << 21) // Values specific to this driver. #define MAX_UHCI_PORTS 8 // the UHCI spec. doesn't define this, so pick a sensible number #define MAX_KEYBOARDS 8 // per host controller #define MAX_PACKETS 32 // per data transfer (must be >= MAX_KEYBOARDS) #define WS_QH_SIZE (1 + MAX_KEYBOARDS) // Queue Head Descriptors #define WS_TD_SIZE (2 + MAX_PACKETS) // Queue Transfer Descriptors #define MILLISEC 1000 // in microseconds //------------------------------------------------------------------------------ // Types //------------------------------------------------------------------------------ // Data structures defined by the UHCI specification. typedef volatile struct { uint32_t link_ptr; uint32_t control_status; uint32_t token; uint32_t buffer_ptr; uint32_t driver_data[4]; } uhci_td_t __attribute__ ((aligned (16))); typedef volatile struct { uint32_t qh_link_ptr; uint32_t qe_link_ptr; uint32_t padding[2]; } uhci_qh_t __attribute__ ((aligned (16))); // Data structures specific to this implementation. typedef struct { hcd_workspace_t base_ws; // System memory data structures used by the host controller. uhci_qh_t qh[WS_QH_SIZE] __attribute__ ((aligned (16))); uhci_td_t td[WS_TD_SIZE] __attribute__ ((aligned (16))); // Keyboard data transfer buffers. hid_kbd_rpt_t kbd_rpt[MAX_KEYBOARDS]; // Saved keyboard reports. hid_kbd_rpt_t prev_kbd_rpt[MAX_KEYBOARDS]; // I/O base address of the host controller registers. uint16_t io_base; // Number of keyboards detected. int num_keyboards; } workspace_t __attribute__ ((aligned (256))); //------------------------------------------------------------------------------ // Private Functions //------------------------------------------------------------------------------ static bool io_wait_until_clr(uint16_t io_reg, uint16_t bit_mask, int max_time) { int timer = max_time >> 3; while (inw(io_reg) & bit_mask) { if (timer == 0) return false; usleep(8); timer--; } return true; } static bool io_wait_until_set(uint16_t io_reg, uint16_t bit_mask, int max_time) { int timer = max_time >> 3; while (~inw(io_reg) & bit_mask) { if (timer == 0) return false; usleep(8); timer--; } return true; } static bool reset_host_controller(uint16_t io_base) { outw(inw(UHCI_USBCMD) | UHCI_USBCMD_HCR, UHCI_USBCMD); usleep(1*MILLISEC); // allow the controller some time to recover from reset return io_wait_until_clr(UHCI_USBCMD, UHCI_USBCMD_HCR, 1000*MILLISEC); } static bool start_host_controller(uint16_t io_base) { outw(UHCI_USBCMD_R_S | UHCI_USBCMD_MAXP, UHCI_USBCMD); return io_wait_until_clr(UHCI_USBSTS, UHCI_USBSTS_HCH, 1000*MILLISEC); } static bool halt_host_controller(uint16_t io_base) { outw(inw(UHCI_USBCMD) & ~UHCI_USBCMD_R_S, UHCI_USBCMD); return io_wait_until_set(UHCI_USBSTS, UHCI_USBSTS_HCH, 1000*MILLISEC); } static bool reset_uhci_port(uint16_t io_base, int port_idx) { uint16_t port_status = inw(UHCI_PORT_SC(port_idx)) & ~UHCI_PORT_SC_PED; outw(port_status | UHCI_PORT_SC_PR, UHCI_PORT_SC(port_idx)); usleep(50*MILLISEC); // USB port reset time outw(port_status & ~UHCI_PORT_SC_PR, UHCI_PORT_SC(port_idx)); return io_wait_until_clr(UHCI_PORT_SC(port_idx), UHCI_PORT_SC_PR, 5*MILLISEC); } static bool enable_uhci_port(uint16_t io_base, int port_idx) { uint16_t port_status = inw(UHCI_PORT_SC(port_idx)); outw(port_status | UHCI_PORT_SC_PED, UHCI_PORT_SC(port_idx)); return io_wait_until_set(UHCI_PORT_SC(port_idx), UHCI_PORT_SC_PED, 1000*MILLISEC); } static void disable_uhci_port(uint16_t io_base, int port_idx) { uint16_t port_status = inw(UHCI_PORT_SC(port_idx)); outw(port_status & ~UHCI_PORT_SC_PED, UHCI_PORT_SC(port_idx)); (void)io_wait_until_clr(UHCI_PORT_SC(port_idx), UHCI_PORT_SC_PED, 1000*MILLISEC); } static void build_uhci_td(uhci_td_t *td, const usb_ep_t *ep, uint32_t pid, uint32_t dt, uint32_t options, const void *buffer, size_t length) { uint32_t device_speed = (ep->device_speed == USB_SPEED_LOW) ? UHCI_TD_LOW_SPEED : UHCI_TD_FULL_SPEED; if (options & UHCI_TD_IOC_Y) { td->link_ptr = UHCI_LP_TERMINATE; } else { td->link_ptr = (uintptr_t)(td + 1) | UHCI_LP_TYPE_TD | UHCI_LP_DEPTH_FIRST; } td->control_status = UHCI_TD_CERR(3) | device_speed | options | UHCI_TD_ACTIVE; td->token = UHCI_TD_LENGTH(length) | dt | UHCI_TD_ENDPOINT(ep->endpoint_num) | UHCI_TD_DEVICE_ADDR(ep->device_id) | pid; td->buffer_ptr = (uintptr_t)buffer; } static uint16_t get_uhci_done(workspace_t *ws) { uint16_t io_base = ws->io_base; uint16_t status = inw(UHCI_USBSTS) & (UHCI_USBSTS_INT | UHCI_USBSTS_ERR); if (status != 0) { if (status & UHCI_USBSTS_ERR || ws->qh[0].qe_link_ptr != UHCI_LP_TERMINATE) { #if 1 uintptr_t td_addr = ws->qh[0].qe_link_ptr & 0xfffffff0; uhci_td_t *td = (uhci_td_t *)td_addr; print_usb_info(" transfer failed TD %08x status %08x token %08x", td_addr, (uintptr_t)td->control_status, (uintptr_t)td->token); #endif write32(&ws->qh[0].qe_link_ptr, UHCI_LP_TERMINATE); status |= UHCI_USBSTS_ERR; } outw(UHCI_USBSTS_INT | UHCI_USBSTS_ERR, UHCI_USBSTS); } return status; } static bool wait_for_uhci_done(workspace_t *ws) { // Rely on the controller to timeout if the device doesn't respond. uint16_t status = 0; while (true) { status = get_uhci_done(ws); if (status != 0) break; usleep(10); } return ~status & UHCI_USBSTS_ERR; } //------------------------------------------------------------------------------ // Driver Methods //------------------------------------------------------------------------------ static bool reset_root_hub_port(const usb_hcd_t *hcd, int port_num) { const workspace_t *ws = (const workspace_t *)hcd->ws; return reset_uhci_port(ws->io_base, port_num - 1); } static bool setup_request(const usb_hcd_t *hcd, const usb_ep_t *ep, const usb_setup_pkt_t *setup_pkt) { workspace_t *ws = (workspace_t *)hcd->ws; write32(&ws->qh[0].qe_link_ptr, UHCI_LP_TERMINATE); build_uhci_td(&ws->td[0], ep, UHCI_TD_PID_SETUP, UHCI_TD_DT(0), UHCI_TD_IOC_N, setup_pkt, sizeof(usb_setup_pkt_t)); build_uhci_td(&ws->td[1], ep, UHCI_TD_PID_IN, UHCI_TD_DT(1), UHCI_TD_IOC_Y, 0, 0); write32(&ws->qh[0].qe_link_ptr, (uintptr_t)(&ws->td[0]) | UHCI_LP_TYPE_TD); return wait_for_uhci_done(ws); } static bool get_data_request(const usb_hcd_t *hcd, const usb_ep_t *ep, const usb_setup_pkt_t *setup_pkt, const void *buffer, size_t length) { workspace_t *ws = (workspace_t *)hcd->ws; size_t packet_size = ep->max_packet_size; if (length > (MAX_PACKETS * packet_size)) { return false; } write32(&ws->qh[0].qe_link_ptr, UHCI_LP_TERMINATE); int pkt_num = 0; build_uhci_td(&ws->td[pkt_num], ep, UHCI_TD_PID_SETUP, UHCI_TD_DT(pkt_num & 1), UHCI_TD_IOC_N, setup_pkt, sizeof(usb_setup_pkt_t)); pkt_num++; while (length > packet_size) { build_uhci_td(&ws->td[pkt_num], ep, UHCI_TD_PID_IN, UHCI_TD_DT(pkt_num & 1), UHCI_TD_SPD, buffer, packet_size); pkt_num++; buffer = (uint8_t *)buffer + packet_size; length -= packet_size; } build_uhci_td(&ws->td[pkt_num], ep, UHCI_TD_PID_IN, UHCI_TD_DT(pkt_num & 1), UHCI_TD_IOC_N, buffer, length); pkt_num++; build_uhci_td(&ws->td[pkt_num], ep, UHCI_TD_PID_OUT, UHCI_TD_DT(1), UHCI_TD_IOC_Y, 0, 0); write32(&ws->qh[0].qe_link_ptr, (uintptr_t)(&ws->td[0]) | UHCI_LP_TYPE_TD); return wait_for_uhci_done(ws); } static void poll_keyboards(const usb_hcd_t *hcd) { workspace_t *ws = (workspace_t *)hcd->ws; uint16_t io_base = ws->io_base; uint32_t status = inw(UHCI_USBSTS) & (UHCI_USBSTS_INT | UHCI_USBSTS_ERR); if (status != 0) { outw(UHCI_USBSTS_INT | UHCI_USBSTS_ERR, UHCI_USBSTS); for (int kbd_idx = 0; kbd_idx < ws->num_keyboards; kbd_idx++) { uhci_qh_t *kbd_qh = &ws->qh[1 + kbd_idx]; uhci_td_t *kbd_td = &ws->td[2 + kbd_idx]; status = kbd_td->control_status; if (status & UHCI_TD_ACTIVE) continue; hid_kbd_rpt_t *kbd_rpt = &ws->kbd_rpt[kbd_idx]; uint32_t error_mask = UHCI_TD_STALLED | UHCI_TD_DB_ERR | UHCI_TD_BABBLE | UHCI_TD_NAK_RX | UHCI_TD_CRC_TO | UHCI_TD_BS_ERR; if (~status & error_mask) { hid_kbd_rpt_t *prev_kbd_rpt = &ws->prev_kbd_rpt[kbd_idx]; if (process_usb_keyboard_report(hcd, kbd_rpt, prev_kbd_rpt)) { *prev_kbd_rpt = *kbd_rpt; } write32(&kbd_td->token, read32(&kbd_td->token) ^ UHCI_TD_DT(1)); } // Reenable the TD. write32(&kbd_td->control_status, kbd_td->driver_data[0]); write32(&kbd_qh->qe_link_ptr, (uintptr_t)kbd_td | UHCI_LP_TYPE_TD); } } } //------------------------------------------------------------------------------ // Driver Method Table //------------------------------------------------------------------------------ static const hcd_methods_t methods = { .reset_root_hub_port = reset_root_hub_port, .allocate_slot = NULL, .release_slot = NULL, .assign_address = assign_usb_address, // use default method .configure_hub_ep = NULL, .configure_kbd_ep = NULL, .setup_request = setup_request, .get_data_request = get_data_request, .poll_keyboards = poll_keyboards }; //------------------------------------------------------------------------------ // Public Functions //------------------------------------------------------------------------------ bool uhci_reset(int bus, int dev, int func, uint16_t io_base) { // Disable PCI and SMM interrupts. pci_config_write16(bus, dev, func, UHCI_LEGSUP, UHCI_LEGSUP_CLEAR); // Ensure the controller is halted and then reset it. if (!halt_host_controller(io_base)) return false; if (!reset_host_controller(io_base)) return false; return true; } bool uhci_probe(uint16_t io_base, usb_hcd_t *hcd) { // Record the heap state to allow us to free memory. uintptr_t initial_heap_mark = heap_mark(HEAP_TYPE_LM_1); // Allocate the frame list. This needs to be aligned on a 4K page boundary. uintptr_t fl_addr = heap_alloc(HEAP_TYPE_LM_1, UHCI_FL_LENGTH * sizeof(uint32_t), PAGE_SIZE); if (fl_addr == 0) { goto no_keyboards_found; } uint32_t *fl = (uint32_t *)fl_addr; // Allocate and initialise a workspace for this controller. This needs to be permanently mapped into virtual memory. uintptr_t workspace_addr = heap_alloc(HEAP_TYPE_LM_1, sizeof(workspace_t), PAGE_SIZE); if (workspace_addr == 0) { goto no_keyboards_found; } workspace_t *ws = (workspace_t *)workspace_addr; memset(ws, 0, sizeof(workspace_t)); ws->io_base = io_base; // Initialise the asynchronous queue head. ws->qh[0].qh_link_ptr = UHCI_LP_TERMINATE; ws->qh[0].qe_link_ptr = UHCI_LP_TERMINATE; // Initialise the frame list to execute the asynchronous queue. for (int i = 0; i < UHCI_FL_LENGTH; i++) { fl[i] = (uintptr_t)(&ws->qh[0]) | UHCI_LP_TYPE_QH; } // Initialise the driver object for this controller. hcd->methods = &methods; hcd->ws = &ws->base_ws; // Initialise the host controller. outw(UHCI_USBINTR_NONE, UHCI_USBINTR); outw(0, UHCI_FRNUM); outl(fl_addr, UHCI_FLBASE); outb(UHCI_SOF_DEFAULT, UHCI_SOF); if (!start_host_controller(io_base)) { goto no_keyboards_found; } // Construct a hub descriptor for the root hub. usb_hub_t root_hub; memset(&root_hub, 0, sizeof(root_hub)); root_hub.ep0 = NULL; root_hub.num_ports = MAX_UHCI_PORTS; usleep(100*MILLISEC); // USB maximum device attach time // Scan the ports, looking for hubs and keyboards. usb_ep_t keyboards[MAX_KEYBOARDS]; int num_keyboards = 0; int num_devices = 0; for (int port_idx = 0; port_idx < root_hub.num_ports; port_idx++) { // If we've filled the keyboard info table, abort now. if (num_keyboards >= MAX_KEYBOARDS) break; // Check if we've passed the last port. uint16_t port_status = inw(UHCI_PORT_SC(port_idx)); if ((~port_status & UHCI_PORT_SC_VALID) || port_status == 0xffff) { root_hub.num_ports = port_idx; break; } // Check if anything is connected to this port. if (~port_status & UHCI_PORT_SC_CCS) continue; // Reset the port. if (!reset_uhci_port(io_base, port_idx)) continue; usleep(10*MILLISEC); // USB reset recovery time // Enable the port. if (!enable_uhci_port(io_base, port_idx)) continue; port_status = inw(UHCI_PORT_SC(port_idx)); // Check the port is active. if (~port_status & UHCI_PORT_SC_PED) continue; num_devices++; // Get the port speed. usb_speed_t port_speed = port_status & UHCI_PORT_SC_LSDA ? USB_SPEED_LOW : USB_SPEED_FULL; // Look for keyboards attached directly or indirectly to this port. if (find_attached_usb_keyboards(hcd, &root_hub, 1 + port_idx, port_speed, num_devices, &num_devices, keyboards, MAX_KEYBOARDS, &num_keyboards)) { continue; } // If we didn't find any keyboard interfaces, we can disable the port. disable_uhci_port(io_base, port_idx); } print_usb_info(" Found %i device%s, %i keyboard%s", num_devices, num_devices != 1 ? "s" : "", num_keyboards, num_keyboards != 1 ? "s" : ""); if (num_keyboards == 0) { (void)halt_host_controller(io_base); goto no_keyboards_found; } ws->num_keyboards = num_keyboards; // Initialise the interrupt QH and TD for each keyboard interface and find the minimum interval. int min_interval = UHCI_FL_LENGTH; uint32_t first_qh_ptr = UHCI_LP_TERMINATE; for (int kbd_idx = 0; kbd_idx < num_keyboards; kbd_idx++) { usb_ep_t *kbd = &keyboards[kbd_idx]; uhci_qh_t *kbd_qh = &ws->qh[1 + kbd_idx]; uhci_td_t *kbd_td = &ws->td[2 + kbd_idx]; hid_kbd_rpt_t *kbd_rpt = &ws->kbd_rpt[kbd_idx]; build_uhci_td(kbd_td, kbd, UHCI_TD_PID_IN, UHCI_TD_DT(0), UHCI_TD_IOC_Y, kbd_rpt, sizeof(hid_kbd_rpt_t)); kbd_td->driver_data[0] = kbd_td->control_status; kbd_qh->qe_link_ptr = (uintptr_t)kbd_td | UHCI_LP_TYPE_TD; kbd_qh->qh_link_ptr = first_qh_ptr; first_qh_ptr = (uintptr_t)kbd_qh | UHCI_LP_TYPE_QH; if (kbd->interval < min_interval) { min_interval = kbd->interval; } } // Re-initialise the frame list to execute the periodic schedule. for (int i = 0; i < UHCI_FL_LENGTH; i++) { write32(&fl[i], UHCI_LP_TERMINATE); } for (int i = 0; i < UHCI_FL_LENGTH; i += min_interval) { write32(&fl[i], first_qh_ptr); } return true; no_keyboards_found: heap_rewind(HEAP_TYPE_LM_1, initial_heap_mark); return false; }