mirror of
https://github.com/memtest86plus/memtest86plus.git
synced 2025-02-25 18:55:23 -06:00
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:
parent
4aea5f4d19
commit
8069b8724b
@ -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();
|
||||||
|
@ -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 \
|
||||||
|
@ -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 \
|
||||||
|
@ -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';
|
||||||
}
|
}
|
||||||
|
@ -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
60
system/memrw32.h
Normal 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
699
system/ohci.c
Normal 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(®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;
|
||||||
|
}
|
26
system/ohci.h
Normal file
26
system/ohci.h
Normal 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
89
system/usb.h
Normal 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
275
system/usbkbd.c
Normal 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
150
system/usbkbd.h
Normal 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
1138
system/xhci.c
Normal file
File diff suppressed because it is too large
Load Diff
26
system/xhci.h
Normal file
26
system/xhci.h
Normal 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
|
Loading…
Reference in New Issue
Block a user