Initial support for native USB keyboard interface.

This adds support for USB keyboards connected directly to an OHCI
or XHCI controller.
This commit is contained in:
Martin Whitaker 2021-12-22 17:31:06 +00:00
parent 4aea5f4d19
commit 8069b8724b
13 changed files with 2644 additions and 22 deletions

View File

@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker. // Copyright (C) 2020-2021 Martin Whitaker.
// //
// Derived from memtest86+ main.c: // Derived from memtest86+ main.c:
// //
@ -151,6 +151,8 @@ static void global_init(void)
config_init(); config_init();
keyboard_init(true);
display_init(); display_init();
error_init(); error_init();

View File

@ -11,13 +11,16 @@ SYS_OBJS = system/cpuid.o \
system/font.o \ system/font.o \
system/hwctrl.o \ system/hwctrl.o \
system/keyboard.o \ system/keyboard.o \
system/ohci.o \
system/pci.o \ system/pci.o \
system/pmem.o \ system/pmem.o \
system/reloc.o \ system/reloc.o \
system/screen.o \ system/screen.o \
system/smp.o \ system/smp.o \
system/temperature.o \ system/temperature.o \
system/vmem.o system/usbkbd.o \
system/vmem.o \
system/xhci.o
LIB_OBJS = lib/barrier.o \ LIB_OBJS = lib/barrier.o \
lib/ctype.o \ lib/ctype.o \

View File

@ -11,13 +11,16 @@ SYS_OBJS = system/cpuid.o \
system/font.o \ system/font.o \
system/hwctrl.o \ system/hwctrl.o \
system/keyboard.o \ system/keyboard.o \
system/ohci.o \
system/pci.o \ system/pci.o \
system/pmem.o \ system/pmem.o \
system/reloc.o \ system/reloc.o \
system/screen.o \ system/screen.o \
system/smp.o \ system/smp.o \
system/temperature.o \ system/temperature.o \
system/vmem.o system/usbkbd.o \
system/vmem.o \
system/xhci.o
LIB_OBJS = lib/barrier.o \ LIB_OBJS = lib/barrier.o \
lib/ctype.o \ lib/ctype.o \

View File

@ -1,9 +1,10 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2020 Martin Whitaker. // Copyright (C) 2020-2021 Martin Whitaker.
#include <stdint.h> #include <stdint.h>
#include "io.h" #include "io.h"
#include "usbkbd.h"
#include "keyboard.h" #include "keyboard.h"
@ -12,7 +13,7 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Convert set 1 scancodes to characters. // Convert set 1 scancodes to characters.
static const char keymap[] = { static const char legacy_keymap[] = {
/* 0x00 */ 0, /* 0x00 */ 0,
/* 0x01 */ ESC, /* 0x01 */ ESC,
/* 0x02 */ '1', /* 0x02 */ '1',
@ -72,16 +73,16 @@ static const char keymap[] = {
/* 0x38 */ 0, /* 0x38 */ 0,
/* 0x39 */ ' ', /* 0x39 */ ' ',
/* 0x3a */ 0, /* 0x3a */ 0,
/* 0x3b */ '1', /* 0x3b */ '1', // F1
/* 0x3c */ '2', /* 0x3c */ '2', // F2
/* 0x3d */ '3', /* 0x3d */ '3', // F3
/* 0x3e */ '4', /* 0x3e */ '4', // F4
/* 0x3f */ '5', /* 0x3f */ '5', // F5
/* 0x40 */ '6', /* 0x40 */ '6', // F6
/* 0x41 */ '7', /* 0x41 */ '7', // F7
/* 0x42 */ '8', /* 0x42 */ '8', // F8
/* 0x43 */ '9', /* 0x43 */ '9', // F9
/* 0x44 */ '0', /* 0x44 */ '0', // F10
/* 0x45 */ 0, /* 0x45 */ 0,
/* 0x46 */ 0, /* 0x46 */ 0,
/* 0x47 */ '7', // keypad /* 0x47 */ '7', // keypad
@ -99,19 +100,147 @@ static const char keymap[] = {
/* 0x53 */ '.', // keypad /* 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 // Public Functions
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void keyboard_init(bool pause_at_end)
{
if (keyboard_types & KT_USB) {
find_usb_keyboards(pause_at_end);
}
}
char get_key(void) char get_key(void)
{ {
uint8_t c = inb(0x64); if (keyboard_types & KT_USB) {
if (c & 0x01) { uint8_t c = get_usb_keycode();
c = inb(0x60); if (c > 0 && c < sizeof(usb_hid_keymap)) {
if (c < sizeof(keymap)) { return usb_hid_keymap[c];
return 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'; return '\0';
} }

View File

@ -1,11 +1,14 @@
// SPDX-License-Identifier: GPL-2.0 // SPDX-License-Identifier: GPL-2.0
#ifndef KEYBOARD_H #ifndef KEYBOARD_H
#define KEYBOARD_H #define KEYBOARD_H
#include <stdbool.h>
/* /*
* Provides the keyboard interface. It converts incoming key codes to * Provides the keyboard interface. It converts incoming key codes to
* ASCII characters. * ASCII characters.
* *
* Copyright (C) 2020 Martin Whitaker. * Copyright (C) 2020-2021 Martin Whitaker.
*/ */
/* /*
@ -13,6 +16,25 @@
*/ */
#define ESC 27 #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 * Checks if a key has been pressed and returns the primary ASCII character
* corresponding to that key if so, otherwise returns the null character. * corresponding to that key if so, otherwise returns the null character.

60
system/memrw32.h Normal file
View File

@ -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 <stdint.h>
/*
* 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

699
system/ohci.c Normal file
View File

@ -0,0 +1,699 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2021 Martin Whitaker.
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#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(&regs->rh_port_status[port_idx], OHCI_PORT_CONNECT_CHG | OHCI_PORT_RESET_CHG);
write32(&regs->rh_port_status[port_idx], OHCI_SET_PORT_RESET);
if (!wait_until_set(&regs->rh_port_status[port_idx], OHCI_PORT_RESET_CHG, 500*MILLISEC)) return false;
}
write32(&regs->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(&regs->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(&regs->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(&regs->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(&regs->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(&regs->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(&regs->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(&regs->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(&regs->revision) & 0xff) != 0x10) {
return NULL;
}
// Take ownership from the SMM if necessary.
if (read32(&regs->control) & OHCI_CTRL_IR) {
write32(&regs->interrupt_enable, OHCI_INTR_OC);
flush32(&regs->command_status, OHCI_CMD_OCR);
if (!wait_until_clr(&regs->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(&regs->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(&regs->control) & OHCI_CTRL_HCFS) {
case OHCI_CTRL_HCFS_RST:
usleep(50*MILLISEC);
break;
case OHCI_CTRL_HCFS_SUS:
case OHCI_CTRL_HCFS_RES:
flush32(&regs->control, OHCI_CTRL_HCFS_SUS);
usleep(10*MILLISEC);
break;
default: // operational
break;
}
// Reset the host controller.
write32(&regs->command_status, OHCI_CMD_HCR);
if (!wait_until_clr(&regs->command_status, OHCI_CMD_HCR, 30)) {
return NULL;
}
// Check we are now in SUSPEND state.
if ((read32(&regs->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(&regs->fm_interval, 1 << 31 | max_packet_size << 16 | fm_interval);
write32(&regs->periodic_start, (fm_interval * 9) / 10);
write32(&regs->hcca, (uintptr_t)(&ws->hcca));
write32(&regs->ctrl_head_ed, (uintptr_t)(&ws->ed[0]));
write32(&regs->bulk_head_ed, 0);
write32(&regs->ctrl_current_ed, 0);
write32(&regs->bulk_current_ed, 0);
write32(&regs->control, OHCI_CTRL_HCFS_RUN | OHCI_CTRL_CLE | OHCI_CTRL_CBSR0);
flush32(&regs->interrupt_status, ~0);
// Power up the ports.
uint32_t rh_descriptor_a = read32(&regs->rh_descriptor_a);
uint32_t rh_descriptor_b = read32(&regs->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(&regs->rh_descriptor_b, rh_descriptor_b & OHCI_RHDB_DR);
}
// Power up all ports.
flush32(&regs->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(&regs->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(&regs->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(&regs->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(&regs->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(&regs->control, OHCI_CTRL_HCFS_RUN | OHCI_CTRL_CLE | OHCI_CTRL_PLE | OHCI_CTRL_CBSR0);
flush32(&regs->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;
}

26
system/ohci.h Normal file
View File

@ -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 <stdint.h>
/*
* 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

89
system/usb.h Normal file
View File

@ -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 <stdbool.h>
#include <stdint.h>
#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

275
system/usbkbd.c Normal file
View File

@ -0,0 +1,275 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2021 Martin Whitaker.
#include <stddef.h>
#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;
}

150
system/usbkbd.h Normal file
View File

@ -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 <stdbool.h>
#include <stdint.h>
#include <usb.h>
/*
* 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

1138
system/xhci.c Normal file

File diff suppressed because it is too large Load Diff

26
system/xhci.h Normal file
View File

@ -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 <stdint.h>
/*
* 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