From 8069b8724bb91d81212747028bff509afad50f27 Mon Sep 17 00:00:00 2001 From: Martin Whitaker Date: Wed, 22 Dec 2021 17:31:06 +0000 Subject: [PATCH] Initial support for native USB keyboard interface. This adds support for USB keyboards connected directly to an OHCI or XHCI controller. --- app/main.c | 4 +- build32/Makefile | 5 +- build64/Makefile | 5 +- system/keyboard.c | 165 ++++++- system/keyboard.h | 24 +- system/memrw32.h | 60 +++ system/ohci.c | 699 ++++++++++++++++++++++++++++ system/ohci.h | 26 ++ system/usb.h | 89 ++++ system/usbkbd.c | 275 +++++++++++ system/usbkbd.h | 150 ++++++ system/xhci.c | 1138 +++++++++++++++++++++++++++++++++++++++++++++ system/xhci.h | 26 ++ 13 files changed, 2644 insertions(+), 22 deletions(-) create mode 100644 system/memrw32.h create mode 100644 system/ohci.c create mode 100644 system/ohci.h create mode 100644 system/usb.h create mode 100644 system/usbkbd.c create mode 100644 system/usbkbd.h create mode 100644 system/xhci.c create mode 100644 system/xhci.h diff --git a/app/main.c b/app/main.c index 4f2ec1a..3c4f6af 100644 --- a/app/main.c +++ b/app/main.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -// Copyright (C) 2020 Martin Whitaker. +// Copyright (C) 2020-2021 Martin Whitaker. // // Derived from memtest86+ main.c: // @@ -151,6 +151,8 @@ static void global_init(void) config_init(); + keyboard_init(true); + display_init(); error_init(); diff --git a/build32/Makefile b/build32/Makefile index 400a9d5..dd10015 100644 --- a/build32/Makefile +++ b/build32/Makefile @@ -11,13 +11,16 @@ SYS_OBJS = system/cpuid.o \ system/font.o \ system/hwctrl.o \ system/keyboard.o \ + system/ohci.o \ system/pci.o \ system/pmem.o \ system/reloc.o \ system/screen.o \ system/smp.o \ system/temperature.o \ - system/vmem.o + system/usbkbd.o \ + system/vmem.o \ + system/xhci.o LIB_OBJS = lib/barrier.o \ lib/ctype.o \ diff --git a/build64/Makefile b/build64/Makefile index 981666d..d290a3a 100644 --- a/build64/Makefile +++ b/build64/Makefile @@ -11,13 +11,16 @@ SYS_OBJS = system/cpuid.o \ system/font.o \ system/hwctrl.o \ system/keyboard.o \ + system/ohci.o \ system/pci.o \ system/pmem.o \ system/reloc.o \ system/screen.o \ system/smp.o \ system/temperature.o \ - system/vmem.o + system/usbkbd.o \ + system/vmem.o \ + system/xhci.o LIB_OBJS = lib/barrier.o \ lib/ctype.o \ diff --git a/system/keyboard.c b/system/keyboard.c index 83b7ef9..6243cb1 100644 --- a/system/keyboard.c +++ b/system/keyboard.c @@ -1,9 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 -// Copyright (C) 2020 Martin Whitaker. +// Copyright (C) 2020-2021 Martin Whitaker. #include #include "io.h" +#include "usbkbd.h" #include "keyboard.h" @@ -12,7 +13,7 @@ //------------------------------------------------------------------------------ // Convert set 1 scancodes to characters. -static const char keymap[] = { +static const char legacy_keymap[] = { /* 0x00 */ 0, /* 0x01 */ ESC, /* 0x02 */ '1', @@ -72,16 +73,16 @@ static const char keymap[] = { /* 0x38 */ 0, /* 0x39 */ ' ', /* 0x3a */ 0, - /* 0x3b */ '1', - /* 0x3c */ '2', - /* 0x3d */ '3', - /* 0x3e */ '4', - /* 0x3f */ '5', - /* 0x40 */ '6', - /* 0x41 */ '7', - /* 0x42 */ '8', - /* 0x43 */ '9', - /* 0x44 */ '0', + /* 0x3b */ '1', // F1 + /* 0x3c */ '2', // F2 + /* 0x3d */ '3', // F3 + /* 0x3e */ '4', // F4 + /* 0x3f */ '5', // F5 + /* 0x40 */ '6', // F6 + /* 0x41 */ '7', // F7 + /* 0x42 */ '8', // F8 + /* 0x43 */ '9', // F9 + /* 0x44 */ '0', // F10 /* 0x45 */ 0, /* 0x46 */ 0, /* 0x47 */ '7', // keypad @@ -99,19 +100,147 @@ static const char keymap[] = { /* 0x53 */ '.', // keypad }; +// Convert USB HID keycodes to characters. +static const char usb_hid_keymap[] = { + /* 0x00 */ 0, + /* 0x01 */ 0, + /* 0x02 */ 0, + /* 0x03 */ 0, + /* 0x04 */ 'a', + /* 0x05 */ 'b', + /* 0x06 */ 'c', + /* 0x07 */ 'd', + /* 0x08 */ 'e', + /* 0x09 */ 'f', + /* 0x0a */ 'g', + /* 0x0b */ 'h', + /* 0x0c */ 'i', + /* 0x0d */ 'j', + /* 0x0e */ 'k', + /* 0x0f */ 'l', + /* 0x10 */ 'm', + /* 0x11 */ 'n', + /* 0x12 */ 'o', + /* 0x13 */ 'p', + /* 0x14 */ 'q', + /* 0x15 */ 'r', + /* 0x16 */ 's', + /* 0x17 */ 't', + /* 0x18 */ 'u', + /* 0x19 */ 'v', + /* 0x1a */ 'w', + /* 0x1b */ 'x', + /* 0x1c */ 'y', + /* 0x1d */ 'z', + /* 0x1e */ '1', + /* 0x1f */ '2', + /* 0x20 */ '3', + /* 0x21 */ '4', + /* 0x22 */ '5', + /* 0x23 */ '6', + /* 0x24 */ '7', + /* 0x25 */ '8', + /* 0x26 */ '9', + /* 0x27 */ '0', + /* 0x28 */ '\n', + /* 0x29 */ ESC, + /* 0x2a */ '\b', + /* 0x2b */ '\t', + /* 0x2c */ ' ', + /* 0x2d */ '-', + /* 0x2e */ '+', + /* 0x2f */ '[', + /* 0x30 */ ']', + /* 0x31 */ '\\', + /* 0x32 */ '#', + /* 0x33 */ ';', + /* 0x34 */ '\'', + /* 0x35 */ '`', + /* 0x36 */ ',', + /* 0x37 */ '.', + /* 0x38 */ '/', + /* 0x39 */ 0, // Caps Lock + /* 0x3a */ '1', // F1 + /* 0x3b */ '2', // F2 + /* 0x3c */ '3', // F3 + /* 0x3d */ '4', // F4 + /* 0x3e */ '5', // F5 + /* 0x3f */ '6', // F6 + /* 0x40 */ '7', // F7 + /* 0x41 */ '8', // F8 + /* 0x42 */ '9', // F9 + /* 0x43 */ '0', // F10 + /* 0x44 */ 0, // F11 + /* 0x45 */ 0, // F12 + /* 0x46 */ 0, // Print Screen + /* 0x47 */ 0, // Scroll Lock + /* 0x48 */ 0, // Pause + /* 0x49 */ 0, // Insert + /* 0x4a */ 0, // Delete + /* 0x4b */ 0, // Home + /* 0x4c */ 0, // Page Up + /* 0x4d */ 0, // Delete + /* 0x4e */ 0, // Page Down + /* 0x4f */ 0, // Cursor Right + /* 0x50 */ 0, // Cursor Left + /* 0x51 */ 0, // Cursor Down + /* 0x52 */ 0, // Cursor Up + /* 0x53 */ 0, // Number Lock + /* 0x54 */ '/', // keypad + /* 0x55 */ '*', // keypad + /* 0x56 */ '-', // keypad + /* 0x57 */ '+', // keypad + /* 0x58 */ '\n', // keypad + /* 0x59 */ '1', // keypad + /* 0x5a */ '2', // keypad + /* 0x5b */ '3', // keypad + /* 0x5c */ '4', // keypad + /* 0x5d */ '5', // keypad + /* 0x5e */ '6', // keypad + /* 0x5f */ '7', // keypad + /* 0x60 */ '8', // keypad + /* 0x61 */ '9', // keypad + /* 0x62 */ '0', // keypad + /* 0x63 */ '.', // keypad + /* 0x64 */ '\\', // Non-US +}; + +//------------------------------------------------------------------------------ +// Public Variables +//------------------------------------------------------------------------------ + +keyboard_types_t keyboard_types = KT_LEGACY | KT_USB; + //------------------------------------------------------------------------------ // Public Functions //------------------------------------------------------------------------------ +void keyboard_init(bool pause_at_end) +{ + if (keyboard_types & KT_USB) { + find_usb_keyboards(pause_at_end); + } +} + char get_key(void) { - uint8_t c = inb(0x64); - if (c & 0x01) { - c = inb(0x60); - if (c < sizeof(keymap)) { - return keymap[c]; + if (keyboard_types & KT_USB) { + uint8_t c = get_usb_keycode(); + if (c > 0 && c < sizeof(usb_hid_keymap)) { + return usb_hid_keymap[c]; } - // Ignore keys we don't recognise and key up codes } + + if (keyboard_types & KT_LEGACY) { + uint8_t c = inb(0x64); + if (c & 0x01) { + c = inb(0x60); + if (c < sizeof(legacy_keymap)) { + return legacy_keymap[c]; + } + // Ignore keys we don't recognise and key up codes + } + } + return '\0'; } diff --git a/system/keyboard.h b/system/keyboard.h index a772fa1..9a28391 100644 --- a/system/keyboard.h +++ b/system/keyboard.h @@ -1,11 +1,14 @@ // SPDX-License-Identifier: GPL-2.0 #ifndef KEYBOARD_H #define KEYBOARD_H + +#include + /* * Provides the keyboard interface. It converts incoming key codes to * ASCII characters. * - * Copyright (C) 2020 Martin Whitaker. + * Copyright (C) 2020-2021 Martin Whitaker. */ /* @@ -13,6 +16,25 @@ */ #define ESC 27 +/* + * A set of supported keyboard types. + */ +typedef enum { + KT_NONE = 0, + KT_LEGACY = 1, + KT_USB = 2 +} keyboard_types_t; + +/* + * The set of enabled keyboard types + */ +extern keyboard_types_t keyboard_types; + +/* + * Initialises the keyboard interface. + */ +void keyboard_init(bool pause_at_end); + /* * Checks if a key has been pressed and returns the primary ASCII character * corresponding to that key if so, otherwise returns the null character. diff --git a/system/memrw32.h b/system/memrw32.h new file mode 100644 index 0000000..a421456 --- /dev/null +++ b/system/memrw32.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef MEMRW32_H +#define MEMRW32_H +/* + * Provides some 32-bit memory access functions. These stop the compiler + * optimizing accesses which need to be ordered and atomic. Mostly used + * for accessing memory-mapped hardware registers. + * + * Copyright (C) 2021 Martin Whitaker. + */ + +#include + +/* + * Reads and returns the value stored in the 32-bit memory location pointed + * to by ptr. + */ +static inline uint32_t read32(const volatile uint32_t *ptr) +{ + uint32_t val; + __asm__ __volatile__( + "movl %1, %0" + : "=r" (val) + : "m" (*ptr) + : "memory" + ); + return val; +} + +/* + * Writes val to the 32-bit memory location pointed to by ptr. + */ +static inline void write32(const volatile uint32_t *ptr, uint32_t val) +{ + __asm__ __volatile__( + "movl %1, %0" + : + : "m" (*ptr), + "r" (val) + : "memory" + ); +} + +/* + * Writes val to the 32-bit memory location pointed to by ptr. Reads it + * back (and discards it) to ensure the write is complete. + */ +static inline void flush32(const volatile uint32_t *ptr, uint32_t val) +{ + __asm__ __volatile__( + "movl %1, %0\n" + "movl %0, %1" + : + : "m" (*ptr), + "r" (val) + : "memory" + ); +} + +#endif // MEMRW32_H diff --git a/system/ohci.c b/system/ohci.c new file mode 100644 index 0000000..6d1d68b --- /dev/null +++ b/system/ohci.c @@ -0,0 +1,699 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2021 Martin Whitaker. + +#include +#include +#include + +#include "memrw32.h" +#include "memsize.h" +#include "pmem.h" + +#include "memory.h" +#include "unistd.h" +#include "usbkbd.h" +#include "usb.h" + +#include "ohci.h" + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +// Values defined by the OHCI specification. + +// HcRevision register + +#define OHCI_CTRL_CBSR 0x00000003 // Control Bulk Service Ratio +#define OHCI_CTRL_CBSR0 0x00000000 // Control Bulk Service Ratio 0 +#define OHCI_CTRL_CBSR1 0x00000001 // Control Bulk Service Ratio 1 +#define OHCI_CTRL_CBSR2 0x00000002 // Control Bulk Service Ratio 2 +#define OHCI_CTRL_CBSR3 0x00000003 // Control Bulk Service Ratio 2 +#define OHCI_CTRL_PLE 0x00000004 // Periodic List Enable +#define OHCI_CTRL_IE 0x00000008 // Isochronous Enable +#define OHCI_CTRL_CLE 0x00000010 // Control List Enable +#define OHCI_CTRL_BLE 0x00000020 // Bulk List Enable +#define OHCI_CTRL_HCFS 0x000000c0 // Host Controller Functional State +#define OHCI_CTRL_HCFS_RST 0x00000000 // Host Controller Functional State is Reset +#define OHCI_CTRL_HCFS_RES 0x00000040 // Host Controller Functional State is Resume +#define OHCI_CTRL_HCFS_RUN 0x00000080 // Host Controller Functional State is Run +#define OHCI_CTRL_HCFS_SUS 0x000000c0 // Host Controller Functional State is Suspend +#define OHCI_CTRL_IR 0x00000100 // Interrupt Routing +#define OHCI_CTRL_RWC 0x00000200 // Remote Wakeup Connected +#define OHCI_CTRL_RWE 0x00000400 // Remote Wakeup Enable + +// HcCommandStatus register + +#define OHCI_CMD_HCR 0x00000001 // Host Controller Reset +#define OHCI_CMD_CLF 0x00000002 // Control List Filled +#define OHCI_CMD_BLF 0x00000004 // Bulk List Filled +#define OHCI_CMD_OCR 0x00000008 // Ownership Change Request + +// HcInterruptStatus register + +#define OHCI_INTR_SC 0x00000001 // Scheduling Overrun +#define OHCI_INTR_WDH 0x00000002 // Writeback Done Head +#define OHCI_INTR_SOF 0x00000004 // Start of Frame +#define OHCI_INTR_RD 0x00000008 // Resume Detected +#define OHCI_INTR_UE 0x00000010 // Unrecoverable Error +#define OHCI_INTR_FNO 0x00000020 // Frame Number Overflow +#define OHCI_INTR_RHSC 0x00000040 // Root Hub Status Change +#define OHCI_INTR_OC 0x40000000 // Ownership Change +#define OHCI_INTR_MIE 0x80000000 // Master Interrupt Enable + +// HcRhDescriptorA register + +#define OHCI_RHDA_PSM 0x00000100 // Power Switching Mode +#define OHCI_RHDA_NPS 0x00000200 // No Power Switching +#define OHCI_RHDA_OCPM 0x00000800 // Over Current Protection Mode +#define OHCI_RHDA_NOCP 0x00001000 // No Over Current Protection + +// HcRhDescriptorB register + +#define OHCI_RHDB_DR 0x0000ffff // Device Removable +#define OHCI_RHDB_PPCM 0xffff0000 // Port Power Control Mask + +// HcRhStatus register + +#define OHCI_RHS_LPS 0x00000001 // Local Power Status +#define OHCI_RHS_OCI 0x00000002 // Over-Current Indicator +#define OHCI_RHS_DRWE 0x00008000 // Device Remote Wakeup Enable +#define OHCI_RHS_LPSC 0x00010000 // Local Power Status Change +#define OHCI_RHS_OCIC 0x00020000 // Over-Current Indicator Change +#define OHCI_RHS_CRWE 0x80000000 // Clear Remote Wakeup Enable + +#define OHCI_SET_GLOBAL_POWER 0x00010000 +#define OHCI_CLR_GLOBAL_POWER 0x00000001 + +// HcRhPortStatus registers + +#define OHCI_PORT_CONNECTED 0x00000001 +#define OHCI_PORT_ENABLED 0x00000002 +#define OHCI_PORT_SUSPENDED 0x00000004 +#define OHCI_PORT_OCI 0x00000008 +#define OHCI_PORT_RESETING 0x00000010 +#define OHCI_PORT_POWERED 0x00000100 +#define OHCI_PORT_LOW_SPEED 0x00000200 +#define OHCI_PORT_CONNECT_CHG 0x00010000 +#define OHCI_PORT_ENABLE_CHG 0x00020000 +#define OHCI_PORT_SUSPEND_CHG 0x00040000 +#define OHCI_PORT_OCI_CHG 0x00080000 +#define OHCI_PORT_RESET_CHG 0x00100000 + +#define OHCI_CLR_PORT_ENABLE 0x00000001 +#define OHCI_SET_PORT_ENABLE 0x00000002 +#define OHCI_SET_PORT_SUSPEND 0x00000004 +#define OHCI_CLR_PORT_SUSPEND 0x00000008 +#define OHCI_SET_PORT_RESET 0x00000010 +#define OHCI_SET_PORT_POWER 0x00000100 +#define OHCI_CLR_PORT_POWER 0x00000200 + +// Endpoint Descriptor data structure + +#define OHCI_ED_FA 0x0000007f // Function Address +#define OHCI_ED_EN 0x00000780 // Endpoint Number +#define OHCI_ED_DIR 0x00001800 // Direction +#define OHCI_ED_DIR_TD 0x00000000 // Direction is From TD +#define OHCI_ED_DIR_OUT 0x00000800 // Direction is OUT +#define OHCI_ED_DIR_IN 0x00001000 // Direction is IN +#define OHCI_ED_SPD 0x00002000 // Speed +#define OHCI_ED_SPD_FULL 0x00000000 // Speed is Full Speed +#define OHCI_ED_SPD_LOW 0x00001000 // Speed is Low Speed +#define OHCI_ED_SKIP 0x00004000 // Skip +#define OHCI_ED_FMT 0x00008000 // Format +#define OHCI_ED_FMT_GEN 0x00000000 // Format is General TD +#define OHCI_ED_FMT_ISO 0x00008000 // Format is Isochronous TD +#define OHCI_ED_MPS 0x07ff0000 // Max Packet Size + +#define OHCI_ED_HALTED 0x00000001 // Halted flag +#define OHCI_ED_TOGGLE 0x00000002 // Toggle carry bit + +// Transfer Descriptor data structure + +#define OHCI_TD_BR 0x00040000 // Buffer Rounding +#define OHCI_TD_DP 0x00180000 // Direction/PID +#define OHCI_TD_DP_SETUP 0x00000000 // Direction/PID is SETUP +#define OHCI_TD_DP_OUT 0x00080000 // Direction/PID is OUT +#define OHCI_TD_DP_IN 0x00100000 // Direction/PID is IN +#define OHCI_TD_DI 0x00e00000 // Delay Interrupt +#define OHCI_TD_DI_NO_DLY 0x00000000 // Delay Interrupt is 0 (no delay) +#define OHCI_TD_DI_NO_INT 0x00e00000 // Delay Interrupt is 7 (no interrupt) +#define OHCI_TD_DT 0x03000000 // Data Toggle +#define OHCI_TD_DT_0 0x00000000 // Data Toggle LSB is 0 +#define OHCI_TD_DT_1 0x01000000 // Data Toggle LSB is 1 +#define OHCI_TD_DT_USE_ED 0x00000000 // Data Toggle MSB is 0 +#define OHCI_TD_DT_USE_TD 0x02000000 // Data Toggle MSB is 1 +#define OHCI_TD_EC 0x0c000000 // Error Count +#define OHCI_TD_CC 0xf0000000 // Condition Code +#define OHCI_TD_CC_NO_ERR 0x00000000 // Condition Code is No Error +#define OHCI_TD_CC_NEW 0xe0000000 // Condition Code is Not Accessed + +// Miscellaneous values + +#define OHCI_MAX_INTERVAL 32 + +// Values specific to this implementation. + +#define MAX_KEYBOARDS 8 // per host controller + +#define WS_ED_SIZE (1 + MAX_KEYBOARDS) // Endpoint Descriptors +#define WS_TD_SIZE (3 + MAX_KEYBOARDS) // Transfer Descriptors +#define WS_DATA_SIZE 1024 // bytes +#define WS_KC_BUFFER_SIZE 8 // keycodes + +#define MILLISEC 1000 // in microseconds + +//------------------------------------------------------------------------------ +// Types +//------------------------------------------------------------------------------ + +// Register sets defined by the OHCI specification. + +typedef volatile struct { + uint32_t revision; + uint32_t control; + uint32_t command_status; + uint32_t interrupt_status; + uint32_t interrupt_enable; + uint32_t interrupt_disable; + uint32_t hcca; + uint32_t period_current_ed; + uint32_t ctrl_head_ed; + uint32_t ctrl_current_ed; + uint32_t bulk_head_ed; + uint32_t bulk_current_ed; + uint32_t done_head; + uint32_t fm_interval; + uint32_t fm_remaining; + uint32_t fm_number; + uint32_t periodic_start; + uint32_t ls_threshold; + uint32_t rh_descriptor_a; + uint32_t rh_descriptor_b; + uint32_t rh_status; + uint32_t rh_port_status[]; +} ohci_op_regs_t; + +// Data structures defined by the OHCI specification. + +typedef volatile struct { + uint32_t intr_head_ed[32]; + uint16_t frame_num; + uint16_t pad; + uint32_t done_head; + uint32_t reserved[30]; +} ohci_hcca_t __attribute__ ((aligned (256))); + +typedef volatile struct { + uint32_t control; + uint32_t tail_ptr; + uint32_t head_ptr; + uint32_t next_ed; +} ohci_ed_t __attribute__ ((aligned (16))); + +typedef volatile struct { + uint32_t control; + uint32_t curr_buff; + uint32_t next_td; + uint32_t buff_end; +} ohci_td_t __attribute__ ((aligned (16))); + +// Data structures specific to this implementation. + +typedef struct { + // System memory data structures used by the host controller. + ohci_hcca_t hcca; + ohci_ed_t ed [WS_ED_SIZE]; + ohci_td_t td [WS_TD_SIZE]; + + // Data transfer buffers. + union { + volatile uint8_t data [WS_DATA_SIZE]; + hid_kbd_rpt_t kbd_rpt [MAX_KEYBOARDS]; + }; + + // Pointer to the host controller registers. + ohci_op_regs_t *op_regs; + + // Values shared between functions during USB device initialisation. + int ep0_max_packet_size; + int config_total_length; + + // Circular buffer for received keycodes. + uint8_t kc_buffer [WS_KC_BUFFER_SIZE]; + int kc_index_i; + int kc_index_o; +} workspace_t __attribute__ ((aligned (256))); + +//------------------------------------------------------------------------------ +// Private Functions +//------------------------------------------------------------------------------ + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +static size_t num_pages(size_t size) +{ + return (size + PAGE_SIZE - 1) >> PAGE_SHIFT; +} + +static bool reset_ohci_port(ohci_op_regs_t *regs, int port_idx) +{ + // The OHCI reset lasts for 10ms, but the USB specification calls for 50ms (but not necessarily continuously). + // So do it 5 times. + for (int i = 0; i < 5; i++) { + write32(®s->rh_port_status[port_idx], OHCI_PORT_CONNECT_CHG | OHCI_PORT_RESET_CHG); + write32(®s->rh_port_status[port_idx], OHCI_SET_PORT_RESET); + if (!wait_until_set(®s->rh_port_status[port_idx], OHCI_PORT_RESET_CHG, 500*MILLISEC)) return false; + } + write32(®s->rh_port_status[port_idx], OHCI_PORT_RESET_CHG); + + usleep(10*MILLISEC); // USB reset recovery time + + // Check the port is now active. + uint32_t status = read32(®s->rh_port_status[port_idx]); + if ( status & OHCI_PORT_RESETING) return false; + if (~status & OHCI_PORT_CONNECTED) return false; + if (~status & OHCI_PORT_ENABLED) return false; + + return true; +} + +static ohci_td_t *get_ohci_done_head(workspace_t *ws) +{ + if (!wait_until_set(&ws->op_regs->interrupt_status, OHCI_INTR_WDH, 10*MILLISEC)) { + return NULL; + } + uintptr_t done_head = ws->hcca.done_head & 0xfffffffe; + write32(&ws->op_regs->interrupt_status, OHCI_INTR_WDH); + return (ohci_td_t *)done_head; +} + +static bool wait_for_ohci_done(workspace_t *ws, int td_expected) +{ + int td_completed = 0; + + ohci_td_t *td = get_ohci_done_head(ws); + while (td != NULL) { + td_completed++; + if ((td->control & OHCI_TD_CC) != OHCI_TD_CC_NO_ERR) { + return false; + } + td = (ohci_td_t *)((uintptr_t)td->next_td); + } + + return td_completed == td_expected; +} + +static void build_ohci_td(ohci_td_t *td, uint32_t control, volatile void *buffer, size_t length) +{ + td->control = OHCI_TD_CC_NEW | control; + td->curr_buff = (uintptr_t)buffer; + td->buff_end = (uintptr_t)buffer + length - 1; + td->next_td = (uintptr_t)(td + 1); +} + +static void build_ohci_ed(ohci_ed_t *ed, uint32_t control, ohci_td_t *head_td, ohci_td_t *tail_td) +{ + // Set the skip flag before modifying the head and tail pointers, in case we are modifying an active ED. + // Use write32() to make sure the compiler doesn't reorder the writes. + write32(&ed->control, OHCI_ED_SKIP); + ed->head_ptr = (uintptr_t)head_td; + ed->tail_ptr = (uintptr_t)tail_td; + write32(&ed->control, control); +} + +static uint32_t ohci_ed_control(int device_idx, int endpoint_idx, int max_packet_size, bool is_low_speed) +{ + uint32_t control = OHCI_ED_FMT_GEN | OHCI_ED_DIR_TD | max_packet_size << 16 | endpoint_idx << 7 | device_idx; + if (is_low_speed) { + control |= OHCI_ED_SPD_LOW; + } else { + control |= OHCI_ED_SPD_FULL; + } + return control; +} + +static bool initialise_device(workspace_t *ws, int port_idx, int device_num, bool is_low_speed) +{ + // The device should currently be in Default state. We first fetch the first 8 bytes of the device descriptor to + // discover the maximum packet size for the control endpoint. We then set the device address, which moves the + // device into Addressed state, and fetch the full device descriptor. We don't currently make use of any of the + // other fields of the device descriptor, but some USB devices may not work correctly if we don't fetch it. + + ohci_op_regs_t *regs = ws->op_regs; + + int usb_address = 0; + int fetch_length = 8; + int max_packet_size = 8; + goto fetch_device_descriptor; + + set_address: + build_setup_packet(ws->data, 0x00, 0x05, usb_address, 0, 0); + build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, ws->data, sizeof(usb_setup_pkt_t)); + build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0); + build_ohci_ed(&ws->ed[0], ohci_ed_control(0, 0, max_packet_size, is_low_speed), &ws->td[0], &ws->td[2]); + write32(®s->command_status, OHCI_CMD_CLF); + if (!wait_for_ohci_done(ws, 2)) { + return false; + } + usleep(2*MILLISEC); // USB set address recovery time. + + fetch_device_descriptor: + build_setup_packet(ws->data, 0x80, 0x06, USB_DESC_DEVICE << 8, 0, fetch_length); + build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, ws->data, sizeof(usb_setup_pkt_t)); + build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_INT, ws->data, fetch_length); + build_ohci_td(&ws->td[2], OHCI_TD_DP_OUT | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0); + build_ohci_ed(&ws->ed[0], ohci_ed_control(usb_address, 0, max_packet_size, is_low_speed), &ws->td[0], &ws->td[3]); + write32(®s->command_status, OHCI_CMD_CLF); + if (!wait_for_ohci_done(ws, 3) || !valid_usb_device_descriptor(ws->data)) { + return false; + } + + if (usb_address == 0) { + usb_device_desc_t *device = (usb_device_desc_t *)ws->data; + max_packet_size = device->max_packet_size; + if (!valid_usb_max_packet_size(max_packet_size, is_low_speed)) { + return false; + } + if (usb_init_options & USB_EXTRA_RESET) { + if (!reset_ohci_port(regs, port_idx)) { + return false; + } + } + usb_address = device_num; + fetch_length = sizeof(usb_device_desc_t); + goto set_address; + } + ws->ep0_max_packet_size = max_packet_size; + + // Fetch the first configuration descriptor and the associated interface and endpoint descriptors. Start by + // requesting just the configuration descriptor. Then read the descriptor to determine whether we need to fetch + // more data. + + fetch_length = sizeof(usb_config_desc_t); + fetch_config_descriptor: + build_setup_packet(ws->data, 0x80, 0x06, USB_DESC_CONFIG << 8, 0, fetch_length); + build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, ws->data, sizeof(usb_setup_pkt_t)); + build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_INT, ws->data, fetch_length); + build_ohci_td(&ws->td[2], OHCI_TD_DP_OUT | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0); + build_ohci_ed(&ws->ed[0], ohci_ed_control(usb_address, 0, max_packet_size, is_low_speed), &ws->td[0], &ws->td[3]); + write32(®s->command_status, OHCI_CMD_CLF); + if (!wait_for_ohci_done(ws, 3) || !valid_usb_config_descriptor(ws->data)) { + return false; + } + usb_config_desc_t *config = (usb_config_desc_t *)ws->data; + int total_length = MIN(config->total_length, WS_DATA_SIZE); + if (total_length > fetch_length) { + fetch_length = total_length; + goto fetch_config_descriptor; + } + ws->config_total_length = total_length; + + return true; +} + +static bool configure_interface(workspace_t *ws, usb_keyboard_info_t *kbd) +{ + ohci_op_regs_t *regs = ws->op_regs; + + // Set the device configuration. + build_setup_packet(ws->data, 0x00, 0x09, 1, 0, 0); + build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, ws->data, sizeof(usb_setup_pkt_t)); + build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0); + build_ohci_ed(&ws->ed[0], ohci_ed_control(kbd->device_num, 0, ws->ep0_max_packet_size, kbd->port_speed), &ws->td[0], &ws->td[2]); + write32(®s->command_status, OHCI_CMD_CLF); + if (!wait_for_ohci_done(ws, 2)) { + return false; + } + + // Set the idle duration to infinite. + build_setup_packet(ws->data, 0x21, 0x0a, 0, kbd->interface_num, 0); + build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, ws->data, sizeof(usb_setup_pkt_t)); + build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0); + build_ohci_ed(&ws->ed[0], ohci_ed_control(kbd->device_num, 0, ws->ep0_max_packet_size, kbd->port_speed), &ws->td[0], &ws->td[2]); + write32(®s->command_status, OHCI_CMD_CLF); + if (!wait_for_ohci_done(ws, 2)) { + return false; + } + + // Select the boot protocol. + build_setup_packet(ws->data, 0x21, 0x0b, 0, kbd->interface_num, 0); + build_ohci_td(&ws->td[0], OHCI_TD_DP_SETUP | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_INT, ws->data, sizeof(usb_setup_pkt_t)); + build_ohci_td(&ws->td[1], OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_1 | OHCI_TD_DI_NO_DLY, 0, 0); + build_ohci_ed(&ws->ed[0], ohci_ed_control(kbd->device_num, 0, ws->ep0_max_packet_size, kbd->port_speed), &ws->td[0], &ws->td[2]); + write32(®s->command_status, OHCI_CMD_CLF); + if (!wait_for_ohci_done(ws, 2)) { + return false; + } + + return true; +} + +//------------------------------------------------------------------------------ +// Public Functions +//------------------------------------------------------------------------------ + +void *ohci_init(uintptr_t base_addr) +{ + ohci_op_regs_t *regs = (ohci_op_regs_t *)base_addr; + + // Check the host controller revison. + if ((read32(®s->revision) & 0xff) != 0x10) { + return NULL; + } + + // Take ownership from the SMM if necessary. + if (read32(®s->control) & OHCI_CTRL_IR) { + write32(®s->interrupt_enable, OHCI_INTR_OC); + flush32(®s->command_status, OHCI_CMD_OCR); + if (!wait_until_clr(®s->control, OHCI_CTRL_IR, 1000*MILLISEC)) { + return NULL; + } + } + + // Preserve the FM interval set by the SMM or BIOS. + // If not set, use the default value. + uint32_t fm_interval = read32(®s->fm_interval) & 0x3fff; + if (fm_interval == 0) { + fm_interval = 0x2edf; + } + + // Prepare for host controller setup (see section 5.1.1.3 of the OHCI spec.). + switch (read32(®s->control) & OHCI_CTRL_HCFS) { + case OHCI_CTRL_HCFS_RST: + usleep(50*MILLISEC); + break; + case OHCI_CTRL_HCFS_SUS: + case OHCI_CTRL_HCFS_RES: + flush32(®s->control, OHCI_CTRL_HCFS_SUS); + usleep(10*MILLISEC); + break; + default: // operational + break; + } + + // Reset the host controller. + write32(®s->command_status, OHCI_CMD_HCR); + if (!wait_until_clr(®s->command_status, OHCI_CMD_HCR, 30)) { + return NULL; + } + + // Check we are now in SUSPEND state. + if ((read32(®s->control) & OHCI_CTRL_HCFS) != OHCI_CTRL_HCFS_SUS) { + return NULL; + } + + // Allocate and initialise a workspace for this controller. This needs to be permanently mapped into virtual + // memory, so allocate it in the first segment. + // TODO: check for segment overflow. + pm_map[0].end -= num_pages(sizeof(workspace_t)); + uintptr_t workspace_addr = pm_map[0].end << PAGE_SHIFT; + workspace_t *ws = (workspace_t *)workspace_addr; + + memset(ws, 0, sizeof(workspace_t)); + + // Initialise the control list ED. + ws->ed[0].control = OHCI_ED_SKIP; + ws->ed[0].next_ed = 0; + + // Initialise the pointer to the device registers. + ws->op_regs = regs; + + // Initialise the keycode buffer. + ws->kc_index_i = 0; + ws->kc_index_o = 0; + + // Initialise the host controller. + uint32_t max_packet_size = ((fm_interval - 210) * 6) / 7; + write32(®s->fm_interval, 1 << 31 | max_packet_size << 16 | fm_interval); + write32(®s->periodic_start, (fm_interval * 9) / 10); + write32(®s->hcca, (uintptr_t)(&ws->hcca)); + write32(®s->ctrl_head_ed, (uintptr_t)(&ws->ed[0])); + write32(®s->bulk_head_ed, 0); + write32(®s->ctrl_current_ed, 0); + write32(®s->bulk_current_ed, 0); + write32(®s->control, OHCI_CTRL_HCFS_RUN | OHCI_CTRL_CLE | OHCI_CTRL_CBSR0); + flush32(®s->interrupt_status, ~0); + + // Power up the ports. + uint32_t rh_descriptor_a = read32(®s->rh_descriptor_a); + uint32_t rh_descriptor_b = read32(®s->rh_descriptor_b); + if (~rh_descriptor_a & OHCI_RHDA_NPS) { + // If we have individual port power control, clear the port power control mask to allow us to power up all + // ports now. + if (rh_descriptor_a & OHCI_RHDA_PSM) { + write32(®s->rh_descriptor_b, rh_descriptor_b & OHCI_RHDB_DR); + } + + // Power up all ports. + flush32(®s->rh_status, OHCI_RHS_LPSC); + int port_power_up_delay = (rh_descriptor_a >> 24) * 2*MILLISEC; + usleep(port_power_up_delay); + + usleep(100*MILLISEC); // USB maximum device attach time. + } + + // Scan the ports, looking for keyboards. + usb_keyboard_info_t keyboard_info[MAX_KEYBOARDS]; + int num_keyboards = 0; + int num_devices = 0; + int device_num = 0; + int min_interval = OHCI_MAX_INTERVAL; + int num_ports = rh_descriptor_a & 0xf; + for (int port_idx = 0; port_idx < num_ports; port_idx++) { + if (num_keyboards >= MAX_KEYBOARDS) continue; + + uint32_t port_status = read32(®s->rh_port_status[port_idx]); + + // Check the port is powered up. + if (~port_status & OHCI_PORT_POWERED) continue; + + // Check if anything is connected to this port. + if (~port_status & OHCI_PORT_CONNECTED) continue; + num_devices++; + + // Reset the port. + if (!reset_ohci_port(regs, port_idx)) continue; + + // Now the port has been reset, we can determine the device speed. + port_status = read32(®s->rh_port_status[port_idx]); + bool is_low_speed = port_status & OHCI_PORT_LOW_SPEED; + + // Initialise the USB device. If successful, this leaves a set of configuration descriptors in the workspace + // data buffer. + if (!initialise_device(ws, port_idx, ++device_num, is_low_speed)) { + goto disable_port; + } + + // Scan the descriptors to see if this device has one or more keyboard interfaces and if so, record that + // information in the keyboard info table. + usb_keyboard_info_t *new_keyboard_info = &keyboard_info[num_keyboards]; + int new_keyboards = get_usb_keyboard_info_from_descriptors(ws->data, ws->config_total_length, new_keyboard_info, + MAX_KEYBOARDS - num_keyboards); + + // Complete the new entries in the keyboard info table and configure the keyboard interfaces. + for (int kbd_idx = 0; kbd_idx < new_keyboards; kbd_idx++) { + usb_keyboard_info_t *kbd = &new_keyboard_info[kbd_idx]; + kbd->port_speed = is_low_speed; + kbd->device_num = device_num; + if (kbd->interval < min_interval) { + min_interval = kbd->interval; + } + configure_interface(ws, kbd); + + print_usb_info(" Keyboard found on port %i interface %i endpoint %i", + 1 + port_idx, kbd->interface_num, kbd->endpoint_num); + } + if (new_keyboards > 0) { + num_keyboards += new_keyboards; + continue; + } + + // If we didn't find any keyboard interfaces, we can disable the port. + disable_port: + write32(®s->rh_port_status[port_idx], OHCI_CLR_PORT_ENABLE); + } + + 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) { + // Shut down the host controller and the root hub. + flush32(®s->control, OHCI_CTRL_HCFS_RST); + + // Delay to allow the controller to reset. + usleep(10); + + // Deallocate the workspace for this controller. + pm_map[0].end += num_pages(sizeof(workspace_t)); + + return NULL; + } + + // Initialise the interrupt ED and TD for each keyboard interface. + ohci_ed_t *last_kbd_ed = NULL; + for (int kbd_idx = 0; kbd_idx < num_keyboards; kbd_idx++) { + usb_keyboard_info_t *kbd = &keyboard_info[kbd_idx]; + + ohci_ed_t *kbd_ed = &ws->ed[1 + kbd_idx]; + ohci_td_t *kbd_td = &ws->td[3 + kbd_idx]; + + hid_kbd_rpt_t *kbd_rpt = &ws->kbd_rpt[kbd_idx]; + + build_ohci_td(kbd_td, OHCI_TD_DP_IN | OHCI_TD_DT_USE_TD | OHCI_TD_DT_0 | OHCI_TD_DI_NO_DLY, kbd_rpt, sizeof(hid_kbd_rpt_t)); + build_ohci_ed(kbd_ed, ohci_ed_control(kbd->device_num, kbd->endpoint_num, kbd->max_packet_size, kbd->port_speed), kbd_td+0, kbd_td+1); + + kbd_ed->next_ed = (uintptr_t)last_kbd_ed; + last_kbd_ed = kbd_ed; + } + + // Initialise the interrupt table. + for (int i = 0; i < OHCI_MAX_INTERVAL; i += min_interval) { + ws->hcca.intr_head_ed[i] = (uintptr_t)last_kbd_ed; + } + write32(®s->control, OHCI_CTRL_HCFS_RUN | OHCI_CTRL_CLE | OHCI_CTRL_PLE | OHCI_CTRL_CBSR0); + flush32(®s->interrupt_status, ~0); + + return ws; +} + +uint8_t ohci_get_keycode(void *workspace) +{ + workspace_t *ws = (workspace_t *)workspace; + + ohci_td_t *td = get_ohci_done_head(ws); + while (td != NULL) { + int kbd_idx = td - ws->td - 3; + + hid_kbd_rpt_t *kbd_rpt = &ws->kbd_rpt[kbd_idx]; + + if ((td->control & OHCI_TD_CC) == OHCI_TD_CC_NO_ERR) { + uint8_t keycode = kbd_rpt->key_code[0]; + if (keycode != 0) { + int kc_index_i = ws->kc_index_i; + int kc_index_n = (kc_index_i + 1) % WS_KC_BUFFER_SIZE; + if (kc_index_n != ws->kc_index_o) { + ws->kc_buffer[kc_index_i] = keycode; + ws->kc_index_i = kc_index_n; + } + } + } + + ohci_td_t *next_td = (ohci_td_t *)((uintptr_t)td->next_td); + + ohci_ed_t *ed = &ws->ed[1 + kbd_idx]; + build_ohci_td(td, td->control & ~OHCI_TD_CC, kbd_rpt, sizeof(hid_kbd_rpt_t)); + build_ohci_ed(ed, ed->control, td+0, td+1); + + td = next_td; + } + + int kc_index_o = ws->kc_index_o; + if (kc_index_o != ws->kc_index_i) { + ws->kc_index_o = (kc_index_o + 1) % WS_KC_BUFFER_SIZE; + return ws->kc_buffer[kc_index_o]; + } + + return 0; +} diff --git a/system/ohci.h b/system/ohci.h new file mode 100644 index 0000000..442376f --- /dev/null +++ b/system/ohci.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef OHCI_H +#define OHCI_H +/* + * Provides support for USB keyboards connected via an OHCI controller. + * + * Copyright (C) 2021 Martin Whitaker. + */ + +#include + +/* + * Initialises the OHCI device found at base_addr, scans all the attached USB + * devices, and configures any HID USB keyboard devices it finds to generate + * periodic interrupt transfers that report key presses. + */ +void *ohci_init(uintptr_t base_addr); + +/* + * Polls the completed periodic interrupt transfers, stores the keycodes from + * any new key press events in an internal queue, and if the keycode queue is + * not empty, pops and returns the keycode from the front of the queue. + */ +uint8_t ohci_get_keycode(void *ws); + +#endif // OHCI_H diff --git a/system/usb.h b/system/usb.h new file mode 100644 index 0000000..0237c9e --- /dev/null +++ b/system/usb.h @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef USB_H +#define USB_H +/* + * Provides definitions of various values and data structures defined by the + * USB specification. + * + * Copyright (C) 2021 Martin Whitaker. + */ + +#include +#include + +#define USB_DESC_DEVICE 0x01 +#define USB_DESC_CONFIG 0x02 +#define USB_DESC_INTERFACE 0x04 +#define USB_DESC_ENDPOINT 0x05 + +typedef volatile struct __attribute__((packed)) { + uint8_t type; + uint8_t request; + uint16_t value; + uint16_t index; + uint16_t length; +} usb_setup_pkt_t; + +typedef volatile struct __attribute__((packed)) { + uint8_t length; + uint8_t type; +} usb_desc_header_t; + +typedef volatile struct __attribute__((packed)) { + uint8_t length; + uint8_t type; + uint8_t usb_minor; + uint8_t usb_major; + uint8_t class; + uint8_t subclass; + uint8_t protocol; + uint8_t max_packet_size; + uint16_t vendor_id; + uint16_t product_id; + uint8_t device_minor; + uint8_t device_major; + uint8_t vendor_str; + uint8_t product_str; + uint8_t serial_num_str; + uint8_t num_configs; +} usb_device_desc_t; + +typedef volatile struct __attribute__((packed)) { + uint8_t length; + uint8_t type; + uint16_t total_length; + uint8_t num_interfaces; + uint8_t config_num; + uint8_t config_str; + uint8_t attributes; + uint8_t max_power; +} usb_config_desc_t; + +typedef volatile struct __attribute__((packed)) { + uint8_t length; + uint8_t type; + uint8_t interface_num; + uint8_t alt_setting; + uint8_t num_endpoints; + uint8_t class; + uint8_t subclass; + uint8_t protocol; + uint8_t interface_str; +} usb_interface_desc_t; + +typedef volatile struct __attribute__((packed)) { + uint8_t length; + uint8_t type; + uint8_t address; + uint8_t attributes; + uint16_t max_packet_size; + uint8_t interval; +} usb_endpoint_desc_t; + +typedef volatile struct __attribute__((packed)) { + uint8_t modifiers; + uint8_t reserved; + uint8_t key_code[6]; +} hid_kbd_rpt_t; + +#endif // USB_H diff --git a/system/usbkbd.c b/system/usbkbd.c new file mode 100644 index 0000000..c4ece5b --- /dev/null +++ b/system/usbkbd.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2021 Martin Whitaker. + +#include + +#include "keyboard.h" +#include "memrw32.h" +#include "pci.h" +#include "screen.h" +#include "usb.h" + +#include "ohci.h" +#include "xhci.h" + +#include "print.h" +#include "unistd.h" + +#include "usbkbd.h" + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +#define MAX_USB_CONTROLLERS 8 + +//------------------------------------------------------------------------------ +// Types +//------------------------------------------------------------------------------ + +typedef enum { + UHCI = 0, + OHCI = 1, + EHCI = 2, + XHCI = 3 +} usb_controller_type_t; + +typedef struct { + usb_controller_type_t type; + void *workspace; +} usb_controller_info_t; + +//------------------------------------------------------------------------------ +// Private Variables +//------------------------------------------------------------------------------ + +static usb_controller_info_t usb_controllers[MAX_USB_CONTROLLERS]; + +static int num_usb_controllers = 0; + +static int print_row = 0; + +//------------------------------------------------------------------------------ +// Public Variables +//------------------------------------------------------------------------------ + +usb_init_options_t usb_init_options = USB_DEFAULT_INIT; + +//------------------------------------------------------------------------------ +// Shared Functions (used by controller drivers) +//------------------------------------------------------------------------------ + +bool wait_until_clr(const volatile uint32_t *reg, uint32_t bit_mask, int max_time) +{ + int timer = max_time >> 3; + while (read32(reg) & bit_mask) { + if (timer == 0) return false; + usleep(8); + timer--; + } + return true; +} + +bool wait_until_set(const volatile uint32_t *reg, uint32_t bit_mask, int max_time) +{ + int timer = max_time >> 3; + while (~read32(reg) & bit_mask) { + if (timer == 0) return false; + usleep(8); + timer--; + } + return true; +} + +int get_usb_keyboard_info_from_descriptors(const volatile uint8_t *desc_buffer, int desc_length, + usb_keyboard_info_t keyboard_info[], int keyboard_info_size) +{ + int num_keyboards = 0; + usb_keyboard_info_t *kbd = NULL; + const volatile uint8_t *curr_ptr = desc_buffer + sizeof(usb_config_desc_t); + const volatile uint8_t *tail_ptr = desc_buffer + desc_length; + while (curr_ptr < tail_ptr) { + // If we've filled the keyboard info table, abort now. + if (num_keyboards >= keyboard_info_size) break; + + const usb_desc_header_t *header = (const usb_desc_header_t *)curr_ptr; + const volatile uint8_t *next_ptr = curr_ptr + header->length; + + // Basic checks for validity. + if (next_ptr < (curr_ptr + 2) || next_ptr > tail_ptr) break; + + if (header->type == USB_DESC_INTERFACE && header->length == sizeof(usb_interface_desc_t)) { + const usb_interface_desc_t *ifc = (const usb_interface_desc_t *)curr_ptr; +#if 0 + print_usb_info("interface %i class %i subclass %i protocol %i", + ifc->interface_num, ifc->class, ifc->subclass, ifc->protocol); +#endif + if (ifc->class == 3 && ifc->subclass == 1 && ifc->protocol == 1) { + kbd = &keyboard_info[num_keyboards]; + kbd->interface_num = ifc->interface_num; + } else { + kbd = NULL; + } + } else if (header->type == USB_DESC_ENDPOINT && header->length == sizeof(usb_endpoint_desc_t)) { + usb_endpoint_desc_t *endpoint = (usb_endpoint_desc_t *)curr_ptr; +#if 0 + print_usb_info("endpoint addr 0x%02x attr 0x%02x", + (uintptr_t)endpoint->address, (uintptr_t)endpoint->attributes); +#endif + if (kbd && (endpoint->address & 0x80) && (endpoint->attributes & 0x3) == 0x3) { + kbd->endpoint_num = endpoint->address & 0xf; + kbd->max_packet_size = endpoint->max_packet_size; + kbd->interval = endpoint->interval; + num_keyboards++; + kbd = NULL; + } + } + curr_ptr = next_ptr; + } + return num_keyboards; +} + +void print_usb_info(const char *fmt, ...) +{ + if (print_row == SCREEN_HEIGHT) { + scroll_screen_region(0, 0, SCREEN_HEIGHT - 1, SCREEN_WIDTH - 1); + print_row--; + } + + va_list args; + + va_start(args, fmt); + (void)vprintf(print_row++, 0, fmt, args); + va_end(args); +} + +//------------------------------------------------------------------------------ +// Public Functions +//------------------------------------------------------------------------------ + +void find_usb_keyboards(bool pause_at_end) +{ + clear_screen(); + print_usb_info("Scanning for USB keyboards..."); + + num_usb_controllers = 0; + for (int bus = 0; bus < PCI_MAX_BUS; bus++) { + for (int dev = 0; dev < PCI_MAX_DEV; dev++) { + for (int func = 0; func < PCI_MAX_FUNC; func++) { + uint16_t vendor_id = pci_config_read16(bus, dev, func, 0x00); + + // Test for device/function present. + if (vendor_id != 0xffff) { + uint16_t device_id = pci_config_read16(bus, dev, func, 0x02); + uint16_t pci_status = pci_config_read16(bus, dev, func, 0x06); + uint16_t class_code = pci_config_read16(bus, dev, func, 0x0a); + uint8_t hdr_type = pci_config_read8 (bus, dev, func, 0x0e); + + // Test for a USB controller. + if (class_code == 0x0c03) { + usb_controller_type_t controller_type = pci_config_read8 (bus, dev, func, 0x09) >> 4; + uintptr_t base_addr; + //uint8_t pm_cap_ptr; + if (controller_type == UHCI) { + base_addr = pci_config_read32(bus, dev, func, 0x20); + } else { + base_addr = pci_config_read32(bus, dev, func, 0x10); +#ifdef __x86_64__ + if (base_addr & 0x4) { + base_addr += (uintptr_t)pci_config_read32(bus, dev, func, 0x14) << 32; + } +#endif + } + base_addr &= ~(uintptr_t)0xf; + + // Search for power management capability. + if (pci_status & 0x10) { + uint8_t cap_ptr = pci_config_read8(bus, dev, func, 0x34) & 0xfe; + while (cap_ptr != 0) { + uint8_t cap_id = pci_config_read8(bus, dev, func, cap_ptr); + if (cap_id == 1) { + uint16_t pm_status = pci_config_read16(bus, dev, func, cap_ptr+2); + // Power on if necessary. + if ((pm_status & 0x3) != 0) { + pci_config_write16(bus, dev, func, cap_ptr+2, 0x8000); + usleep(10000); + } + //pm_cap_ptr = cap_ptr; + break; + } + cap_ptr = pci_config_read8(bus, dev, func, cap_ptr+1) & 0xfe; + } + } + + // Make sure the device is enabled. + uint16_t control = pci_config_read16(bus, dev, func, 0x04); + pci_config_write16(bus, dev, func, 0x04, control | 0x0007); + + // Initialise the device according to its type. + usb_controller_info_t *new_controller = &usb_controllers[num_usb_controllers]; + new_controller->type = controller_type; + new_controller->workspace = NULL; + if (controller_type == UHCI) { + print_usb_info("Found UHCI controller %04x:%04x at %08x", + (uintptr_t)vendor_id, (uintptr_t)device_id, base_addr); + } + if (controller_type == OHCI) { + print_usb_info("Found OHCI controller %04x:%04x at %08x", + (uintptr_t)vendor_id, (uintptr_t)device_id, base_addr); + new_controller->workspace = ohci_init(base_addr); + } + if (controller_type == EHCI) { + print_usb_info("Found EHCI controller %04x:%04x at %08x", + (uintptr_t)vendor_id, (uintptr_t)device_id, base_addr); + } + if (controller_type == XHCI) { + print_usb_info("Found XHCI controller %04x:%04x at %08x", + (uintptr_t)vendor_id, (uintptr_t)device_id, base_addr); + new_controller->workspace = xhci_init(base_addr); + } + if (new_controller->workspace != NULL) { + num_usb_controllers++; + // If we've filled the controller table, abort now. + if (num_usb_controllers == MAX_USB_CONTROLLERS) { + return; + } + } + } + // Break out if this is a single function device. + if (func == 0 && (hdr_type & 0x80) == 0) { + break; + } + } else { + // Break out if no device is present. + if (func == 0) { + break; + } + } + } + } + } + + if (pause_at_end) { + print_usb_info("Press any key to continue..."); + while (get_key() == 0) {} + } +} + +uint8_t get_usb_keycode(void) +{ + for (int i = 0; i < num_usb_controllers; i++) { + uint8_t keycode = 0; + switch (usb_controllers[i].type) { + case OHCI: + keycode = ohci_get_keycode(usb_controllers[i].workspace); + break; + case XHCI: + keycode = xhci_get_keycode(usb_controllers[i].workspace); + break; + default: + break; + } + if (keycode != 0) return keycode; + } + return 0; +} diff --git a/system/usbkbd.h b/system/usbkbd.h new file mode 100644 index 0000000..b1464ff --- /dev/null +++ b/system/usbkbd.h @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef USBKBD_H +#define USBKBD_H +/* + * Provides low-level support for USB keyboards. + * + * Copyright (C) 2021 Martin Whitaker. + */ + +#include +#include + +#include + +/* + * A USB keyboard device descriptor used internally by the various HCI drivers. + */ +typedef struct { + int port_speed; + int device_num; + int interface_num; + int endpoint_num; + int max_packet_size; + int interval; +} usb_keyboard_info_t; + +/* + * A set of USB device initialisation options. + */ +typedef enum { + USB_DEFAULT_INIT = 0, + USB_EXTRA_RESET = 1 +} usb_init_options_t; + +/* + * The selected USB device initialisation options. + * + * Used internally by the various HCI drivers. + */ +extern usb_init_options_t usb_init_options; + +/* + * Constructs a USB setup packet in buffer using the provided values/ + * + * Used internally by the various HCI drivers. + */ +static inline void build_setup_packet(volatile void *buffer, int type, int request, int value, int index, int length) +{ + usb_setup_pkt_t *pkt = (usb_setup_pkt_t *)buffer; + + pkt->type = type; + pkt->request = request; + pkt->value = value; + pkt->index = index; + pkt->length = length; +} + +/* + * Returns true if size is a valid value for the maximum packet size for a + * low speed or full speed USB device or endpoint. + * + * Used internally by the various HCI drivers. + */ +static inline bool valid_usb_max_packet_size(int size, bool is_low_speed) +{ + return (size == 8) || (!is_low_speed && (size == 16 || size == 32 || size == 64)); +} + +/* + * Returns true if buffer appears to contain a valid USB device descriptor. + * + * Used internally by the various HCI drivers. + */ +static inline bool valid_usb_device_descriptor(volatile uint8_t *buffer) +{ + usb_desc_header_t *desc = (usb_desc_header_t *)buffer; + + return desc->length == sizeof(usb_device_desc_t) && desc->type == USB_DESC_DEVICE; +} + +/* + * Returns true if buffer appears to contain a valid USB configuration descriptor. + * + * Used internally by the various HCI drivers. + */ +static inline bool valid_usb_config_descriptor(volatile uint8_t *buffer) +{ + usb_desc_header_t *desc = (usb_desc_header_t *)buffer; + + return desc->length == sizeof(usb_config_desc_t) && desc->type == USB_DESC_CONFIG; +} + +/* + * Waits for all the bits set in bit_mask to be cleared in the register pointed + * to by reg or for max_time microseconds to elapse. + * + * Used internally by the various HCI drivers. + */ +bool wait_until_clr(const volatile uint32_t *reg, uint32_t bit_mask, int max_time); + +/* + * Waits for all the bits set in bit_mask to also be set in the register pointed + * to by reg or for max_time microseconds to elapse. + * + * Used internally by the various HCI drivers. + */ +bool wait_until_set(const volatile uint32_t *reg, uint32_t bit_mask, int max_time); + +/* + * Scans the descriptors obtained from a USB GET_CONFIGURATION request and + * stored in the buffer defined by desc_buffer and desc_length. Adds an entry + * in the table defined by keyboard_info and keyboard_info_size for each + * endpoint it finds that identifies as a HID keyboard device, filling in + * the interface_num, endpoint_num, max_packet_size, and interval fields. + * Returns the number of entries added. The scan is terminated early if the + * table is full. + * + * Used internally by the various HCI drivers. + */ +int get_usb_keyboard_info_from_descriptors(const volatile uint8_t *desc_buffer, int desc_length, + usb_keyboard_info_t keyboard_info[], int keyboard_info_size); + +/* + * Displays an informational message, scrolling the screen if necessary. + * Takes the same arguments as the printf function. + * + * Used internally by the various HCI drivers. + */ +void print_usb_info(const char *fmt, ...); + +/* + * Scans the attached USB devices and initialises all HID keyboard devices + * it finds (subject to implementation limits on the number of devices). + * Records the information needed to subsequently poll those devices for + * key presses. + * + * Used internally by keyboard.c. + */ +void find_usb_keyboards(bool pause_at_end); + +/* + * Polls the keyboards discovered by find_usb_keyboards. Consumes and returns + * the HID key code for the first key press it detects. Returns zero if no key + * has been pressed. + * + * Used internally by keyboard.c. + */ +uint8_t get_usb_keycode(void); + +#endif // USBKBD_H diff --git a/system/xhci.c b/system/xhci.c new file mode 100644 index 0000000..1e3cefc --- /dev/null +++ b/system/xhci.c @@ -0,0 +1,1138 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2021 Martin Whitaker. + +#include +#include + +#include "memrw32.h" +#include "memsize.h" +#include "pmem.h" + +#include "memory.h" +#include "unistd.h" +#include "usbkbd.h" +#include "usb.h" + +#include "xhci.h" + +#define QEMU_WORKAROUND 1 + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +// Values defined by the XHCI specification. + +// Basic limits + +#define XHCI_MAX_PORTS 255 +#define XHCI_MAX_SLOTS 255 + +#define XHCI_MAX_CONTEXT_SIZE 64 + +#define XHCI_MAX_IP_CONTEXT_SIZE (33 * XHCI_MAX_CONTEXT_SIZE) +#define XHCI_MAX_OP_CONTEXT_SIZE (32 * XHCI_MAX_CONTEXT_SIZE) + +// Extended capability ID values + +#define XHCI_EXT_CAP_LEGACY_SUPPORT 1 +#define XHCI_EXT_CAP_SUPPORTED_PROTOCOL 2 + +// USB Command register + +#define XHCI_USBCMD_R_S 0x00000001 // Run/Stop +#define XHCI_USBCMD_HCRST 0x00000002 // Host Controller Reset +#define XHCI_USBCMD_INTE 0x00000004 // Interrupter Enable +#define XHCI_USBCMD_HSEE 0x00000008 // Host System Error Enable + +// USB Status register + +#define XHCI_USBSTS_HCH 0x00000001 // Host Controller Halted +#define XHCI_USBSTS_HSE 0x00000004 // Host System Error +#define XHCI_USBSTS_CNR 0x00000800 // Controller Not Ready + +// Port Status and Control register + +#define XHCI_PORT_SC_CCS 0x00000001 // Current Connect Status +#define XHCI_PORT_SC_PED 0x00000002 // Port Enable/Disable +#define XHCI_PORT_SC_OCA 0x00000004 // Over-Current +#define XHCI_PORT_SC_PR 0x00000010 // Port Reset +#define XHCI_PORT_SC_PLS 0x000001e0 // Port Link State +#define XHCI_PORT_SC_PP 0x00000200 // Port Power +#define XHCI_PORT_SC_PS 0x00003c00 // Port Speed +#define XHCI_PORT_SC_PRSC 0x00200000 // Port Reset + +#define XHCI_PORT_SC_PS_OFFSET 10 // first bit of Port Speed + +// Transfer Request Block data structure + +#define XHCI_TRB_ENT (1 << 1) // Evaluate Next TRB +#define XHCI_TRB_TC (1 << 1) // Toggle Cycle +#define XHCI_TRB_ISP (1 << 2) // Interrupt on Short Packet +#define XHCI_TRB_NS (1 << 3) // No Snoop +#define XHCI_TRB_CH (1 << 4) // Chain bit +#define XHCI_TRB_IOC (1 << 5) // Interrupt on Completion +#define XHCI_TRB_IDT (1 << 6) // Immediate Data +#define XHCI_TRB_BEI (1 << 9) // Block Event Interrupt +#define XHCI_TRB_BSR (1 << 9) // Block Set Address Request + +#define XHCI_TRB_TYPE (63 << 10) +#define XHCI_TRB_NORMAL (1 << 10) +#define XHCI_TRB_SETUP_STAGE (2 << 10) +#define XHCI_TRB_DATA_STAGE (3 << 10) +#define XHCI_TRB_STATUS_STAGE (4 << 10) +#define XHCI_TRB_LINK (6 << 10) +#define XHCI_TRB_ENABLE_SLOT (9 << 10) +#define XHCI_TRB_DISABLE_SLOT (10 << 10) +#define XHCI_TRB_ADDRESS_DEVICE (11 << 10) +#define XHCI_TRB_CONFIGURE_ENDPOINT (12 << 10) +#define XHCI_TRB_EVALUATE_CONTEXT (13 << 10) +#define XHCI_TRB_NOOP (23 << 10) +#define XHCI_TRB_TRANSFER_EVENT (32 << 10) +#define XHCI_TRB_COMMAND_COMPLETE (33 << 10) + +#define XHCI_TRB_TRT_NO_DATA (0 << 16) // Transfer Type (Setup Stage TRB) +#define XHCI_TRB_TRT_OUT (2 << 16) // Transfer Type (Setup Stage TRB) +#define XHCI_TRB_TRT_IN (3 << 16) // Transfer Type (Setup Stage TRB) + +#define XHCI_TRB_DIR_OUT (0 << 16) // Direction (Data/Status Stage TRB) +#define XHCI_TRB_DIR_IN (1 << 16) // Direction (Data/Status Stage TRB) + +// Add Context flags + +#define XHCI_CONTEXT_A0 (1 << 0) +#define XHCI_CONTEXT_A1 (1 << 1) + +// Port Speed values + +#define XHCI_FULL_SPEED 1 +#define XHCI_LOW_SPEED 2 +#define XHCI_HIGH_SPEED 3 + +// Endpoint Type values + +#define XHCI_EP_NOT_VALID 0 +#define XHCI_EP_ISOCH_OUT 1 +#define XHCI_EP_BULK_OUT 2 +#define XHCI_EP_INTERRUPT_OUT 3 +#define XHCI_EP_CONTROL 4 +#define XHCI_EP_ISOCH_IN 5 +#define XHCI_EP_BULK_IN 6 +#define XHCI_EP_INTERRUPT_IN 7 + +// Event Completion Code values + +#define XHCI_EVENT_CC_SUCCESS 1 +#define XHCI_EVENT_CC_TIMEOUT 191 // specific to this implementation + +// Values specific to this implementation. + +#define PORT_TYPE_PST_MASK 0x1f // Protocol Slot Type mask +#define PORT_TYPE_USB2 0x40 +#define PORT_TYPE_USB3 0x80 + +#define MAX_KEYBOARDS 8 // per host controller + +#define WS_CR_SIZE 8 // TRBs (multiple of 4 to maintain 64 byte alignment) +#define WS_ER_SIZE 16 // TRBs (multiple of 4 to maintain 64 byte alignment) +#define WS_ERST_SIZE 4 // entries (multiple of 4 to maintain 64 byte alignment) +#define WS_DATA_SIZE 1024 // bytes +#define WS_KC_BUFFER_SIZE 8 // keycodes + +#define EP_TR_SIZE 8 // TRBs (multiple of 4 to maintain 64 byte alignment) + +#define MILLISEC 1000 // in microseconds + +//------------------------------------------------------------------------------ +// Types +//------------------------------------------------------------------------------ + +// Register sets defined by the XHCI specification. + +typedef struct { + uint8_t cap_length; + uint8_t reserved; + uint16_t hci_version; + uint32_t hcs_params1; + uint32_t hcs_params2; + uint32_t hcs_params3; + uint32_t hcc_params1; + uint32_t db_offset; + uint32_t rts_offset; + uint32_t hcc_params2; +} xhci_cap_regs_t; + +typedef volatile struct { + uint32_t sc; + uint32_t pmsc; + uint32_t li; + uint32_t hlpmc; +} xhci_port_regs_t; + +typedef volatile struct { + uint32_t usb_command; + uint32_t usb_status; + uint32_t page_size; + uint32_t reserved1[2]; + uint32_t dn_control; + uint64_t cr_control; + uint32_t reserved2[4]; + uint64_t dcbaap; + uint32_t config; + uint32_t reserved3[241]; + xhci_port_regs_t port_regs[]; +} xhci_op_regs_t; + +typedef volatile struct { + uint32_t management; + uint32_t moderation; + uint32_t erst_size; + uint32_t reserved; + uint64_t erst_addr; + uint64_t erdp; +} xhci_int_regs_t; + +typedef volatile struct { + uint32_t mf_index; + uint32_t reserved[7]; + xhci_int_regs_t ir[]; +} xhci_rt_regs_t; + +typedef volatile uint32_t xhci_db_reg_t; + +// Extended capability structures defined by the XHCI specification. + +typedef struct { + uint8_t id; + uint8_t next_offset; + uint8_t id_specific[2]; +} xhci_ext_cap_t; + +typedef struct { + uint8_t id; + uint8_t next_offset; + uint8_t bios_owns; + uint8_t host_owns; + uint32_t ctrl_stat; +} xhci_legacy_support_t; + +typedef struct { + uint8_t id; + uint8_t next_offset; + uint8_t revision_minor; + uint8_t revision_major; + uint32_t name_string; + uint8_t port_offset; + uint8_t port_count; + uint16_t params1; + uint32_t params2; + uint32_t speed_id[]; +} xhci_supported_protocol_t; + +// Data structures defined by the XHCI specification. + +typedef struct { + uint32_t drop_context_flags; + uint32_t add_context_flags; + uint32_t reserved1[5]; + uint8_t config_value; + uint8_t interface_num; + uint8_t alternate_setting; + uint8_t reserved2; +} xhci_ctrl_context_t __attribute__ ((aligned (16))); + +typedef struct { + uint32_t params1; + uint16_t max_exit_latency; + uint8_t root_hub_port_num; + uint8_t num_ports; + uint8_t tt_hub_slot_id; + uint8_t tt_port_num; + uint16_t params2; + uint8_t usb_dev_addr; + uint8_t reserved1; + uint8_t reserved2; + uint8_t slot_state; + uint32_t reserved3[4]; +} xhci_slot_context_t __attribute__ ((aligned (16))); + +typedef struct { + uint8_t state; + uint8_t params1; + uint8_t interval; + uint8_t max_esit_payload_h; + uint8_t params2; + uint8_t max_burst_size; + uint16_t max_packet_size; + uint64_t tr_dequeue_ptr; + uint16_t average_trb_length; + uint16_t max_esit_payload_l; + uint32_t reserved[3]; +} xhci_ep_context_t __attribute__ ((aligned (16))); + +typedef struct { + xhci_ctrl_context_t ctrl; + xhci_slot_context_t slot; + xhci_ep_context_t ep[31]; +} xhci_input_context_t __attribute__ ((aligned (16))); + +typedef volatile struct { + uint64_t params1; + uint32_t params2; + uint32_t control; +} xhci_trb_t __attribute__ ((aligned (16))); + +typedef volatile struct { + uint64_t segment_addr; + uint16_t segment_size; + uint16_t reserved1; + uint32_t reserved2; +} xhci_erst_entry_t __attribute__ ((aligned (16))); + +// Data structures specific to this implementation. + +typedef volatile struct { + xhci_trb_t tr [EP_TR_SIZE]; + uint32_t enqueue_state; + uint32_t padding[15]; +} ep_tr_t __attribute__ ((aligned (64))); + +typedef struct { + // System memory data structures used by the host controller. + xhci_trb_t cr [WS_CR_SIZE]; // command ring + xhci_trb_t er [WS_ER_SIZE]; // event ring + xhci_erst_entry_t erst [WS_ERST_SIZE]; // event ring segment table + + // Data transfer buffers. + union { + volatile uint8_t data [WS_DATA_SIZE]; + hid_kbd_rpt_t kbd_rpt [MAX_KEYBOARDS]; + }; + + // Keyboard data transfer rings + ep_tr_t kbd_tr [MAX_KEYBOARDS]; + + // Pointers to the host controller registers. + xhci_op_regs_t *op_regs; + xhci_rt_regs_t *rt_regs; + xhci_db_reg_t *db_regs; + + // Host controller TRB ring enqueue cycle and index. + uint32_t cr_enqueue_state; + uint32_t er_dequeue_state; + + // Values shared between functions during USB device initialisation. + int32_t config_total_length; + uintptr_t input_context_addr; + uintptr_t output_context_addr; + uintptr_t control_ep_tr_addr; + + // Keyboard slot ID lookup table + int32_t kbd_slot_id [MAX_KEYBOARDS]; + + // Keyboard endpoint ID lookup table + int32_t kbd_ep_id [MAX_KEYBOARDS]; + + // Circular buffer for received keycodes. + uint8_t kc_buffer [WS_KC_BUFFER_SIZE]; + int32_t kc_index_i; + int32_t kc_index_o; + +} workspace_t __attribute__ ((aligned (64))); + +//------------------------------------------------------------------------------ +// Private Variables +//------------------------------------------------------------------------------ + +// The heap segment of the physical memory map is used when allocating memory +// to the controller that we don't need to access during normal operation. +// Any memory we do need to access during normal operation is allocated from +// segment 0, which is permanently mapped into the virtual memory address space. + +static int heap_segment = -1; + +//------------------------------------------------------------------------------ +// Private Functions +//------------------------------------------------------------------------------ + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +static size_t num_pages(size_t size) +{ + return (size + PAGE_SIZE - 1) >> PAGE_SHIFT; +} + +static size_t round_up(size_t size, size_t alignment) +{ + return (size + alignment - 1) & ~(alignment - 1); +} + +// The read64 and write64 functions provided here provide compatibility with both +// 32-bit and 64-bit hosts and with both 32-bit and 64-bit XHCI controllers. + +static uint64_t read64(const volatile uint64_t *ptr) +{ + uint32_t val_l = read32((const volatile uint32_t *)ptr + 0); + uint32_t val_h = read32((const volatile uint32_t *)ptr + 1); + return (uint64_t)val_h << 32 | (uint64_t)val_l; +} + +static void write64(volatile uint64_t *ptr, uint64_t val) +{ + write32((volatile uint32_t *)ptr + 0, (uint32_t)(val >> 0)); + write32((volatile uint32_t *)ptr + 1, (uint32_t)(val >> 32)); +} + +#ifdef QEMU_WORKAROUND +static void memcpy32(void *dst, const void *src, size_t size) +{ + uint32_t *dst_word = (uint32_t *)dst; + uint32_t *src_word = (uint32_t *)src; + size_t num_words = size / sizeof(uint32_t); + for (size_t i = 0; i < num_words; i++) { + write32(&dst_word[i], read32(&src_word[i])); + } +} +#endif + +static int default_max_packet_size(int port_speed) +{ + switch (port_speed) { + case XHCI_LOW_SPEED: + return 8; + case XHCI_FULL_SPEED: + return 64; + case XHCI_HIGH_SPEED: + return 64; + default: + return 512; + } +} + +static int xhci_ep_interval(int config_interval, int port_speed) +{ + if (port_speed < XHCI_HIGH_SPEED) { + int log2_interval = 7; + while ((1 << log2_interval) > config_interval) { + log2_interval--; + } + return 3 + log2_interval; + } else { + if (config_interval >= 1 && config_interval <= 16) { + return config_interval - 1; + } else { + return 3; + } + } +} + +static bool reset_host_controller(xhci_op_regs_t *op_regs) +{ + write32(&op_regs->usb_command, read32(&op_regs->usb_command) | XHCI_USBCMD_HCRST); + + usleep(1*MILLISEC); // some controllers need time to recover from reset + + return wait_until_clr(&op_regs->usb_command, XHCI_USBCMD_HCRST, 1000*MILLISEC) + && wait_until_clr(&op_regs->usb_status, XHCI_USBSTS_CNR, 1000*MILLISEC); +} + +static bool start_host_controller(xhci_op_regs_t *op_regs) +{ + write32(&op_regs->usb_command, read32(&op_regs->usb_command) | XHCI_USBCMD_R_S); + return wait_until_clr(&op_regs->usb_status, XHCI_USBSTS_HCH, 20*MILLISEC); +} + +static bool halt_host_controller(xhci_op_regs_t *op_regs) +{ + write32(&op_regs->usb_command, read32(&op_regs->usb_command) & ~XHCI_USBCMD_R_S); + return wait_until_set(&op_regs->usb_status, XHCI_USBSTS_HCH, 20*MILLISEC); +} + +static int get_xhci_port_speed(xhci_op_regs_t *op_regs, int port_idx) +{ + return (read32(&op_regs->port_regs[port_idx].sc) & XHCI_PORT_SC_PS) >> XHCI_PORT_SC_PS_OFFSET; +} + +static void reset_xhci_port(xhci_op_regs_t *op_regs, int port_idx) +{ + write32(&op_regs->port_regs[port_idx].sc, XHCI_PORT_SC_PP | XHCI_PORT_SC_PR); +} + +static void disable_xhci_port(xhci_op_regs_t *op_regs, int port_idx) +{ + write32(&op_regs->port_regs[port_idx].sc, XHCI_PORT_SC_PP | XHCI_PORT_SC_PED); + wait_until_clr(&op_regs->port_regs[port_idx].sc, XHCI_PORT_SC_PED, 20*MILLISEC); +} + +static void ring_host_controller_doorbell(xhci_db_reg_t *db_regs) +{ + write32(&db_regs[0], 0); +} + +static void ring_device_doorbell(xhci_db_reg_t *db_regs, int slot_id, int db_target) +{ + write32(&db_regs[slot_id], db_target); +} + +static uint32_t event_type(const xhci_trb_t *event) +{ + return event->control & XHCI_TRB_TYPE; +} + +static int event_cc(const xhci_trb_t *event) +{ + return event->params2 >> 24; +} + +static int event_slot_id(const xhci_trb_t *event) +{ + return event->control >> 24; +} + +static int event_ep_id(const xhci_trb_t *event) +{ + return (event->control >> 16) & 0x1f; +} + +static uint32_t enqueue_trb(xhci_trb_t *trb_ring, uint32_t ring_size, uint32_t enqueue_state, + uint32_t control, uint64_t params1, uint32_t params2) +{ + // The ring enqueue state records the current cycle and the next free slot. + uint32_t cycle = enqueue_state / ring_size; + uint32_t index = enqueue_state % ring_size; + + // If at the last slot, insert a Link TRB and start a new cycle. + if (index == (ring_size - 1)) { + write64(&trb_ring[index].params1, (uintptr_t)trb_ring); + write32(&trb_ring[index].params2, 0); + write32(&trb_ring[index].control, XHCI_TRB_LINK | XHCI_TRB_TC | cycle); + cycle ^= 1; + index = 0; + } + + // Insert the TRB. + write64(&trb_ring[index].params1, params1); + write32(&trb_ring[index].params2, params2); + write32(&trb_ring[index].control, control | cycle); + index++; + + // Return the new ring enqueue state. + return cycle * ring_size + index; +} + +static void enqueue_xhci_command(workspace_t *ws, uint32_t control, uint64_t params1, uint32_t params2) +{ + ws->cr_enqueue_state = enqueue_trb(ws->cr, WS_CR_SIZE, ws->cr_enqueue_state, control, params1, params2); +} + +static bool get_xhci_event(workspace_t *ws, xhci_trb_t *event) +{ + // Get the event ring dequeue state, which records the current cycle and next slot to be read. + uint32_t dequeue_state = ws->er_dequeue_state; + uint32_t cycle = dequeue_state / WS_ER_SIZE; + uint32_t index = dequeue_state % WS_ER_SIZE; + + // Copy the next slot. + event->params1 = ws->er[index].params1; + event->params2 = ws->er[index].params2; + event->control = ws->er[index].control; + + // If the cycle count doesn't match, that slot hasn't been filled yet. + if ((event->control & 0x1) != cycle) return false; + + // Advance the dequeue pointer. + write64(&ws->rt_regs->ir[0].erdp, (uintptr_t)(&ws->er[index])); + + // Update the event ring dequeue state. + if (index == (WS_ER_SIZE - 1)) { + cycle ^= 1; + index = 0; + } else { + index++; + } + ws->er_dequeue_state = cycle * WS_ER_SIZE + index; + + return true; +} + +static uint32_t wait_for_xhci_event(workspace_t *ws, uint32_t wanted_type, int max_time, xhci_trb_t *event) +{ + int timer = max_time >> 3; + while (!get_xhci_event(ws, event) || event_type(event) != wanted_type) { + if (timer == 0) return XHCI_EVENT_CC_TIMEOUT; + usleep(8); + timer--; + } + return event_cc(event); +} + +static void issue_setup_stage_trb(ep_tr_t *ep_tr, const volatile uint8_t *packet, int transfer_length) +{ + uint64_t params1 = *(const uint64_t *)packet; + uint32_t params2 = transfer_length; + uint32_t control = XHCI_TRB_SETUP_STAGE | XHCI_TRB_TRT_IN | XHCI_TRB_IDT; + ep_tr->enqueue_state = enqueue_trb(ep_tr->tr, EP_TR_SIZE, ep_tr->enqueue_state, control, params1, params2); +} + +static void issue_data_stage_trb(ep_tr_t *ep_tr, const volatile uint8_t *buffer, uint32_t dir, int transfer_length) +{ + uint64_t params1 = (uintptr_t)buffer; + uint32_t params2 = transfer_length; + uint32_t control = XHCI_TRB_DATA_STAGE | dir; + ep_tr->enqueue_state = enqueue_trb(ep_tr->tr, EP_TR_SIZE, ep_tr->enqueue_state, control, params1, params2); +} + +static void issue_status_stage_trb(ep_tr_t *ep_tr, uint32_t dir) +{ + uint32_t control = XHCI_TRB_STATUS_STAGE | dir | XHCI_TRB_IOC; + ep_tr->enqueue_state = enqueue_trb(ep_tr->tr, EP_TR_SIZE, ep_tr->enqueue_state, control, 0, 0); +} + +static void issue_normal_trb(ep_tr_t *ep_tr, const volatile void *buffer, uint32_t dir, int transfer_length) +{ + uint64_t params1 = (uintptr_t)buffer; + uint32_t params2 = transfer_length; + uint32_t control = XHCI_TRB_NORMAL | dir | XHCI_TRB_IOC; + ep_tr->enqueue_state = enqueue_trb(ep_tr->tr, EP_TR_SIZE, ep_tr->enqueue_state, control, params1, params2); +} + +static bool disable_xhci_slot(workspace_t *ws, int slot_id) +{ + xhci_trb_t event; + + enqueue_xhci_command(ws, XHCI_TRB_DISABLE_SLOT | slot_id << 24, 0, 0); + ring_host_controller_doorbell(ws->db_regs); + if (wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 10*MILLISEC, &event) == XHCI_EVENT_CC_SUCCESS) { + return false; + } + return true; +} + +static int init_device(workspace_t *ws, int port_idx, int slot_type, int context_size, uint64_t device_context_index[]) +{ + xhci_trb_t event; + + // Get the port speed. + + int port_speed = get_xhci_port_speed(ws->op_regs, port_idx); + + // Allocate a device slot and set up its output context. + + enqueue_xhci_command(ws, XHCI_TRB_ENABLE_SLOT | slot_type << 16, 0, 0); + ring_host_controller_doorbell(ws->db_regs); + if (wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 10*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) { + return 0; + } + int slot_id = event_slot_id(&event); + + write64(&device_context_index[slot_id], ws->output_context_addr); + + // Prepare the input context for the ADDRESS_DEVICE command. + + xhci_ctrl_context_t *ctrl_context = (xhci_ctrl_context_t *)ws->input_context_addr; + ctrl_context->add_context_flags = XHCI_CONTEXT_A0 | XHCI_CONTEXT_A1; + + xhci_slot_context_t *slot_context = (xhci_slot_context_t *)(ws->input_context_addr + context_size); + slot_context->root_hub_port_num = 1 + port_idx; + slot_context->params1 = 1 << 27 | port_speed << 20; + + xhci_ep_context_t *ep_context = (xhci_ep_context_t *)(ws->input_context_addr + 2 * context_size); + ep_context->params2 = XHCI_EP_CONTROL << 3 | 3 << 1; // EP Type | CErr + ep_context->max_burst_size = 0; + ep_context->max_packet_size = default_max_packet_size(port_speed); + ep_context->tr_dequeue_ptr = ws->control_ep_tr_addr | 1; + + // Initialise the control endpoint transfer ring. + + ep_tr_t *ep_tr = (ep_tr_t *)ws->control_ep_tr_addr; + ep_tr->enqueue_state = EP_TR_SIZE; // cycle = 1, index = 0 + + // Set the device address. For full speed devices we need to read the first 8 bytes of the device descriptor + // to determine the maximum packet size the device supports and update the device context accordingly. For + // compatibility with some older USB devices we need to read the first 8 bytes of the device descriptor before + // actually setting the address. We can conveniently combine both these requirements. + + int fetch_length = sizeof(usb_device_desc_t); + uint32_t command_flags = 0; + if (port_speed < XHCI_HIGH_SPEED) { + fetch_length = 8; + command_flags = XHCI_TRB_BSR; + } + set_address: + enqueue_xhci_command(ws, XHCI_TRB_ADDRESS_DEVICE | command_flags | slot_id << 24, ws->input_context_addr, 0); + ring_host_controller_doorbell(ws->db_regs); + if (wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) { + goto disable_slot; + } + if (command_flags == 0) { + usleep(2*MILLISEC); // USB set address recovery time. + } + + build_setup_packet(ws->data, 0x80, 0x06, USB_DESC_DEVICE << 8, 0, fetch_length); + issue_setup_stage_trb(ep_tr, ws->data, sizeof(usb_setup_pkt_t)); + issue_data_stage_trb(ep_tr, ws->data, XHCI_TRB_DIR_IN, fetch_length); + issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_OUT); + ring_device_doorbell(ws->db_regs, slot_id, 1); + if (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS + || !valid_usb_device_descriptor(ws->data)) { + goto disable_slot; + } + + if (command_flags == XHCI_TRB_BSR) { + usb_device_desc_t *device = (usb_device_desc_t *)ws->data; + if (!valid_usb_max_packet_size(device->max_packet_size, port_speed == XHCI_LOW_SPEED)) { + return false; + } + if (usb_init_options & USB_EXTRA_RESET) { + reset_xhci_port(ws->op_regs, port_idx); + } + ep_context->max_packet_size = device->max_packet_size; + ep_context->tr_dequeue_ptr += 3 * sizeof(xhci_trb_t); + + fetch_length = sizeof(usb_device_desc_t); + command_flags = 0; + goto set_address; + } + + // Fetch the first configuration descriptor and the associated interface and endpoint descriptors. Start by + // requesting just the configuration descriptor. Then read the descriptor to determine whether we need to + // fetch more data. + + fetch_length = sizeof(usb_config_desc_t); + fetch_config_descriptor: + build_setup_packet(ws->data, 0x80, 0x06, USB_DESC_CONFIG << 8, 0, fetch_length); + issue_setup_stage_trb(ep_tr, ws->data, sizeof(usb_setup_pkt_t)); + issue_data_stage_trb(ep_tr, ws->data, XHCI_TRB_DIR_IN, fetch_length); + issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_OUT); + ring_device_doorbell(ws->db_regs, slot_id, 1); + if (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS + || !valid_usb_config_descriptor(ws->data)) { + goto disable_slot; + } + usb_config_desc_t *config = (usb_config_desc_t *)ws->data; + int total_length = MIN(config->total_length, WS_DATA_SIZE); + if (total_length > fetch_length) { + fetch_length = total_length; + goto fetch_config_descriptor; + } + ws->config_total_length = total_length; + + return slot_id; + + disable_slot: + if (disable_xhci_slot(ws, slot_id)) { + write64(&device_context_index[slot_id], 0); + } + return 0; +} + +static bool configure_interface(workspace_t *ws, int slot_id, int context_size, usb_keyboard_info_t *kbd, int kbd_idx) +{ + xhci_trb_t event; + + // Calculate the endpoint ID. This is used both to select an endpoint context and as a doorbell target. + int ep_id = 2 * kbd->endpoint_num + 1; // EP IN + + // Fill in the lookup tables in the workspace. + ws->kbd_slot_id[kbd_idx] = slot_id; + ws->kbd_ep_id [kbd_idx] = ep_id; + + // The input context has already been initialised, so we just need to change the values used by the + // CONFIGURE_ENDPOINT command before issuing the command. + + xhci_ctrl_context_t *ctrl_context = (xhci_ctrl_context_t *)ws->input_context_addr; + ctrl_context->add_context_flags = XHCI_CONTEXT_A0 | 1 << ep_id; + + xhci_slot_context_t *slot_context = (xhci_slot_context_t *)(ws->input_context_addr + context_size); + slot_context->params1 = ep_id << 27 | kbd->port_speed << 20; + + xhci_ep_context_t *ep_context = (xhci_ep_context_t *)(ws->input_context_addr + (1 + ep_id) * context_size); + ep_context->params1 = 0; + ep_context->params2 = XHCI_EP_INTERRUPT_IN << 3 | 3 << 1; // EP Type | CErr + ep_context->interval = kbd->interval; + ep_context->max_burst_size = 0; + ep_context->max_packet_size = kbd->max_packet_size; + ep_context->tr_dequeue_ptr = (uintptr_t)(&ws->kbd_tr[kbd_idx]) | 1; + ep_context->average_trb_length = sizeof(hid_kbd_rpt_t); + ep_context->max_esit_payload_l = kbd->max_packet_size; + ep_context->max_esit_payload_h = 0; + + enqueue_xhci_command(ws, XHCI_TRB_CONFIGURE_ENDPOINT | slot_id << 24, ws->input_context_addr, 0); + ring_host_controller_doorbell(ws->db_regs); + if (wait_for_xhci_event(ws, XHCI_TRB_COMMAND_COMPLETE, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) { + return false; + } + + // Now configure the device itself. + + ep_tr_t *ep_tr = (ep_tr_t *)ws->control_ep_tr_addr; + + // Set the device configuration. + build_setup_packet(ws->data, 0x00, 0x09, 1, 0, 0); + issue_setup_stage_trb(ep_tr, ws->data, sizeof(usb_setup_pkt_t)); + issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_IN); + ring_device_doorbell(ws->db_regs, slot_id, 1); + if (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) { + return false; + } + + // Set the idle duration to infinite. + build_setup_packet(ws->data, 0x21, 0x0a, 0, kbd->interface_num, 0); + issue_setup_stage_trb(ep_tr, ws->data, sizeof(usb_setup_pkt_t)); + issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_IN); + ring_device_doorbell(ws->db_regs, slot_id, 1); + if (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) { + return false; + } + + // Select the boot protocol. + build_setup_packet(ws->data, 0x21, 0x0b, 0, kbd->interface_num, 0); + issue_setup_stage_trb(ep_tr, ws->data, sizeof(usb_setup_pkt_t)); + issue_status_stage_trb(ep_tr, XHCI_TRB_DIR_IN); + ring_device_doorbell(ws->db_regs, slot_id, 1); + if (wait_for_xhci_event(ws, XHCI_TRB_TRANSFER_EVENT, 100*MILLISEC, &event) != XHCI_EVENT_CC_SUCCESS) { + return false; + } + + return true; +} + +static int identify_keyboard(workspace_t *ws, int slot_id, int ep_id) +{ + for (int kbd_idx = 0; kbd_idx < MAX_KEYBOARDS; kbd_idx++) { + if (slot_id == ws->kbd_slot_id[kbd_idx] && ep_id == ws->kbd_ep_id[kbd_idx]) { + return kbd_idx; + } + } + return -1; +} + +static bool set_heap_segment(void) +{ + // Use the largest 32-bit addressable physical memory segment for the heap. + uintptr_t max_segment_size = 0; + for (int i = 0; i < pm_map_size && pm_map[i].end <= PAGE_C(4,GB); i++) { + uintptr_t segment_size = pm_map[i].end - pm_map[i].start; + if (segment_size >= max_segment_size) { + max_segment_size = segment_size; + heap_segment = i; + } + } + return max_segment_size > 0; +} + +//------------------------------------------------------------------------------ +// Public Functions +//------------------------------------------------------------------------------ + +void *xhci_init(uintptr_t base_addr) +{ + if (heap_segment < 0) { + if (!set_heap_segment()) return NULL; + } + uintptr_t heap_segment_end = pm_map[heap_segment].end; + + uint8_t port_type[XHCI_MAX_PORTS]; + + memset(port_type, 0, sizeof(port_type)); + + xhci_cap_regs_t *cap_regs = (xhci_cap_regs_t *)base_addr; + +#ifdef QEMU_WORKAROUND + xhci_cap_regs_t cap_regs_copy; + memcpy32(&cap_regs_copy, cap_regs, sizeof(cap_regs_copy)); + cap_regs = &cap_regs_copy; +#endif + + // Walk the extra capabilities list. + uintptr_t ext_cap_base = base_addr; + uintptr_t ext_cap_offs = cap_regs->hcc_params1 >> 16; + while (ext_cap_offs != 0) { + ext_cap_base += ext_cap_offs * sizeof(uint32_t); + xhci_ext_cap_t *ext_cap = (xhci_ext_cap_t *)ext_cap_base; + +#ifdef QEMU_WORKAROUND + xhci_ext_cap_t ext_cap_copy; + memcpy32(&ext_cap_copy, ext_cap, sizeof(ext_cap_copy)); + ext_cap = &ext_cap_copy; +#endif + if (ext_cap->id == XHCI_EXT_CAP_LEGACY_SUPPORT) { + xhci_legacy_support_t *legacy_support = (xhci_legacy_support_t *)ext_cap_base; + // Take ownership from the SMM if necessary. + int timer = 1000; + while (legacy_support->bios_owns & 0x1) { + legacy_support->host_owns = 0x1; + if (timer == 0) return NULL; + usleep(1*MILLISEC); + timer--; + } + } + if (ext_cap->id == XHCI_EXT_CAP_SUPPORTED_PROTOCOL) { + xhci_supported_protocol_t *protocol = (xhci_supported_protocol_t *)ext_cap_base; + +#ifdef QEMU_WORKAROUND + xhci_supported_protocol_t protocol_copy; + memcpy32(&protocol_copy, protocol, sizeof(protocol_copy)); + protocol = &protocol_copy; +#endif + // Record the ports covered by this protocol. + uint8_t protocol_type = protocol->params2 & 0x1f; // the Protocol Slot Type + switch (protocol->revision_major) { + case 0x02: + protocol_type |= PORT_TYPE_USB2; + break; + case 0x03: + protocol_type |= PORT_TYPE_USB3; + break; + } +#if 0 + print_usb_info("protocol revision %i.%i type %i offset %i count %i", + protocol->revision_major, protocol->revision_minor / 16, + protocol_type, protocol->port_offset, protocol->port_count); +#endif + for (int i = 0; i < protocol->port_count; i++) { + int port_idx = protocol->port_offset + i - 1; + if (port_idx >= 0 && port_idx < XHCI_MAX_PORTS) { + port_type[port_idx] = protocol_type; + } + } + } + ext_cap_offs = ext_cap->next_offset; + } + + xhci_op_regs_t *op_regs = (xhci_op_regs_t *)(base_addr + cap_regs->cap_length); + xhci_rt_regs_t *rt_regs = (xhci_rt_regs_t *)(base_addr + cap_regs->rts_offset); + xhci_db_reg_t *db_regs = (xhci_db_reg_t *)(base_addr + cap_regs->db_offset); + + // Ensure the controller is halted before resetting it. + if (!halt_host_controller(op_regs)) return NULL; + + if (!reset_host_controller(op_regs)) return NULL; + + // Record the controller page size. + uintptr_t xhci_page_size = (read32(&op_regs->page_size) & 0xffff) << 12; + uintptr_t xhci_page_mask = xhci_page_size - 1; + + // Find the maximum number of device slots the controller supports. + int max_slots = cap_regs->hcs_params1 & 0xff; + + // Find the number of scratchpad buffers the controller wants. + uintptr_t num_scratchpad_buffers = ((cap_regs->hcs_params2 >> 21) & 0x1f) << 5 + | ((cap_regs->hcs_params2 >> 27) & 0x1f); + + // Allocate and clear the scratchpad memory on the heap. This must be aligned to the controller page size. + // TODO: check for heap overflow. + uintptr_t scratchpad_size = num_scratchpad_buffers * xhci_page_size; + pm_map[heap_segment].end -= num_pages(scratchpad_size); + pm_map[heap_segment].end &= ~(xhci_page_mask >> PAGE_SHIFT); + uintptr_t scratchpad_addr = pm_map[heap_segment].end << PAGE_SHIFT; + + memset((void *)scratchpad_addr, 0, scratchpad_size); + + // Allocate and initialise the device context base address and scratchpad buffer arrays on the heap. + // Both need to be aligned on a 64 byte boundary. + // TODO: check for heap overflow. + uintptr_t device_context_index_size = (1 + max_slots) * sizeof(uint64_t); + uintptr_t scratchpad_buffer_index_offs = round_up(device_context_index_size, 64); + uintptr_t scratchpad_buffer_index_size = num_scratchpad_buffers * sizeof(uint64_t); + pm_map[heap_segment].end -= num_pages(scratchpad_buffer_index_offs + scratchpad_buffer_index_size); + uintptr_t device_context_index_addr = pm_map[heap_segment].end << PAGE_SHIFT; + + memset((void *)device_context_index_addr, 0, device_context_index_size); + + uint64_t *device_context_index = (uint64_t *)device_context_index_addr; + if (num_scratchpad_buffers > 0) { + uintptr_t scratchpad_buffer_index_addr = device_context_index_addr + scratchpad_buffer_index_offs; + device_context_index[0] = scratchpad_buffer_index_addr; + uint64_t *scratchpad_buffer_index = (uint64_t *)scratchpad_buffer_index_addr; + for (uintptr_t i = 0; i < num_scratchpad_buffers; i++) { + scratchpad_buffer_index[i] = scratchpad_addr + i * xhci_page_size; + } + } + + // Allocate and initialise a workspace for this controller. This needs to be permanently mapped into virtual + // memory, so allocate it in the first segment. + // TODO: check for segment overflow. + pm_map[0].end -= num_pages(sizeof(workspace_t)); + uintptr_t workspace_addr = pm_map[0].end << PAGE_SHIFT; + workspace_t *ws = (workspace_t *)workspace_addr; + + memset(ws, 0, sizeof(workspace_t)); + + ws->op_regs = op_regs; + ws->rt_regs = rt_regs; + ws->db_regs = db_regs; + + ws->cr_enqueue_state = WS_CR_SIZE; // cycle = 1, index = 0 + ws->er_dequeue_state = WS_ER_SIZE; // cycle = 1, index = 0 + + // Initialise the ERST for the primary interrupter. We only use the first segment. + ws->erst[0].segment_addr = (uintptr_t)(&ws->er); + ws->erst[0].segment_size = WS_ER_SIZE; + + write64(&rt_regs->ir[0].erdp, (uintptr_t)(&ws->er)); + write32(&rt_regs->ir[0].erst_size, 1); + write64(&rt_regs->ir[0].erst_addr, (uintptr_t)(&ws->erst)); + + // Initialise and start the controller. + write64(&op_regs->cr_control, (read64(&op_regs->cr_control) & 0x30) | (uintptr_t)(&ws->cr) | 0x1); + write64(&op_regs->dcbaap, device_context_index_addr); + write32(&op_regs->config, (read32(&op_regs->config) & 0xfffffc00) | max_slots); + if (!start_host_controller(op_regs)) { + pm_map[heap_segment].end = heap_segment_end; + return NULL; + } + + // Record the controller context size. + uint32_t context_size = cap_regs->hcc_params1 & 0x4 ? 64 : 32; + + // Scan the ports, looking for keyboards. + usb_keyboard_info_t keyboard_info[MAX_KEYBOARDS]; + int num_keyboards = 0; + int num_devices = 0; + int num_ports = cap_regs->hcs_params1 & 0xff; + for (int port_idx = 0; port_idx < num_ports; port_idx++) { + if (num_keyboards >= MAX_KEYBOARDS) continue; + + // Check if this port is valid. + if (port_type[port_idx] == 0) continue; + + // Check if anything is connected to this port. + uint32_t port_status = read32(&op_regs->port_regs[port_idx].sc); + if (~port_status & XHCI_PORT_SC_CCS) continue; + num_devices++; + + // Reset the port (USB2 only). + if (port_type[port_idx] & PORT_TYPE_USB2) { + reset_xhci_port(op_regs, port_idx); + } + + // Wait for the device to be enabled. + if (!wait_until_set(&op_regs->port_regs[port_idx].sc, XHCI_PORT_SC_PED, 500*MILLISEC)) continue; + + // Allocate and initialise a private workspace for this device. + // TODO: check for heap overflow. + const size_t device_ws_size = XHCI_MAX_OP_CONTEXT_SIZE + sizeof(ep_tr_t); + pm_map[heap_segment].end -= num_pages(device_ws_size); + uintptr_t device_workspace_addr = pm_map[heap_segment].end << PAGE_SHIFT; + ws->output_context_addr = device_workspace_addr; + ws->control_ep_tr_addr = device_workspace_addr + XHCI_MAX_OP_CONTEXT_SIZE; + + memset((void *)device_workspace_addr, 0, device_ws_size); + + // Temporarily allocate and initialise the input context data structure on the heap. + // As we only need this for the lifetime of this function, there's no need to adjust pm_map. + ws->input_context_addr = device_workspace_addr - XHCI_MAX_IP_CONTEXT_SIZE; + + memset((void *)ws->input_context_addr, 0, XHCI_MAX_IP_CONTEXT_SIZE); + + // Retrieve the protocol slot type. + int slot_type = port_type[port_idx] & PORT_TYPE_PST_MASK; + + // Initialise the device. If successful, this leaves a set of configuration descriptors in the workspace + // data buffer. + int slot_id = init_device(ws, port_idx, slot_type, context_size, device_context_index); + if (slot_id == 0) { + goto disable_port; + } + + // Scan the descriptors to see if this device has one or more keyboard interfaces and if so, record that + // information in the keyboard info table. + usb_keyboard_info_t *new_keyboard_info = &keyboard_info[num_keyboards]; + int new_keyboards = get_usb_keyboard_info_from_descriptors(ws->data, ws->config_total_length, new_keyboard_info, + MAX_KEYBOARDS - num_keyboards); + + // Complete the new entries in the keyboard info table and configure the keyboard interfaces. + for (int kbd_idx = 0; kbd_idx < new_keyboards; kbd_idx++) { + usb_keyboard_info_t *kbd = &new_keyboard_info[kbd_idx]; + + kbd->port_speed = get_xhci_port_speed(op_regs, port_idx); + kbd->interval = xhci_ep_interval(kbd->interval, kbd->port_speed); + configure_interface(ws, slot_id, context_size, kbd, num_keyboards + kbd_idx); + + print_usb_info(" Keyboard found on port %i interface %i endpoint %i", + 1 + port_idx, kbd->interface_num, kbd->endpoint_num); + } + + if (new_keyboards > 0) { + num_keyboards += new_keyboards; + continue; + } + + // If we didn't find any keyboard interfaces, we can free the allocated resources and disable the port. + + if (disable_xhci_slot(ws, slot_id)) { + write64(&device_context_index[slot_id], 0); + } + + disable_port: + disable_xhci_port(op_regs, port_idx); + pm_map[heap_segment].end += num_pages(device_ws_size); + } + + 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) { + // Halt the host controller. + (void)halt_host_controller(op_regs); + + // Free the pages we allocated in segment 0. + pm_map[0].end += num_pages(sizeof(workspace_t)); + + // Free the pages we allocated in the heap segment. + pm_map[heap_segment].end = heap_segment_end; + + return NULL; + } + + // Initialise the interrupt TRB ring for each keyboard interface. + for (int kbd_idx = 0; kbd_idx < num_keyboards; kbd_idx++) { + ep_tr_t *kbd_tr = &ws->kbd_tr[kbd_idx]; + kbd_tr->enqueue_state = EP_TR_SIZE; // cycle = 1, index = 0 + + hid_kbd_rpt_t *kbd_rpt = &ws->kbd_rpt[kbd_idx]; + issue_normal_trb(kbd_tr, kbd_rpt, XHCI_TRB_DIR_IN, sizeof(hid_kbd_rpt_t)); + ring_device_doorbell(ws->db_regs, ws->kbd_slot_id[kbd_idx], ws->kbd_ep_id[kbd_idx]); + } + + return ws; +} + +uint8_t xhci_get_keycode(void *workspace) +{ + workspace_t *ws = (workspace_t *)workspace; + + xhci_trb_t event; + + while (get_xhci_event(ws, &event)) { + if (event_type(&event) != XHCI_TRB_TRANSFER_EVENT || event_cc(&event) != XHCI_EVENT_CC_SUCCESS) continue; + + int kbd_idx = identify_keyboard(ws, event_slot_id(&event), event_ep_id(&event)); + if (kbd_idx < 0) continue; + + hid_kbd_rpt_t *kbd_rpt = &ws->kbd_rpt[kbd_idx]; + uint8_t keycode = kbd_rpt->key_code[0]; + if (keycode != 0) { + int kc_index_i = ws->kc_index_i; + int kc_index_n = (kc_index_i + 1) % WS_KC_BUFFER_SIZE; + if (kc_index_n != ws->kc_index_o) { + ws->kc_buffer[kc_index_i] = keycode; + ws->kc_index_i = kc_index_n; + } + } + + ep_tr_t *kbd_tr = &ws->kbd_tr[kbd_idx]; + issue_normal_trb(kbd_tr, kbd_rpt, XHCI_TRB_DIR_IN, sizeof(hid_kbd_rpt_t)); + ring_device_doorbell(ws->db_regs, ws->kbd_slot_id[kbd_idx], ws->kbd_ep_id[kbd_idx]); + } + + int kc_index_o = ws->kc_index_o; + if (kc_index_o != ws->kc_index_i) { + ws->kc_index_o = (kc_index_o + 1) % WS_KC_BUFFER_SIZE; + return ws->kc_buffer[kc_index_o]; + } + + return 0; +} diff --git a/system/xhci.h b/system/xhci.h new file mode 100644 index 0000000..b3e9283 --- /dev/null +++ b/system/xhci.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0 +#ifndef XHCI_H +#define XHCI_H +/* + * Provides support for USB keyboards connected via an XHCI controller. + * + * Copyright (C) 2021 Martin Whitaker. + */ + +#include + +/* + * Initialises the XHCI device found at base_addr, scans all the attached USB + * devices, and configures any HID USB keyboard devices it finds to generate + * periodic interrupt transfers that report key presses. + */ +void *xhci_init(uintptr_t base_addr); + +/* + * Polls the completed periodic interrupt transfers, stores the keycodes from + * any new key press events in an internal queue, and if the keycode queue is + * not empty, pops and returns the keycode from the front of the queue. + */ +uint8_t xhci_get_keycode(void *ws); + +#endif // XHCI_H