feat(kernel): Add USB devicde and keyboard/mouse support
This commit is contained in:
@@ -291,4 +291,10 @@ namespace Drivers::PS2::Keyboard {
|
||||
return g_Modifiers;
|
||||
}
|
||||
|
||||
void InjectKeyEvent(const KeyEvent& event) {
|
||||
g_BufferLock.Acquire();
|
||||
BufferPush(event);
|
||||
g_BufferLock.Release();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -49,4 +49,7 @@ namespace Drivers::PS2::Keyboard {
|
||||
// Modifier state query
|
||||
const ModifierState& GetModifiers();
|
||||
|
||||
// Inject a key event from an external source (e.g., USB HID keyboard)
|
||||
void InjectKeyEvent(const KeyEvent& event);
|
||||
|
||||
};
|
||||
@@ -196,4 +196,21 @@ namespace Drivers::PS2::Mouse {
|
||||
g_MaxY = maxY;
|
||||
}
|
||||
|
||||
void InjectMouseReport(uint8_t buttons, int8_t deltaX, int8_t deltaY, int8_t scroll) {
|
||||
g_StateLock.Acquire();
|
||||
|
||||
g_State.X += (int32_t)deltaX;
|
||||
g_State.Y += (int32_t)deltaY;
|
||||
g_State.Buttons = buttons;
|
||||
g_State.ScrollDelta = (int32_t)scroll;
|
||||
|
||||
// Clamp to screen bounds
|
||||
if (g_State.X < 0) g_State.X = 0;
|
||||
if (g_State.Y < 0) g_State.Y = 0;
|
||||
if (g_State.X > g_MaxX) g_State.X = g_MaxX;
|
||||
if (g_State.Y > g_MaxY) g_State.Y = g_MaxY;
|
||||
|
||||
g_StateLock.Release();
|
||||
}
|
||||
|
||||
};
|
||||
@@ -34,4 +34,7 @@ namespace Drivers::PS2::Mouse {
|
||||
|
||||
void SetBounds(int32_t maxX, int32_t maxY);
|
||||
|
||||
// Inject a mouse report from an external source (e.g., USB HID mouse)
|
||||
void InjectMouseReport(uint8_t buttons, int8_t deltaX, int8_t deltaY, int8_t scroll);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,350 @@
|
||||
/*
|
||||
* HidKeyboard.cpp
|
||||
* USB HID Boot Protocol Keyboard driver
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "HidKeyboard.hpp"
|
||||
#include <Drivers/PS2/Keyboard.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Drivers::USB::HidKeyboard {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// USB HID Usage ID -> PS/2 Scancode Set 1 translation table
|
||||
// Index = USB HID Usage ID, Value = PS/2 Scancode Set 1 make code
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static const uint8_t g_HidToScancode[256] = {
|
||||
// 0x00 - 0x03: No Event, Error Roll Over, POST Fail, Error Undefined
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
// 0x04 - 0x1D: Letters a-z
|
||||
0x1E, 0x30, 0x2E, 0x20, 0x12, 0x21, 0x22, 0x23, // a b c d e f g h
|
||||
0x17, 0x24, 0x25, 0x26, 0x32, 0x31, 0x18, 0x19, // i j k l m n o p
|
||||
0x10, 0x13, 0x1F, 0x14, 0x16, 0x2F, 0x11, 0x2D, // q r s t u v w x
|
||||
0x15, 0x2C, // y z
|
||||
// 0x1E - 0x27: Digits 1-9, 0
|
||||
0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, // 1 2 3 4 5 6 7 8
|
||||
0x0A, 0x0B, // 9 0
|
||||
// 0x28 - 0x38: Special keys
|
||||
0x1C, // 0x28 Enter
|
||||
0x01, // 0x29 Escape
|
||||
0x0E, // 0x2A Backspace
|
||||
0x0F, // 0x2B Tab
|
||||
0x39, // 0x2C Space
|
||||
0x0C, // 0x2D Minus
|
||||
0x0D, // 0x2E Equal
|
||||
0x1A, // 0x2F Left Bracket
|
||||
0x1B, // 0x30 Right Bracket
|
||||
0x2B, // 0x31 Backslash
|
||||
0x2B, // 0x32 Non-US # (same as backslash)
|
||||
0x27, // 0x33 Semicolon
|
||||
0x28, // 0x34 Apostrophe
|
||||
0x29, // 0x35 Grave Accent / Tilde
|
||||
0x33, // 0x36 Comma
|
||||
0x34, // 0x37 Period
|
||||
0x35, // 0x38 Slash
|
||||
// 0x39: Caps Lock
|
||||
0x3A,
|
||||
// 0x3A - 0x45: F1 - F12
|
||||
0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, // F1 - F6
|
||||
0x41, 0x42, 0x43, 0x44, 0x57, 0x58, // F7 - F12
|
||||
// 0x46: Print Screen, 0x47: Scroll Lock, 0x48: Pause
|
||||
0x00, 0x46, 0x00,
|
||||
// 0x49 - 0x4E: Insert, Home, Page Up, Delete, End, Page Down
|
||||
0x52, 0x47, 0x49, 0x53, 0x4F, 0x51,
|
||||
// 0x4F - 0x52: Arrow keys (Right, Left, Down, Up)
|
||||
0x4D, 0x4B, 0x50, 0x48,
|
||||
// 0x53: Num Lock
|
||||
0x45,
|
||||
// 0x54 - 0x63: Keypad
|
||||
0x35, // 0x54 KP /
|
||||
0x37, // 0x55 KP *
|
||||
0x4A, // 0x56 KP -
|
||||
0x4E, // 0x57 KP +
|
||||
0x1C, // 0x58 KP Enter
|
||||
0x4F, // 0x59 KP 1
|
||||
0x50, // 0x5A KP 2
|
||||
0x51, // 0x5B KP 3
|
||||
0x4B, // 0x5C KP 4
|
||||
0x4C, // 0x5D KP 5
|
||||
0x4D, // 0x5E KP 6
|
||||
0x47, // 0x5F KP 7
|
||||
0x48, // 0x60 KP 8
|
||||
0x49, // 0x61 KP 9
|
||||
0x52, // 0x62 KP 0
|
||||
0x53, // 0x63 KP .
|
||||
// 0x64 - 0xFF: remaining entries are zero (non-US backslash, application, etc.)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x64 - 0x6B
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x6C - 0x73
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x74 - 0x7B
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x7C - 0x83
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x84 - 0x8B
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x8C - 0x93
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x94 - 0x9B
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x9C - 0xA3
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA4 - 0xAB
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xAC - 0xB3
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xB4 - 0xBB
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xBC - 0xC3
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC4 - 0xCB
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xCC - 0xD3
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD4 - 0xDB
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xDC - 0xE3
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xE4 - 0xEB
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xEC - 0xF3
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xF4 - 0xFB
|
||||
0x00, 0x00, 0x00, 0x00 // 0xFC - 0xFF
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Scancode Set 1 -> ASCII lookup tables (from PS/2 keyboard driver)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static const char g_ScancodeToAscii[128] = {
|
||||
0, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 - 0x07
|
||||
'7', '8', '9', '0', '-', '=', '\b', '\t', // 0x08 - 0x0F
|
||||
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10 - 0x17
|
||||
'o', 'p', '[', ']', '\n', 0, 'a', 's', // 0x18 - 0x1F
|
||||
'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20 - 0x27
|
||||
'\'', '`', 0, '\\', 'z', 'x', 'c', 'v', // 0x28 - 0x2F
|
||||
'b', 'n', 'm', ',', '.', '/', 0, '*', // 0x30 - 0x37
|
||||
0, ' ', 0, 0, 0, 0, 0, 0, // 0x38 - 0x3F
|
||||
0, 0, 0, 0, 0, 0, 0, '7', // 0x40 - 0x47
|
||||
'8', '9', '-', '4', '5', '6', '+', '1', // 0x48 - 0x4F
|
||||
'2', '3', '0', '.', 0, 0, 0, 0, // 0x50 - 0x57
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 0x58 - 0x5F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 0x60 - 0x67
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 0x68 - 0x6F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 0x70 - 0x77
|
||||
0, 0, 0, 0, 0, 0, 0, 0 // 0x78 - 0x7F
|
||||
};
|
||||
|
||||
static const char g_ScancodeToAsciiShifted[128] = {
|
||||
0, 0x1B, '!', '@', '#', '$', '%', '^', // 0x00 - 0x07
|
||||
'&', '*', '(', ')', '_', '+', '\b', '\t', // 0x08 - 0x0F
|
||||
'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', // 0x10 - 0x17
|
||||
'O', 'P', '{', '}', '\n', 0, 'A', 'S', // 0x18 - 0x1F
|
||||
'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', // 0x20 - 0x27
|
||||
'"', '~', 0, '|', 'Z', 'X', 'C', 'V', // 0x28 - 0x2F
|
||||
'B', 'N', 'M', '<', '>', '?', 0, '*', // 0x30 - 0x37
|
||||
0, ' ', 0, 0, 0, 0, 0, 0, // 0x38 - 0x3F
|
||||
0, 0, 0, 0, 0, 0, 0, '7', // 0x40 - 0x47
|
||||
'8', '9', '-', '4', '5', '6', '+', '1', // 0x48 - 0x4F
|
||||
'2', '3', '0', '.', 0, 0, 0, 0, // 0x50 - 0x57
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 0x58 - 0x5F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 0x60 - 0x67
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 0x68 - 0x6F
|
||||
0, 0, 0, 0, 0, 0, 0, 0, // 0x70 - 0x77
|
||||
0, 0, 0, 0, 0, 0, 0, 0 // 0x78 - 0x7F
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// USB HID modifier byte bit definitions
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
constexpr uint8_t MOD_LEFT_CTRL = (1 << 0);
|
||||
constexpr uint8_t MOD_LEFT_SHIFT = (1 << 1);
|
||||
constexpr uint8_t MOD_LEFT_ALT = (1 << 2);
|
||||
constexpr uint8_t MOD_LEFT_GUI = (1 << 3);
|
||||
constexpr uint8_t MOD_RIGHT_CTRL = (1 << 4);
|
||||
constexpr uint8_t MOD_RIGHT_SHIFT = (1 << 5);
|
||||
constexpr uint8_t MOD_RIGHT_ALT = (1 << 6);
|
||||
constexpr uint8_t MOD_RIGHT_GUI = (1 << 7);
|
||||
|
||||
// PS/2 scancodes for modifier keys (for synthetic events)
|
||||
constexpr uint8_t SC_LEFT_CTRL = 0x1D;
|
||||
constexpr uint8_t SC_LEFT_SHIFT = 0x2A;
|
||||
constexpr uint8_t SC_LEFT_ALT = 0x38;
|
||||
constexpr uint8_t SC_RIGHT_CTRL = 0x1D; // Extended in PS/2, but we use same base
|
||||
constexpr uint8_t SC_RIGHT_SHIFT = 0x36;
|
||||
constexpr uint8_t SC_RIGHT_ALT = 0x38; // Extended in PS/2
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// State
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static uint8_t g_SlotId = 0;
|
||||
static uint8_t g_PrevKeys[6] = {};
|
||||
static uint8_t g_PrevModifiers = 0;
|
||||
|
||||
// Typematic repeat state
|
||||
static uint8_t g_RepeatKey = 0; // HID usage ID currently repeating
|
||||
static uint16_t g_HoldCount = 0; // Reports since key was first held
|
||||
|
||||
// Tuned for ~16ms report interval (SET_IDLE(4))
|
||||
constexpr uint16_t TYPEMATIC_DELAY = 31; // ~500ms before repeat starts
|
||||
constexpr uint16_t TYPEMATIC_PERIOD = 2; // ~32ms between repeats (~31 chars/sec)
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Check if a key usage ID is present in a 6-key array
|
||||
static bool KeyInArray(uint8_t key, const uint8_t* arr) {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (arr[i] == key) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if a HID usage ID is a non-character key (arrows, nav, F-keys, etc.)
|
||||
// These share scancodes with keypad numbers but must not produce ASCII.
|
||||
static bool IsNonCharKey(uint8_t hidUsage) {
|
||||
return (hidUsage >= 0x39 && hidUsage <= 0x53);
|
||||
}
|
||||
|
||||
// Build and inject a KeyEvent into the PS/2 keyboard buffer
|
||||
static void InjectKey(uint8_t scancode, bool pressed, uint8_t modifiers,
|
||||
bool nonChar = false) {
|
||||
bool shift = (modifiers & (MOD_LEFT_SHIFT | MOD_RIGHT_SHIFT)) != 0;
|
||||
bool ctrl = (modifiers & (MOD_LEFT_CTRL | MOD_RIGHT_CTRL)) != 0;
|
||||
bool alt = (modifiers & (MOD_LEFT_ALT | MOD_RIGHT_ALT)) != 0;
|
||||
|
||||
char ascii = 0;
|
||||
if (!nonChar && scancode < 128 && pressed) {
|
||||
if (shift) {
|
||||
ascii = g_ScancodeToAsciiShifted[scancode];
|
||||
} else {
|
||||
ascii = g_ScancodeToAscii[scancode];
|
||||
}
|
||||
}
|
||||
|
||||
Drivers::PS2::Keyboard::KeyEvent event = {
|
||||
.Scancode = scancode,
|
||||
.Ascii = ascii,
|
||||
.Pressed = pressed,
|
||||
.Shift = shift,
|
||||
.Ctrl = ctrl,
|
||||
.Alt = alt,
|
||||
.CapsLock = false
|
||||
};
|
||||
|
||||
Drivers::PS2::Keyboard::InjectKeyEvent(event);
|
||||
}
|
||||
|
||||
// Inject a modifier key event
|
||||
static void InjectModifierKey(uint8_t scancode, bool pressed, uint8_t modifiers) {
|
||||
Drivers::PS2::Keyboard::KeyEvent event = {
|
||||
.Scancode = scancode,
|
||||
.Ascii = 0,
|
||||
.Pressed = pressed,
|
||||
.Shift = (modifiers & (MOD_LEFT_SHIFT | MOD_RIGHT_SHIFT)) != 0,
|
||||
.Ctrl = (modifiers & (MOD_LEFT_CTRL | MOD_RIGHT_CTRL)) != 0,
|
||||
.Alt = (modifiers & (MOD_LEFT_ALT | MOD_RIGHT_ALT)) != 0,
|
||||
.CapsLock = false
|
||||
};
|
||||
|
||||
Drivers::PS2::Keyboard::InjectKeyEvent(event);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public API
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
void RegisterDevice(uint8_t slotId) {
|
||||
g_SlotId = slotId;
|
||||
|
||||
// Reset state
|
||||
for (int i = 0; i < 6; i++) g_PrevKeys[i] = 0;
|
||||
g_PrevModifiers = 0;
|
||||
g_RepeatKey = 0;
|
||||
g_HoldCount = 0;
|
||||
|
||||
KernelLogStream(OK, "USB/KB") << "Registered HID keyboard on slot " << (uint64_t)slotId;
|
||||
}
|
||||
|
||||
void ProcessReport(const uint8_t* data, uint16_t length) {
|
||||
if (length < 8) return;
|
||||
|
||||
uint8_t modifiers = data[0];
|
||||
// data[1] is reserved
|
||||
const uint8_t* keys = &data[2];
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Handle modifier changes
|
||||
// -----------------------------------------------------------------
|
||||
uint8_t modChanged = modifiers ^ g_PrevModifiers;
|
||||
|
||||
if (modChanged & MOD_LEFT_CTRL) {
|
||||
InjectModifierKey(SC_LEFT_CTRL, (modifiers & MOD_LEFT_CTRL) != 0, modifiers);
|
||||
}
|
||||
if (modChanged & MOD_LEFT_SHIFT) {
|
||||
InjectModifierKey(SC_LEFT_SHIFT, (modifiers & MOD_LEFT_SHIFT) != 0, modifiers);
|
||||
}
|
||||
if (modChanged & MOD_LEFT_ALT) {
|
||||
InjectModifierKey(SC_LEFT_ALT, (modifiers & MOD_LEFT_ALT) != 0, modifiers);
|
||||
}
|
||||
if (modChanged & MOD_RIGHT_CTRL) {
|
||||
InjectModifierKey(SC_RIGHT_CTRL, (modifiers & MOD_RIGHT_CTRL) != 0, modifiers);
|
||||
}
|
||||
if (modChanged & MOD_RIGHT_SHIFT) {
|
||||
InjectModifierKey(SC_RIGHT_SHIFT, (modifiers & MOD_RIGHT_SHIFT) != 0, modifiers);
|
||||
}
|
||||
if (modChanged & MOD_RIGHT_ALT) {
|
||||
InjectModifierKey(SC_RIGHT_ALT, (modifiers & MOD_RIGHT_ALT) != 0, modifiers);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Detect newly pressed keys (in current but not in previous)
|
||||
// -----------------------------------------------------------------
|
||||
bool newKeyPressed = false;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
uint8_t key = keys[i];
|
||||
if (key == 0) continue;
|
||||
|
||||
if (!KeyInArray(key, g_PrevKeys)) {
|
||||
uint8_t scancode = g_HidToScancode[key];
|
||||
if (scancode != 0) {
|
||||
InjectKey(scancode, true, modifiers, IsNonCharKey(key));
|
||||
g_RepeatKey = key;
|
||||
g_HoldCount = 0;
|
||||
newKeyPressed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Detect released keys (in previous but not in current)
|
||||
// -----------------------------------------------------------------
|
||||
for (int i = 0; i < 6; i++) {
|
||||
uint8_t key = g_PrevKeys[i];
|
||||
if (key == 0) continue;
|
||||
|
||||
if (!KeyInArray(key, keys)) {
|
||||
uint8_t scancode = g_HidToScancode[key];
|
||||
if (scancode != 0) {
|
||||
InjectKey(scancode, false, modifiers, IsNonCharKey(key));
|
||||
}
|
||||
if (key == g_RepeatKey) {
|
||||
g_RepeatKey = 0;
|
||||
g_HoldCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Typematic repeat for held keys
|
||||
// -----------------------------------------------------------------
|
||||
if (!newKeyPressed && g_RepeatKey != 0 && KeyInArray(g_RepeatKey, keys)) {
|
||||
g_HoldCount++;
|
||||
if (g_HoldCount >= TYPEMATIC_DELAY &&
|
||||
(g_HoldCount - TYPEMATIC_DELAY) % TYPEMATIC_PERIOD == 0) {
|
||||
uint8_t scancode = g_HidToScancode[g_RepeatKey];
|
||||
if (scancode != 0) {
|
||||
InjectKey(scancode, true, modifiers, IsNonCharKey(g_RepeatKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Save current state for next report
|
||||
// -----------------------------------------------------------------
|
||||
for (int i = 0; i < 6; i++) g_PrevKeys[i] = keys[i];
|
||||
g_PrevModifiers = modifiers;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* HidKeyboard.hpp
|
||||
* USB HID Boot Protocol Keyboard driver
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Drivers::USB::HidKeyboard {
|
||||
|
||||
// Register a keyboard device by slot ID
|
||||
void RegisterDevice(uint8_t slotId);
|
||||
|
||||
// Process an 8-byte boot protocol keyboard report
|
||||
void ProcessReport(const uint8_t* data, uint16_t length);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* HidMouse.cpp
|
||||
* USB HID Boot Protocol Mouse driver
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "HidMouse.hpp"
|
||||
#include <Drivers/PS2/Mouse.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Drivers::USB::HidMouse {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// State
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static uint8_t g_SlotId = 0;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Public API
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
void RegisterDevice(uint8_t slotId) {
|
||||
g_SlotId = slotId;
|
||||
|
||||
KernelLogStream(OK, "USB/Mouse") << "Registered HID mouse on slot " << (uint64_t)slotId;
|
||||
}
|
||||
|
||||
void ProcessReport(const uint8_t* data, uint16_t length) {
|
||||
if (length < 3) return;
|
||||
|
||||
// Boot protocol mouse report:
|
||||
// Byte 0: Buttons (bit 0 = left, bit 1 = right, bit 2 = middle)
|
||||
// Byte 1: X displacement (signed int8_t)
|
||||
// Byte 2: Y displacement (signed int8_t)
|
||||
// Byte 3: Scroll wheel (signed int8_t, optional)
|
||||
|
||||
uint8_t buttons = data[0] & 0x07;
|
||||
int8_t deltaX = (int8_t)data[1];
|
||||
int8_t deltaY = (int8_t)data[2];
|
||||
int8_t scroll = (length >= 4) ? (int8_t)data[3] : 0;
|
||||
|
||||
Drivers::PS2::Mouse::InjectMouseReport(buttons, deltaX, deltaY, scroll);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* HidMouse.hpp
|
||||
* USB HID Boot Protocol Mouse driver
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Drivers::USB::HidMouse {
|
||||
|
||||
// Register a mouse device by slot ID
|
||||
void RegisterDevice(uint8_t slotId);
|
||||
|
||||
// Process a 3-4 byte boot protocol mouse report
|
||||
void ProcessReport(const uint8_t* data, uint16_t length);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,410 @@
|
||||
/*
|
||||
* UsbDevice.cpp
|
||||
* USB device enumeration and configuration
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "UsbDevice.hpp"
|
||||
#include "Xhci.hpp"
|
||||
#include "HidKeyboard.hpp"
|
||||
#include "HidMouse.hpp"
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
// Access xHCI internal state needed during enumeration
|
||||
namespace Drivers::USB::Xhci {
|
||||
extern volatile uint32_t g_cmdCompletionSlotId;
|
||||
extern uint64_t* g_dcbaa;
|
||||
}
|
||||
|
||||
namespace Drivers::USB::UsbDevice {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static uint16_t MaxPacketSizeForSpeed(uint32_t speed) {
|
||||
switch (speed) {
|
||||
case Xhci::SPEED_LOW: return 8;
|
||||
case Xhci::SPEED_FULL: return 8;
|
||||
case Xhci::SPEED_HIGH: return 64;
|
||||
case Xhci::SPEED_SUPER: return 512;
|
||||
default: return 64;
|
||||
}
|
||||
}
|
||||
|
||||
// Map xHCI port speed to the slot context speed field value.
|
||||
// Per the xHCI spec the slot context speed field uses the same encoding
|
||||
// as PORTSC (1=Full, 2=Low, 3=High, 4=Super).
|
||||
static uint32_t SpeedToSlotContextValue(uint32_t speed) {
|
||||
return speed; // Same encoding
|
||||
}
|
||||
|
||||
// Convert USB endpoint bInterval to xHCI Endpoint Context Interval value.
|
||||
// HS/SS: bInterval is already in 2^(n-1) * 125µs encoding — use directly.
|
||||
// FS/LS: bInterval is in milliseconds (frames) — convert via fls(bInterval * 8).
|
||||
static uint32_t ConvertInterval(uint32_t speed, uint8_t bInterval) {
|
||||
if (bInterval == 0) return 0;
|
||||
|
||||
if (speed == Xhci::SPEED_HIGH || speed == Xhci::SPEED_SUPER) {
|
||||
return bInterval;
|
||||
}
|
||||
|
||||
// FS/LS: bInterval ms → microframes, then find highest set bit position
|
||||
uint32_t microframes = (uint32_t)bInterval * 8;
|
||||
uint32_t interval = 0;
|
||||
while (microframes > 0) {
|
||||
interval++;
|
||||
microframes >>= 1;
|
||||
}
|
||||
if (interval > 15) interval = 15;
|
||||
return interval;
|
||||
}
|
||||
|
||||
// Convert xHCI port speed to a human-readable string
|
||||
static const char* SpeedToString(uint32_t speed) {
|
||||
switch (speed) {
|
||||
case Xhci::SPEED_LOW: return "Low";
|
||||
case Xhci::SPEED_FULL: return "Full";
|
||||
case Xhci::SPEED_HIGH: return "High";
|
||||
case Xhci::SPEED_SUPER: return "Super";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// EnumerateDevice
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
uint8_t EnumerateDevice(uint8_t portId, uint32_t speed) {
|
||||
KernelLogStream(INFO, "USB") << "Enumerating device on port "
|
||||
<< (uint64_t)portId << " speed=" << SpeedToString(speed);
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 1: Enable Slot
|
||||
// -----------------------------------------------------------------
|
||||
Xhci::TRB enableSlotTrb = {};
|
||||
enableSlotTrb.Control = (Xhci::TRB_ENABLE_SLOT << Xhci::TRB_TYPE_SHIFT);
|
||||
|
||||
uint32_t cc = Xhci::SendCommand(enableSlotTrb);
|
||||
if (cc != Xhci::CC_SUCCESS) {
|
||||
KernelLogStream(ERROR, "USB") << "Enable Slot failed, cc=" << (uint64_t)cc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t slotId = (uint8_t)Xhci::g_cmdCompletionSlotId;
|
||||
if (slotId == 0 || slotId > Xhci::MAX_SLOTS) {
|
||||
KernelLogStream(ERROR, "USB") << "Invalid slot ID: " << (uint64_t)slotId;
|
||||
return 0;
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId << " enabled";
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 2: Allocate device output context and set DCBAA entry
|
||||
// -----------------------------------------------------------------
|
||||
auto* dev = Xhci::GetDevice(slotId);
|
||||
dev->Active = true;
|
||||
dev->PortId = portId;
|
||||
dev->Speed = speed;
|
||||
|
||||
// Allocate a zeroed page for the output DeviceContext
|
||||
auto* outputCtx = (Xhci::DeviceContext*)Memory::g_pfa->AllocateZeroed();
|
||||
dev->OutputContext = outputCtx;
|
||||
dev->OutputContextPhys = Memory::SubHHDM(outputCtx);
|
||||
|
||||
// Point DCBAA[slotId] to the output context physical address
|
||||
Xhci::g_dcbaa[slotId] = dev->OutputContextPhys;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 3: Build Input Context for Address Device command
|
||||
// -----------------------------------------------------------------
|
||||
auto* inputCtx = (Xhci::InputContext*)Memory::g_pfa->AllocateZeroed();
|
||||
|
||||
// Input Control Context: add Slot Context (bit 0) and EP0 (bit 1)
|
||||
inputCtx->ICC.AddFlags = 0x3;
|
||||
|
||||
// Slot Context
|
||||
uint32_t speedVal = SpeedToSlotContextValue(speed);
|
||||
uint32_t ctxEntries = 1; // Context Entries = 1 (Slot + EP0 only)
|
||||
inputCtx->Slot.Field0 = (ctxEntries << 27) | (speedVal << 20);
|
||||
inputCtx->Slot.Field1 = ((uint32_t)portId << 16); // Root Hub Port Number
|
||||
|
||||
// EP0 Context
|
||||
// Allocate EP0 transfer ring
|
||||
auto* ep0Ring = (Xhci::TRB*)Memory::g_pfa->AllocateZeroed();
|
||||
dev->EP0Ring = ep0Ring;
|
||||
dev->EP0RingPhys = Memory::SubHHDM(ep0Ring);
|
||||
dev->EP0RingEnqueue = 0;
|
||||
dev->EP0RingCCS = true;
|
||||
|
||||
// Set up Link TRB at last position to wrap back to start
|
||||
// (bit 1 = Toggle Cycle on Link TRBs)
|
||||
Xhci::TRB& ep0Link = ep0Ring[Xhci::XFER_RING_SIZE - 1];
|
||||
ep0Link.Parameter0 = (uint32_t)(dev->EP0RingPhys & 0xFFFFFFFF);
|
||||
ep0Link.Parameter1 = (uint32_t)(dev->EP0RingPhys >> 32);
|
||||
ep0Link.Status = 0;
|
||||
ep0Link.Control = (Xhci::TRB_LINK << Xhci::TRB_TYPE_SHIFT) | Xhci::TRB_ENT;
|
||||
|
||||
uint16_t maxPacket = MaxPacketSizeForSpeed(speed);
|
||||
|
||||
// Field1: CErr=3 (bits 2:1), EP Type=Control=4 (bits 5:3), Max Packet Size (bits 31:16)
|
||||
inputCtx->EP[0].Field1 = (3 << 1)
|
||||
| (Xhci::EP_TYPE_CONTROL << 3)
|
||||
| ((uint32_t)maxPacket << 16);
|
||||
// TR Dequeue Pointer with DCS=1
|
||||
inputCtx->EP[0].TRDequeuePtr = dev->EP0RingPhys | 1;
|
||||
// Average TRB Length = 8
|
||||
inputCtx->EP[0].Field2 = 8;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 4: Address Device command
|
||||
// -----------------------------------------------------------------
|
||||
Xhci::TRB addrTrb = {};
|
||||
uint64_t inputCtxPhys = Memory::SubHHDM(inputCtx);
|
||||
addrTrb.Parameter0 = (uint32_t)(inputCtxPhys & 0xFFFFFFFF);
|
||||
addrTrb.Parameter1 = (uint32_t)(inputCtxPhys >> 32);
|
||||
addrTrb.Control = (Xhci::TRB_ADDRESS_DEVICE << Xhci::TRB_TYPE_SHIFT)
|
||||
| ((uint32_t)slotId << 24);
|
||||
|
||||
cc = Xhci::SendCommand(addrTrb);
|
||||
if (cc != Xhci::CC_SUCCESS) {
|
||||
KernelLogStream(ERROR, "USB") << "Address Device failed, slot="
|
||||
<< (uint64_t)slotId << " cc=" << (uint64_t)cc;
|
||||
dev->Active = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId << " addressed";
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 5: GET_DESCRIPTOR (Device)
|
||||
// -----------------------------------------------------------------
|
||||
DeviceDescriptor devDesc = {};
|
||||
cc = Xhci::ControlTransfer(slotId, REQTYPE_DEV_TO_HOST, REQ_GET_DESCRIPTOR,
|
||||
(DESC_DEVICE << 8), 0, sizeof(DeviceDescriptor),
|
||||
&devDesc, true);
|
||||
if (cc != Xhci::CC_SUCCESS && cc != Xhci::CC_SHORT_PACKET) {
|
||||
KernelLogStream(ERROR, "USB") << "GET_DESCRIPTOR(Device) failed, cc=" << (uint64_t)cc;
|
||||
dev->Active = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
dev->VendorId = devDesc.idVendor;
|
||||
dev->ProductId = devDesc.idProduct;
|
||||
|
||||
KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId
|
||||
<< ": VID:PID = " << base::hex << (uint64_t)devDesc.idVendor
|
||||
<< ":" << (uint64_t)devDesc.idProduct << base::dec;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 6: GET_DESCRIPTOR (Configuration) -- header first, then full
|
||||
// -----------------------------------------------------------------
|
||||
ConfigDescriptor cfgHdr = {};
|
||||
cc = Xhci::ControlTransfer(slotId, REQTYPE_DEV_TO_HOST, REQ_GET_DESCRIPTOR,
|
||||
(DESC_CONFIGURATION << 8), 0, sizeof(ConfigDescriptor),
|
||||
&cfgHdr, true);
|
||||
if (cc != Xhci::CC_SUCCESS && cc != Xhci::CC_SHORT_PACKET) {
|
||||
KernelLogStream(ERROR, "USB") << "GET_DESCRIPTOR(Config header) failed, cc=" << (uint64_t)cc;
|
||||
dev->Active = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t cfgBuf[256] = {};
|
||||
uint16_t totalLen = cfgHdr.wTotalLength;
|
||||
if (totalLen > 256) totalLen = 256;
|
||||
|
||||
cc = Xhci::ControlTransfer(slotId, REQTYPE_DEV_TO_HOST, REQ_GET_DESCRIPTOR,
|
||||
(DESC_CONFIGURATION << 8), 0, totalLen,
|
||||
cfgBuf, true);
|
||||
if (cc != Xhci::CC_SUCCESS && cc != Xhci::CC_SHORT_PACKET) {
|
||||
KernelLogStream(ERROR, "USB") << "GET_DESCRIPTOR(Config full) failed, cc=" << (uint64_t)cc;
|
||||
dev->Active = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 7: Parse configuration descriptor blob
|
||||
// -----------------------------------------------------------------
|
||||
uint16_t offset = 0;
|
||||
bool foundHid = false;
|
||||
bool foundEp = false;
|
||||
|
||||
while (offset + 2 <= totalLen) {
|
||||
uint8_t len = cfgBuf[offset];
|
||||
uint8_t type = cfgBuf[offset + 1];
|
||||
if (len == 0) break;
|
||||
|
||||
if (type == DESC_INTERFACE && offset + sizeof(InterfaceDescriptor) <= totalLen) {
|
||||
auto* iface = (InterfaceDescriptor*)&cfgBuf[offset];
|
||||
// Reset foundHid at each new interface boundary so we don't
|
||||
// accidentally pick up endpoints from a different interface.
|
||||
foundHid = false;
|
||||
if (!foundEp &&
|
||||
iface->bInterfaceClass == CLASS_HID &&
|
||||
iface->bInterfaceSubClass == SUBCLASS_BOOT) {
|
||||
dev->InterfaceClass = iface->bInterfaceClass;
|
||||
dev->InterfaceSubClass = iface->bInterfaceSubClass;
|
||||
dev->InterfaceProtocol = iface->bInterfaceProtocol;
|
||||
foundHid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == DESC_ENDPOINT && foundHid && !foundEp &&
|
||||
offset + sizeof(EndpointDescriptor) <= totalLen) {
|
||||
auto* ep = (EndpointDescriptor*)&cfgBuf[offset];
|
||||
if ((ep->bEndpointAddress & EP_DIR_IN) &&
|
||||
(ep->bmAttributes & EP_XFER_TYPE_MASK) == EP_XFER_INTERRUPT) {
|
||||
dev->InterruptEpNum = ep->bEndpointAddress & 0x0F;
|
||||
dev->InterruptMaxPacket = ep->wMaxPacketSize & 0x7FF;
|
||||
dev->InterruptInterval = ep->bInterval;
|
||||
foundEp = true;
|
||||
}
|
||||
}
|
||||
|
||||
offset += len;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 8: SET_CONFIGURATION
|
||||
// -----------------------------------------------------------------
|
||||
cc = Xhci::ControlTransfer(slotId, REQTYPE_HOST_TO_DEV, REQ_SET_CONFIGURATION,
|
||||
cfgHdr.bConfigurationValue, 0, 0, nullptr, false);
|
||||
if (cc != Xhci::CC_SUCCESS) {
|
||||
KernelLogStream(ERROR, "USB") << "SET_CONFIGURATION failed, cc=" << (uint64_t)cc;
|
||||
dev->Active = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 9: Configure Endpoint (if HID interrupt endpoint was found)
|
||||
// -----------------------------------------------------------------
|
||||
if (foundEp) {
|
||||
// Device Context Index for an IN endpoint: DCI = EpNum * 2 + 1
|
||||
uint8_t dci = dev->InterruptEpNum * 2 + 1;
|
||||
|
||||
auto* inputCtx2 = (Xhci::InputContext*)Memory::g_pfa->AllocateZeroed();
|
||||
|
||||
// ICC: Add slot context (bit 0) and the interrupt endpoint (bit dci)
|
||||
inputCtx2->ICC.AddFlags = (1 << 0) | (1 << dci);
|
||||
|
||||
// Copy the current slot context from the output context
|
||||
inputCtx2->Slot = dev->OutputContext->Slot;
|
||||
|
||||
// Update Context Entries in slot context to at least cover this DCI
|
||||
uint32_t newCtxEntries = dci;
|
||||
inputCtx2->Slot.Field0 = (inputCtx2->Slot.Field0 & ~(0x1Fu << 27))
|
||||
| (newCtxEntries << 27);
|
||||
|
||||
// Allocate interrupt transfer ring
|
||||
auto* intRing = (Xhci::TRB*)Memory::g_pfa->AllocateZeroed();
|
||||
dev->InterruptRing = intRing;
|
||||
dev->InterruptRingPhys = Memory::SubHHDM(intRing);
|
||||
dev->InterruptRingEnqueue = 0;
|
||||
dev->InterruptRingCCS = true;
|
||||
|
||||
// Set up Link TRB at last position
|
||||
Xhci::TRB& intLink = intRing[Xhci::XFER_RING_SIZE - 1];
|
||||
intLink.Parameter0 = (uint32_t)(dev->InterruptRingPhys & 0xFFFFFFFF);
|
||||
intLink.Parameter1 = (uint32_t)(dev->InterruptRingPhys >> 32);
|
||||
intLink.Status = 0;
|
||||
intLink.Control = (Xhci::TRB_LINK << Xhci::TRB_TYPE_SHIFT) | Xhci::TRB_ENT;
|
||||
|
||||
// Endpoint Context for the interrupt IN endpoint
|
||||
auto& epCtx = inputCtx2->EP[dci - 1]; // EP array is 0-indexed, DCI 1 = EP[0]
|
||||
|
||||
// Field0: Interval (bits 23:16) — convert bInterval to xHCI encoding
|
||||
uint32_t xhciInterval = ConvertInterval(speed, dev->InterruptInterval);
|
||||
epCtx.Field0 = (xhciInterval << 16);
|
||||
|
||||
// Field1: CErr=3 (bits 2:1), EP Type=Interrupt IN=7 (bits 5:3),
|
||||
// Max Packet Size (bits 31:16)
|
||||
epCtx.Field1 = (3 << 1)
|
||||
| (Xhci::EP_TYPE_INTERRUPT_IN << 3)
|
||||
| ((uint32_t)dev->InterruptMaxPacket << 16);
|
||||
|
||||
// TR Dequeue Pointer with DCS=1
|
||||
epCtx.TRDequeuePtr = dev->InterruptRingPhys | 1;
|
||||
|
||||
// Average TRB Length
|
||||
epCtx.Field2 = dev->InterruptMaxPacket;
|
||||
|
||||
// Send Configure Endpoint command
|
||||
Xhci::TRB cfgTrb = {};
|
||||
uint64_t inputCtx2Phys = Memory::SubHHDM(inputCtx2);
|
||||
cfgTrb.Parameter0 = (uint32_t)(inputCtx2Phys & 0xFFFFFFFF);
|
||||
cfgTrb.Parameter1 = (uint32_t)(inputCtx2Phys >> 32);
|
||||
cfgTrb.Control = (Xhci::TRB_CONFIGURE_ENDPOINT << Xhci::TRB_TYPE_SHIFT)
|
||||
| ((uint32_t)slotId << 24);
|
||||
|
||||
cc = Xhci::SendCommand(cfgTrb);
|
||||
if (cc != Xhci::CC_SUCCESS) {
|
||||
KernelLogStream(ERROR, "USB") << "Configure Endpoint failed, slot="
|
||||
<< (uint64_t)slotId << " cc=" << (uint64_t)cc;
|
||||
dev->Active = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId
|
||||
<< ": Interrupt EP " << (uint64_t)dev->InterruptEpNum
|
||||
<< " configured (DCI " << (uint64_t)dci << ")";
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 10: SET_PROTOCOL(0) -- request Boot Protocol
|
||||
// -----------------------------------------------------------------
|
||||
if (foundEp) {
|
||||
cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_PROTOCOL,
|
||||
0, 0, 0, nullptr, false);
|
||||
if (cc != Xhci::CC_SUCCESS) {
|
||||
KernelLogStream(WARNING, "USB") << "SET_PROTOCOL(Boot) failed, cc=" << (uint64_t)cc;
|
||||
// Non-fatal: some devices only support boot protocol anyway
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 11: SET_IDLE(4) -- 16ms idle rate for software typematic
|
||||
// -----------------------------------------------------------------
|
||||
if (foundEp) {
|
||||
// wValue upper byte = duration in 4ms units, lower byte = report ID
|
||||
cc = Xhci::ControlTransfer(slotId, REQTYPE_CLASS_IFACE, REQ_SET_IDLE,
|
||||
(4 << 8), 0, 0, nullptr, false);
|
||||
if (cc != Xhci::CC_SUCCESS) {
|
||||
KernelLogStream(WARNING, "USB") << "SET_IDLE(4) failed, cc=" << (uint64_t)cc;
|
||||
// Non-fatal: not all devices support SET_IDLE
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 12: Queue first interrupt transfer
|
||||
// -----------------------------------------------------------------
|
||||
if (foundEp) {
|
||||
Xhci::QueueInterruptTransfer(slotId);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 13: Register with the appropriate HID driver
|
||||
// -----------------------------------------------------------------
|
||||
if (dev->InterfaceProtocol == PROTOCOL_KEYBOARD) {
|
||||
HidKeyboard::RegisterDevice(slotId);
|
||||
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": HID Boot Keyboard";
|
||||
} else if (dev->InterfaceProtocol == PROTOCOL_MOUSE) {
|
||||
HidMouse::RegisterDevice(slotId);
|
||||
KernelLogStream(OK, "USB") << "Slot " << (uint64_t)slotId << ": HID Boot Mouse";
|
||||
} else if (foundEp) {
|
||||
KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId
|
||||
<< ": HID device, protocol=" << (uint64_t)dev->InterfaceProtocol;
|
||||
} else {
|
||||
KernelLogStream(INFO, "USB") << "Slot " << (uint64_t)slotId
|
||||
<< ": Non-HID device, class=" << (uint64_t)devDesc.bDeviceClass;
|
||||
}
|
||||
|
||||
return slotId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* UsbDevice.hpp
|
||||
* USB device enumeration and standard descriptors
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Drivers::USB::UsbDevice {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// USB Standard Descriptors
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct DeviceDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint16_t bcdUSB;
|
||||
uint8_t bDeviceClass;
|
||||
uint8_t bDeviceSubClass;
|
||||
uint8_t bDeviceProtocol;
|
||||
uint8_t bMaxPacketSize0;
|
||||
uint16_t idVendor;
|
||||
uint16_t idProduct;
|
||||
uint16_t bcdDevice;
|
||||
uint8_t iManufacturer;
|
||||
uint8_t iProduct;
|
||||
uint8_t iSerialNumber;
|
||||
uint8_t bNumConfigurations;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ConfigDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint16_t wTotalLength;
|
||||
uint8_t bNumInterfaces;
|
||||
uint8_t bConfigurationValue;
|
||||
uint8_t iConfiguration;
|
||||
uint8_t bmAttributes;
|
||||
uint8_t bMaxPower;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct InterfaceDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bInterfaceNumber;
|
||||
uint8_t bAlternateSetting;
|
||||
uint8_t bNumEndpoints;
|
||||
uint8_t bInterfaceClass;
|
||||
uint8_t bInterfaceSubClass;
|
||||
uint8_t bInterfaceProtocol;
|
||||
uint8_t iInterface;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct EndpointDescriptor {
|
||||
uint8_t bLength;
|
||||
uint8_t bDescriptorType;
|
||||
uint8_t bEndpointAddress;
|
||||
uint8_t bmAttributes;
|
||||
uint16_t wMaxPacketSize;
|
||||
uint8_t bInterval;
|
||||
} __attribute__((packed));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// USB Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Descriptor types
|
||||
constexpr uint8_t DESC_DEVICE = 1;
|
||||
constexpr uint8_t DESC_CONFIGURATION = 2;
|
||||
constexpr uint8_t DESC_INTERFACE = 4;
|
||||
constexpr uint8_t DESC_ENDPOINT = 5;
|
||||
|
||||
// USB class codes
|
||||
constexpr uint8_t CLASS_HID = 0x03;
|
||||
constexpr uint8_t SUBCLASS_BOOT = 0x01;
|
||||
constexpr uint8_t PROTOCOL_KEYBOARD = 0x01;
|
||||
constexpr uint8_t PROTOCOL_MOUSE = 0x02;
|
||||
|
||||
// USB standard requests (bRequest)
|
||||
constexpr uint8_t REQ_GET_DESCRIPTOR = 0x06;
|
||||
constexpr uint8_t REQ_SET_CONFIGURATION = 0x09;
|
||||
constexpr uint8_t REQ_SET_INTERFACE = 0x0B;
|
||||
|
||||
// HID class requests
|
||||
constexpr uint8_t REQ_SET_PROTOCOL = 0x0B;
|
||||
constexpr uint8_t REQ_SET_IDLE = 0x0A;
|
||||
|
||||
// Request type (bmRequestType)
|
||||
constexpr uint8_t REQTYPE_DEV_TO_HOST = 0x80;
|
||||
constexpr uint8_t REQTYPE_HOST_TO_DEV = 0x00;
|
||||
constexpr uint8_t REQTYPE_CLASS_IFACE = 0x21; // Host-to-device, class, interface
|
||||
|
||||
// Endpoint direction mask
|
||||
constexpr uint8_t EP_DIR_IN = 0x80;
|
||||
|
||||
// Endpoint transfer type mask
|
||||
constexpr uint8_t EP_XFER_TYPE_MASK = 0x03;
|
||||
constexpr uint8_t EP_XFER_INTERRUPT = 0x03;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Enumerate a newly connected device on the given port with the given speed.
|
||||
// Returns the assigned slot ID, or 0 on failure.
|
||||
uint8_t EnumerateDevice(uint8_t portId, uint32_t speed);
|
||||
|
||||
};
|
||||
@@ -0,0 +1,973 @@
|
||||
/*
|
||||
* Xhci.cpp
|
||||
* xHCI (USB 3.x) Host Controller driver
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Xhci.hpp"
|
||||
#include "UsbDevice.hpp"
|
||||
#include "HidKeyboard.hpp"
|
||||
#include "HidMouse.hpp"
|
||||
#include <Pci/Pci.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Hal/Apic/Interrupts.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Drivers::USB::Xhci {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Static state
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static bool g_initialized = false;
|
||||
static bool g_bootScanComplete = false; // true after initial port scan finishes
|
||||
|
||||
// Hot-plug deferred work
|
||||
static volatile bool g_hotplugPending[MAX_PORTS] = {};
|
||||
static bool g_hotplugProcessing = false;
|
||||
|
||||
// MMIO region pointers
|
||||
static volatile uint8_t* g_mmioBase = nullptr;
|
||||
static uint8_t g_capLength = 0;
|
||||
static volatile uint8_t* g_opBase = nullptr;
|
||||
static volatile uint8_t* g_rtBase = nullptr;
|
||||
static volatile uint8_t* g_dbBase = nullptr;
|
||||
|
||||
// Controller parameters
|
||||
static uint32_t g_maxSlots = 0;
|
||||
static uint32_t g_maxPorts = 0;
|
||||
|
||||
// DCBAA (Device Context Base Address Array) -- not static: accessed by UsbDevice.cpp
|
||||
uint64_t* g_dcbaa = nullptr;
|
||||
static uint64_t g_dcbaaPhys = 0;
|
||||
|
||||
// Command ring
|
||||
static TRB* g_cmdRing = nullptr;
|
||||
static uint64_t g_cmdRingPhys = 0;
|
||||
static uint32_t g_cmdRingEnqueue = 0;
|
||||
static bool g_cmdRingCCS = true;
|
||||
|
||||
// Event ring
|
||||
static TRB* g_evtRing = nullptr;
|
||||
static uint64_t g_evtRingPhys = 0;
|
||||
static uint32_t g_evtRingDequeue = 0;
|
||||
static bool g_evtRingCCS = true;
|
||||
|
||||
// Event Ring Segment Table
|
||||
static ERSTEntry* g_erst = nullptr;
|
||||
static uint64_t g_erstPhys = 0;
|
||||
|
||||
// Command completion tracking
|
||||
static volatile bool g_cmdCompleted = false;
|
||||
static volatile uint32_t g_cmdCompletionCode = 0;
|
||||
volatile uint32_t g_cmdCompletionSlotId = 0; // not static: accessed by UsbDevice.cpp
|
||||
|
||||
// Transfer completion tracking (for EP0 control transfers during init)
|
||||
static volatile bool g_xferCompleted = false;
|
||||
static volatile uint32_t g_xferCompletionCode = 0;
|
||||
|
||||
// Per-device info
|
||||
static UsbDeviceInfo g_devices[MAX_SLOTS + 1] = {};
|
||||
|
||||
// Interrupt transfer data buffers (per slot)
|
||||
static uint8_t* g_interruptDataBuf[MAX_SLOTS + 1] = {};
|
||||
static uint64_t g_interruptDataBufPhys[MAX_SLOTS + 1] = {};
|
||||
|
||||
// Scratchpad buffer array
|
||||
static uint64_t* g_scratchpadBufs = nullptr;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Register access helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void WriteOp(uint32_t reg, uint32_t value) {
|
||||
*(volatile uint32_t*)(g_opBase + reg) = value;
|
||||
}
|
||||
|
||||
static uint32_t ReadOp(uint32_t reg) {
|
||||
return *(volatile uint32_t*)(g_opBase + reg);
|
||||
}
|
||||
|
||||
static void WriteRt(uint32_t reg, uint32_t value) {
|
||||
*(volatile uint32_t*)(g_rtBase + reg) = value;
|
||||
}
|
||||
|
||||
static uint32_t ReadRt(uint32_t reg) {
|
||||
return *(volatile uint32_t*)(g_rtBase + reg);
|
||||
}
|
||||
|
||||
static uint32_t ReadCap(uint32_t reg) {
|
||||
return *(volatile uint32_t*)(g_mmioBase + reg);
|
||||
}
|
||||
|
||||
static void WriteDoorbell(uint32_t index, uint32_t value) {
|
||||
*(volatile uint32_t*)(g_dbBase + index * 4) = value;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// DMA buffer allocation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static uint8_t* AllocateDmaBuffer(uint64_t& outPhysAddr) {
|
||||
void* virt = Memory::g_pfa->AllocateZeroed();
|
||||
outPhysAddr = Memory::SubHHDM(virt);
|
||||
return (uint8_t*)virt;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Transfer ring advance helpers (handle Link TRB wrap)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// Advance EP0 ring enqueue pointer, activating Link TRB when reached
|
||||
static void AdvanceEP0Ring(UsbDeviceInfo& dev) {
|
||||
dev.EP0RingEnqueue++;
|
||||
if (dev.EP0RingEnqueue >= XFER_RING_SIZE - 1) {
|
||||
// Reached Link TRB — set its cycle bit and wrap
|
||||
TRB& link = dev.EP0Ring[XFER_RING_SIZE - 1];
|
||||
if (dev.EP0RingCCS) {
|
||||
link.Control |= TRB_CYCLE_BIT;
|
||||
} else {
|
||||
link.Control &= ~TRB_CYCLE_BIT;
|
||||
}
|
||||
dev.EP0RingCCS = !dev.EP0RingCCS;
|
||||
dev.EP0RingEnqueue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Advance interrupt ring enqueue pointer, activating Link TRB when reached
|
||||
static void AdvanceInterruptRing(UsbDeviceInfo& dev) {
|
||||
dev.InterruptRingEnqueue++;
|
||||
if (dev.InterruptRingEnqueue >= XFER_RING_SIZE - 1) {
|
||||
TRB& link = dev.InterruptRing[XFER_RING_SIZE - 1];
|
||||
if (dev.InterruptRingCCS) {
|
||||
link.Control |= TRB_CYCLE_BIT;
|
||||
} else {
|
||||
link.Control &= ~TRB_CYCLE_BIT;
|
||||
}
|
||||
dev.InterruptRingCCS = !dev.InterruptRingCCS;
|
||||
dev.InterruptRingEnqueue = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Forward declarations
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void HandleInterrupt(uint8_t irq);
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// MSI setup (same pattern as E1000E)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static bool SetupMsi(uint8_t bus, uint8_t dev, uint8_t func) {
|
||||
uint8_t cap = Pci::FindCapability(bus, dev, func, Pci::PCI_CAP_MSI);
|
||||
if (cap == 0) {
|
||||
KernelLogStream(INFO, "xHCI") << "MSI capability not found";
|
||||
return false;
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "xHCI") << "MSI capability at offset " << base::hex << (uint64_t)cap;
|
||||
|
||||
// Read Message Control (cap+2)
|
||||
uint16_t msgCtrl = Pci::LegacyRead16(bus, dev, func, cap + 2);
|
||||
bool is64bit = (msgCtrl & (1 << 7)) != 0;
|
||||
|
||||
// Write Message Address (cap+4): BSP APIC ID 0, physical destination, fixed delivery
|
||||
Pci::LegacyWrite32(bus, dev, func, cap + 4, MSI_ADDR_BASE);
|
||||
|
||||
// Write Message Data (vector number, edge-triggered, fixed delivery)
|
||||
if (is64bit) {
|
||||
// 64-bit: Upper Address at cap+8, Data at cap+12
|
||||
Pci::LegacyWrite32(bus, dev, func, cap + 8, 0);
|
||||
Pci::LegacyWrite16(bus, dev, func, cap + 12, MSI_VECTOR);
|
||||
} else {
|
||||
// 32-bit: Data at cap+8
|
||||
Pci::LegacyWrite16(bus, dev, func, cap + 8, MSI_VECTOR);
|
||||
}
|
||||
|
||||
// Enable MSI: set bit 0 (MSI Enable), clear bits 6:4 (single message)
|
||||
msgCtrl &= ~(0x70); // Clear Multiple Message Enable (bits 6:4)
|
||||
msgCtrl |= (1 << 0); // MSI Enable
|
||||
Pci::LegacyWrite16(bus, dev, func, cap + 2, msgCtrl);
|
||||
|
||||
// Disable legacy INTx in PCI command register
|
||||
uint16_t pciCmd = Pci::LegacyRead16(bus, dev, func, PCI_REG_COMMAND);
|
||||
pciCmd |= PCI_CMD_INTX_DISABLE;
|
||||
Pci::LegacyWrite16(bus, dev, func, PCI_REG_COMMAND, pciCmd);
|
||||
|
||||
// Register the interrupt handler for MSI vector
|
||||
Hal::RegisterIrqHandler(MSI_IRQ, HandleInterrupt);
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "MSI enabled: vector " << base::dec << (uint64_t)MSI_VECTOR
|
||||
<< " (IRQ slot " << (uint64_t)MSI_IRQ << ")" << (is64bit ? " [64-bit]" : " [32-bit]");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// PollEvents - process event ring
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
void PollEvents() {
|
||||
while (true) {
|
||||
TRB& evt = g_evtRing[g_evtRingDequeue];
|
||||
|
||||
// Check if the cycle bit matches our expected cycle state
|
||||
bool evtCycle = (evt.Control & TRB_CYCLE_BIT) != 0;
|
||||
if (evtCycle != g_evtRingCCS) {
|
||||
break; // No more events
|
||||
}
|
||||
|
||||
uint32_t trbType = (evt.Control & TRB_TYPE_MASK) >> TRB_TYPE_SHIFT;
|
||||
|
||||
switch (trbType) {
|
||||
case TRB_COMMAND_COMPLETION: {
|
||||
uint32_t completionCode = (evt.Status >> 24) & 0xFF;
|
||||
uint32_t slotId = (evt.Control >> 24) & 0xFF;
|
||||
g_cmdCompletionCode = completionCode;
|
||||
g_cmdCompletionSlotId = slotId;
|
||||
g_cmdCompleted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case TRB_PORT_STATUS_CHANGE: {
|
||||
uint32_t portId = (evt.Parameter0 >> 24) & 0xFF;
|
||||
uint32_t portsc = ReadOp(OP_PORTSC_BASE + (portId - 1) * OP_PORTSC_STRIDE);
|
||||
// Clear change bits (write-1-to-clear)
|
||||
WriteOp(OP_PORTSC_BASE + (portId - 1) * OP_PORTSC_STRIDE,
|
||||
(portsc & PORTSC_PRESERVE) | PORTSC_CHANGE_BITS);
|
||||
|
||||
// Defer enumeration to ProcessDeferredWork (called from timer tick)
|
||||
if (g_bootScanComplete && portId >= 1 && portId <= g_maxPorts) {
|
||||
g_hotplugPending[portId - 1] = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TRB_TRANSFER_EVENT: {
|
||||
uint32_t completionCode = (evt.Status >> 24) & 0xFF;
|
||||
uint32_t slotId = (evt.Control >> 24) & 0xFF;
|
||||
uint32_t epDci = (evt.Control >> 16) & 0x1F;
|
||||
|
||||
if (epDci == 1) {
|
||||
// EP0 (DCI 1) - control transfer completion
|
||||
g_xferCompletionCode = completionCode;
|
||||
g_xferCompleted = true;
|
||||
} else if (slotId > 0 && slotId <= MAX_SLOTS && g_devices[slotId].Active) {
|
||||
// Interrupt IN endpoint completion
|
||||
UsbDeviceInfo& dev = g_devices[slotId];
|
||||
|
||||
if (completionCode == CC_SUCCESS || completionCode == CC_SHORT_PACKET) {
|
||||
uint16_t len = dev.InterruptMaxPacket;
|
||||
|
||||
// Residual byte count: bits 23:0 of Status gives residual
|
||||
uint32_t residual = evt.Status & 0x00FFFFFF;
|
||||
if (residual < len) {
|
||||
len = dev.InterruptMaxPacket - (uint16_t)residual;
|
||||
}
|
||||
|
||||
// Dispatch to HID driver based on interface protocol
|
||||
if (dev.InterfaceClass == UsbDevice::CLASS_HID) {
|
||||
if (dev.InterfaceProtocol == UsbDevice::PROTOCOL_KEYBOARD) {
|
||||
HidKeyboard::ProcessReport(g_interruptDataBuf[slotId], len);
|
||||
} else if (dev.InterfaceProtocol == UsbDevice::PROTOCOL_MOUSE) {
|
||||
HidMouse::ProcessReport(g_interruptDataBuf[slotId], len);
|
||||
}
|
||||
}
|
||||
|
||||
// Only re-queue on success — re-queuing on error would
|
||||
// create an infinite loop of failed transfers.
|
||||
QueueInterruptTransfer(slotId);
|
||||
} else {
|
||||
KernelLogStream(WARNING, "xHCI") << "Transfer error on slot "
|
||||
<< base::dec << (uint64_t)slotId << " ep " << (uint64_t)epDci
|
||||
<< " cc=" << (uint64_t)completionCode;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Advance dequeue pointer
|
||||
g_evtRingDequeue++;
|
||||
if (g_evtRingDequeue >= EVT_RING_SIZE) {
|
||||
g_evtRingDequeue = 0;
|
||||
g_evtRingCCS = !g_evtRingCCS;
|
||||
}
|
||||
}
|
||||
|
||||
// Update ERDP to tell the controller we have processed events
|
||||
// Bit 3 (EHB - Event Handler Busy) must be set to clear it
|
||||
uint64_t erdp = g_evtRingPhys + (uint64_t)g_evtRingDequeue * sizeof(TRB);
|
||||
erdp |= (1 << 3); // Set EHB to clear it
|
||||
WriteRt(IR0_ERDP, (uint32_t)(erdp & 0xFFFFFFFF));
|
||||
WriteRt(IR0_ERDP + 4, (uint32_t)(erdp >> 32));
|
||||
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// HandleInterrupt
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static void HandleInterrupt(uint8_t irq) {
|
||||
(void)irq;
|
||||
|
||||
// Clear USBSTS.EINT only (don't accidentally clear other W1C bits)
|
||||
WriteOp(OP_USBSTS, USBSTS_EINT);
|
||||
|
||||
// Clear IMAN.IP and ensure IE stays enabled
|
||||
WriteRt(IR0_IMAN, IMAN_IP | IMAN_IE);
|
||||
|
||||
PollEvents();
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// SendCommand - send a command TRB on the command ring
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
uint32_t SendCommand(const TRB& trb) {
|
||||
// Place TRB at current enqueue position
|
||||
TRB& slot = g_cmdRing[g_cmdRingEnqueue];
|
||||
slot.Parameter0 = trb.Parameter0;
|
||||
slot.Parameter1 = trb.Parameter1;
|
||||
slot.Status = trb.Status;
|
||||
|
||||
// Set the type and cycle bit in control
|
||||
uint32_t control = trb.Control & ~TRB_CYCLE_BIT;
|
||||
if (g_cmdRingCCS) {
|
||||
control |= TRB_CYCLE_BIT;
|
||||
}
|
||||
slot.Control = control;
|
||||
|
||||
// Advance enqueue pointer
|
||||
g_cmdRingEnqueue++;
|
||||
if (g_cmdRingEnqueue >= CMD_RING_SIZE - 1) {
|
||||
// We've reached the Link TRB - toggle its cycle bit and wrap
|
||||
TRB& link = g_cmdRing[CMD_RING_SIZE - 1];
|
||||
// Update the link TRB cycle bit to match current CCS
|
||||
if (g_cmdRingCCS) {
|
||||
link.Control |= TRB_CYCLE_BIT;
|
||||
} else {
|
||||
link.Control &= ~TRB_CYCLE_BIT;
|
||||
}
|
||||
g_cmdRingCCS = !g_cmdRingCCS;
|
||||
g_cmdRingEnqueue = 0;
|
||||
}
|
||||
|
||||
// Clear completion flag and ring the host controller doorbell
|
||||
g_cmdCompleted = false;
|
||||
WriteDoorbell(0, 0);
|
||||
|
||||
// Poll until command completes (with timeout)
|
||||
for (uint32_t i = 0; i < 100000; i++) {
|
||||
PollEvents();
|
||||
if (g_cmdCompleted) {
|
||||
return g_cmdCompletionCode;
|
||||
}
|
||||
// Small delay
|
||||
for (int j = 0; j < 100; j++) {
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
KernelLogStream(WARNING, "xHCI") << "Command timeout";
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ControlTransfer - perform a control transfer on EP0
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
uint32_t ControlTransfer(uint8_t slotId, uint8_t bmRequestType, uint8_t bRequest,
|
||||
uint16_t wValue, uint16_t wIndex, uint16_t wLength,
|
||||
void* data, bool dirIn) {
|
||||
|
||||
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) {
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
UsbDeviceInfo& dev = g_devices[slotId];
|
||||
|
||||
// --- Setup Stage TRB ---
|
||||
TRB& setup = dev.EP0Ring[dev.EP0RingEnqueue];
|
||||
setup.Parameter0 = (uint32_t)bmRequestType | ((uint32_t)bRequest << 8) | ((uint32_t)wValue << 16);
|
||||
setup.Parameter1 = (uint32_t)wIndex | ((uint32_t)wLength << 16);
|
||||
setup.Status = 8; // Setup packet is always 8 bytes
|
||||
|
||||
uint32_t setupControl = (TRB_SETUP_STAGE << TRB_TYPE_SHIFT) | TRB_IDT;
|
||||
if (wLength > 0) {
|
||||
setupControl |= dirIn ? TRB_TRT_IN : TRB_TRT_OUT;
|
||||
} else {
|
||||
setupControl |= TRB_TRT_NODATA;
|
||||
}
|
||||
if (dev.EP0RingCCS) {
|
||||
setupControl |= TRB_CYCLE_BIT;
|
||||
}
|
||||
setup.Control = setupControl;
|
||||
|
||||
// Advance EP0 enqueue (handles Link TRB wrap)
|
||||
AdvanceEP0Ring(dev);
|
||||
|
||||
// --- Data Stage TRB (if wLength > 0) ---
|
||||
if (wLength > 0 && data != nullptr) {
|
||||
uint64_t dataPhys = Memory::SubHHDM(data);
|
||||
|
||||
TRB& dataTrb = dev.EP0Ring[dev.EP0RingEnqueue];
|
||||
dataTrb.Parameter0 = (uint32_t)(dataPhys & 0xFFFFFFFF);
|
||||
dataTrb.Parameter1 = (uint32_t)(dataPhys >> 32);
|
||||
dataTrb.Status = wLength;
|
||||
|
||||
uint32_t dataControl = (TRB_DATA_STAGE << TRB_TYPE_SHIFT);
|
||||
if (dirIn) {
|
||||
dataControl |= TRB_DIR_IN;
|
||||
}
|
||||
if (dev.EP0RingCCS) {
|
||||
dataControl |= TRB_CYCLE_BIT;
|
||||
}
|
||||
dataTrb.Control = dataControl;
|
||||
|
||||
AdvanceEP0Ring(dev);
|
||||
}
|
||||
|
||||
// --- Status Stage TRB ---
|
||||
TRB& status = dev.EP0Ring[dev.EP0RingEnqueue];
|
||||
status.Parameter0 = 0;
|
||||
status.Parameter1 = 0;
|
||||
status.Status = 0;
|
||||
|
||||
// Status stage direction is opposite of data stage
|
||||
uint32_t statusControl = (TRB_STATUS_STAGE << TRB_TYPE_SHIFT) | TRB_IOC;
|
||||
if (wLength > 0 && !dirIn) {
|
||||
statusControl |= TRB_DIR_IN;
|
||||
} else if (wLength == 0) {
|
||||
statusControl |= TRB_DIR_IN;
|
||||
}
|
||||
if (dev.EP0RingCCS) {
|
||||
statusControl |= TRB_CYCLE_BIT;
|
||||
}
|
||||
status.Control = statusControl;
|
||||
|
||||
AdvanceEP0Ring(dev);
|
||||
|
||||
// Ring doorbell for this slot, target EP0 (DCI 1)
|
||||
g_xferCompleted = false;
|
||||
WriteDoorbell(slotId, 1);
|
||||
|
||||
// Poll until transfer completes
|
||||
for (uint32_t i = 0; i < 100000; i++) {
|
||||
PollEvents();
|
||||
if (g_xferCompleted) {
|
||||
return g_xferCompletionCode;
|
||||
}
|
||||
for (int j = 0; j < 100; j++) {
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
KernelLogStream(WARNING, "xHCI") << "Control transfer timeout on slot " << base::dec << (uint64_t)slotId;
|
||||
return 0xFF;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// QueueInterruptTransfer
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
void QueueInterruptTransfer(uint8_t slotId) {
|
||||
if (slotId == 0 || slotId > MAX_SLOTS || !g_devices[slotId].Active) {
|
||||
return;
|
||||
}
|
||||
|
||||
UsbDeviceInfo& dev = g_devices[slotId];
|
||||
|
||||
// Allocate interrupt data buffer if not yet allocated
|
||||
if (g_interruptDataBuf[slotId] == nullptr) {
|
||||
g_interruptDataBuf[slotId] = AllocateDmaBuffer(g_interruptDataBufPhys[slotId]);
|
||||
}
|
||||
|
||||
// Build a Normal TRB on the interrupt ring
|
||||
TRB& trb = dev.InterruptRing[dev.InterruptRingEnqueue];
|
||||
trb.Parameter0 = (uint32_t)(g_interruptDataBufPhys[slotId] & 0xFFFFFFFF);
|
||||
trb.Parameter1 = (uint32_t)(g_interruptDataBufPhys[slotId] >> 32);
|
||||
trb.Status = dev.InterruptMaxPacket;
|
||||
|
||||
uint32_t control = (TRB_NORMAL << TRB_TYPE_SHIFT) | TRB_IOC | TRB_ISP;
|
||||
if (dev.InterruptRingCCS) {
|
||||
control |= TRB_CYCLE_BIT;
|
||||
}
|
||||
trb.Control = control;
|
||||
|
||||
// Advance enqueue (handles Link TRB wrap)
|
||||
AdvanceInterruptRing(dev);
|
||||
|
||||
// Ring doorbell: target = (InterruptEpNum * 2 + 1) for IN endpoint DCI
|
||||
uint8_t target = dev.InterruptEpNum * 2 + 1;
|
||||
WriteDoorbell(slotId, target);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// RingDoorbell
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
void RingDoorbell(uint8_t slotId, uint8_t target) {
|
||||
WriteDoorbell(slotId, target);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// GetDevice
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
UsbDeviceInfo* GetDevice(uint8_t slotId) {
|
||||
if (slotId == 0 || slotId > MAX_SLOTS) {
|
||||
return nullptr;
|
||||
}
|
||||
return &g_devices[slotId];
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// IsInitialized
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
bool IsInitialized() {
|
||||
return g_initialized;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ProcessDeferredWork - handle hot-plug outside interrupt context
|
||||
// Called from timer tick (same pattern as E1000E::Poll)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
void ProcessDeferredWork() {
|
||||
if (!g_initialized || !g_bootScanComplete) return;
|
||||
if (g_hotplugProcessing) return;
|
||||
g_hotplugProcessing = true;
|
||||
|
||||
for (uint32_t port = 0; port < g_maxPorts; port++) {
|
||||
if (!g_hotplugPending[port]) continue;
|
||||
g_hotplugPending[port] = false;
|
||||
|
||||
uint32_t portsc = ReadOp(OP_PORTSC_BASE + port * OP_PORTSC_STRIDE);
|
||||
|
||||
if (portsc & PORTSC_CCS) {
|
||||
// Device connected — check if already assigned to a slot
|
||||
bool alreadyActive = false;
|
||||
for (uint8_t s = 1; s <= MAX_SLOTS; s++) {
|
||||
if (g_devices[s].Active && g_devices[s].PortId == port + 1) {
|
||||
alreadyActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (alreadyActive) continue;
|
||||
|
||||
if (portsc & PORTSC_PED) {
|
||||
// Already enabled — enumerate directly
|
||||
uint32_t speed = (portsc >> 10) & 0xF;
|
||||
UsbDevice::EnumerateDevice(port + 1, speed);
|
||||
} else {
|
||||
// Need port reset first
|
||||
WriteOp(OP_PORTSC_BASE + port * OP_PORTSC_STRIDE,
|
||||
(portsc & PORTSC_PRESERVE) | PORTSC_PR | PORTSC_CHANGE_BITS);
|
||||
|
||||
bool resetDone = false;
|
||||
for (uint32_t i = 0; i < 100000; i++) {
|
||||
PollEvents();
|
||||
uint32_t ps = ReadOp(OP_PORTSC_BASE + port * OP_PORTSC_STRIDE);
|
||||
if (ps & PORTSC_PRC) {
|
||||
resetDone = true;
|
||||
break;
|
||||
}
|
||||
for (int j = 0; j < 100; j++) {
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
if (!resetDone) {
|
||||
KernelLogStream(WARNING, "xHCI") << "Hot-plug: port "
|
||||
<< base::dec << (uint64_t)(port + 1) << " reset timeout";
|
||||
continue;
|
||||
}
|
||||
|
||||
portsc = ReadOp(OP_PORTSC_BASE + port * OP_PORTSC_STRIDE);
|
||||
uint32_t speed = (portsc >> 10) & 0xF;
|
||||
|
||||
// Clear change bits
|
||||
WriteOp(OP_PORTSC_BASE + port * OP_PORTSC_STRIDE,
|
||||
(portsc & PORTSC_PRESERVE) | PORTSC_CHANGE_BITS);
|
||||
|
||||
UsbDevice::EnumerateDevice(port + 1, speed);
|
||||
}
|
||||
} else {
|
||||
// Device disconnected — deactivate its slot
|
||||
for (uint8_t s = 1; s <= MAX_SLOTS; s++) {
|
||||
if (g_devices[s].Active && g_devices[s].PortId == port + 1) {
|
||||
g_devices[s].Active = false;
|
||||
KernelLogStream(INFO, "xHCI") << "Hot-unplug: slot "
|
||||
<< base::dec << (uint64_t)s << " (port "
|
||||
<< (uint64_t)(port + 1) << ") deactivated";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_hotplugProcessing = false;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Initialize
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
void Initialize() {
|
||||
KernelLogStream(INFO, "xHCI") << "Scanning for xHCI controller...";
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 1: Find xHCI controller on PCI bus
|
||||
// -----------------------------------------------------------------
|
||||
auto& devices = Pci::GetDevices();
|
||||
const Pci::PciDevice* foundDev = nullptr;
|
||||
|
||||
for (uint64_t i = 0; i < devices.size(); i++) {
|
||||
if (devices[i].ClassCode == PCI_CLASS_SERIAL &&
|
||||
devices[i].SubClass == PCI_SUBCLASS_USB &&
|
||||
devices[i].ProgIf == PCI_PROGIF_XHCI) {
|
||||
foundDev = &devices[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundDev == nullptr) {
|
||||
KernelLogStream(WARNING, "xHCI") << "No xHCI controller found";
|
||||
return;
|
||||
}
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "Found controller at PCI "
|
||||
<< base::hex << (uint64_t)foundDev->Bus << ":"
|
||||
<< (uint64_t)foundDev->Device << "." << (uint64_t)foundDev->Function;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 2: Read BAR0 and map MMIO region
|
||||
// -----------------------------------------------------------------
|
||||
uint32_t bar0 = Pci::LegacyRead32(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_BAR0);
|
||||
uint64_t mmioPhys = bar0 & 0xFFFFFFF0;
|
||||
|
||||
// Check if 64-bit BAR (type field bits 2:1 == 0b10)
|
||||
if ((bar0 & 0x06) == 0x04) {
|
||||
uint32_t bar1 = Pci::LegacyRead32(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_BAR1);
|
||||
mmioPhys |= ((uint64_t)bar1 << 32);
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "xHCI") << "BAR0 physical: " << base::hex << mmioPhys;
|
||||
|
||||
// Map 64KB (16 pages) of MMIO space
|
||||
constexpr uint64_t MmioSize = 0x10000;
|
||||
for (uint64_t offset = 0; offset < MmioSize; offset += 0x1000) {
|
||||
Memory::VMM::g_paging->MapMMIO(mmioPhys + offset, Memory::HHDM(mmioPhys + offset));
|
||||
}
|
||||
|
||||
g_mmioBase = (volatile uint8_t*)Memory::HHDM(mmioPhys);
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 3: Enable PCI bus master and memory space
|
||||
// -----------------------------------------------------------------
|
||||
uint16_t pciCmd = Pci::LegacyRead16(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_COMMAND);
|
||||
pciCmd |= PCI_CMD_BUS_MASTER | PCI_CMD_MEM_SPACE;
|
||||
Pci::LegacyWrite16(foundDev->Bus, foundDev->Device, foundDev->Function, PCI_REG_COMMAND, pciCmd);
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "Bus mastering enabled";
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 4: Parse capability registers
|
||||
// -----------------------------------------------------------------
|
||||
g_capLength = *(volatile uint8_t*)(g_mmioBase + CAP_CAPLENGTH);
|
||||
|
||||
uint32_t hciVersion = *(volatile uint16_t*)(g_mmioBase + CAP_HCIVERSION);
|
||||
KernelLogStream(INFO, "xHCI") << "Version: " << base::hex << (uint64_t)hciVersion
|
||||
<< ", CapLength: " << (uint64_t)g_capLength;
|
||||
|
||||
uint32_t hcsParams1 = ReadCap(CAP_HCSPARAMS1);
|
||||
g_maxSlots = hcsParams1 & 0xFF;
|
||||
g_maxPorts = (hcsParams1 >> 24) & 0xFF;
|
||||
|
||||
uint32_t hcsParams2 = ReadCap(CAP_HCSPARAMS2);
|
||||
uint32_t scratchpadBufsHi = (hcsParams2 >> 21) & 0x1F;
|
||||
uint32_t scratchpadBufsLo = (hcsParams2 >> 27) & 0x1F;
|
||||
uint32_t maxScratchpadBufs = (scratchpadBufsHi << 5) | scratchpadBufsLo;
|
||||
|
||||
uint32_t dbOff = ReadCap(CAP_DBOFF) & ~0x3u;
|
||||
uint32_t rtsOff = ReadCap(CAP_RTSOFF) & ~0x1Fu;
|
||||
|
||||
g_opBase = g_mmioBase + g_capLength;
|
||||
g_rtBase = g_mmioBase + rtsOff;
|
||||
g_dbBase = g_mmioBase + dbOff;
|
||||
|
||||
KernelLogStream(INFO, "xHCI") << "MaxSlots: " << base::dec << (uint64_t)g_maxSlots
|
||||
<< ", MaxPorts: " << (uint64_t)g_maxPorts
|
||||
<< ", ScratchpadBufs: " << (uint64_t)maxScratchpadBufs;
|
||||
|
||||
// Cap slots to our maximum
|
||||
if (g_maxSlots > MAX_SLOTS) {
|
||||
g_maxSlots = MAX_SLOTS;
|
||||
}
|
||||
if (g_maxPorts > MAX_PORTS) {
|
||||
g_maxPorts = MAX_PORTS;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 5: Halt controller
|
||||
// -----------------------------------------------------------------
|
||||
uint32_t usbcmd = ReadOp(OP_USBCMD);
|
||||
usbcmd &= ~USBCMD_RS;
|
||||
WriteOp(OP_USBCMD, usbcmd);
|
||||
|
||||
// Wait for HCH (Halted) to be set
|
||||
for (uint32_t i = 0; i < 100000; i++) {
|
||||
if (ReadOp(OP_USBSTS) & USBSTS_HCH) {
|
||||
break;
|
||||
}
|
||||
for (int j = 0; j < 10; j++) {
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
if (!(ReadOp(OP_USBSTS) & USBSTS_HCH)) {
|
||||
KernelLogStream(WARNING, "xHCI") << "Controller failed to halt";
|
||||
}
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "Controller halted";
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 6: Reset controller
|
||||
// -----------------------------------------------------------------
|
||||
WriteOp(OP_USBCMD, USBCMD_HCRST);
|
||||
|
||||
// Wait for HCRST to clear
|
||||
for (uint32_t i = 0; i < 100000; i++) {
|
||||
if (!(ReadOp(OP_USBCMD) & USBCMD_HCRST)) {
|
||||
break;
|
||||
}
|
||||
for (int j = 0; j < 10; j++) {
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for CNR (Controller Not Ready) to clear
|
||||
for (uint32_t i = 0; i < 100000; i++) {
|
||||
if (!(ReadOp(OP_USBSTS) & USBSTS_CNR)) {
|
||||
break;
|
||||
}
|
||||
for (int j = 0; j < 10; j++) {
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
if (ReadOp(OP_USBSTS) & USBSTS_CNR) {
|
||||
KernelLogStream(WARNING, "xHCI") << "Controller not ready after reset";
|
||||
}
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "Controller reset complete";
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 7: Program CONFIG register (MaxSlotsEn)
|
||||
// -----------------------------------------------------------------
|
||||
WriteOp(OP_CONFIG, g_maxSlots);
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 8: Allocate DCBAA
|
||||
// -----------------------------------------------------------------
|
||||
g_dcbaa = (uint64_t*)AllocateDmaBuffer(g_dcbaaPhys);
|
||||
|
||||
// Write DCBAAP (64-bit, split into two 32-bit writes)
|
||||
WriteOp(OP_DCBAAP, (uint32_t)(g_dcbaaPhys & 0xFFFFFFFF));
|
||||
WriteOp(OP_DCBAAP + 4, (uint32_t)(g_dcbaaPhys >> 32));
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "DCBAA at phys " << base::hex << g_dcbaaPhys;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 9: Scratchpad buffers
|
||||
// -----------------------------------------------------------------
|
||||
if (maxScratchpadBufs > 0) {
|
||||
uint64_t spArrayPhys;
|
||||
g_scratchpadBufs = (uint64_t*)AllocateDmaBuffer(spArrayPhys);
|
||||
|
||||
for (uint32_t i = 0; i < maxScratchpadBufs; i++) {
|
||||
uint64_t bufPhys;
|
||||
AllocateDmaBuffer(bufPhys);
|
||||
g_scratchpadBufs[i] = bufPhys;
|
||||
}
|
||||
|
||||
// DCBAA[0] = physical address of the scratchpad buffer array
|
||||
g_dcbaa[0] = spArrayPhys;
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "Allocated " << base::dec << (uint64_t)maxScratchpadBufs << " scratchpad buffers";
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 10: Command ring
|
||||
// -----------------------------------------------------------------
|
||||
g_cmdRing = (TRB*)AllocateDmaBuffer(g_cmdRingPhys);
|
||||
|
||||
// Set up Link TRB at the last position
|
||||
TRB& linkTrb = g_cmdRing[CMD_RING_SIZE - 1];
|
||||
linkTrb.Parameter0 = (uint32_t)(g_cmdRingPhys & 0xFFFFFFFF);
|
||||
linkTrb.Parameter1 = (uint32_t)(g_cmdRingPhys >> 32);
|
||||
linkTrb.Status = 0;
|
||||
linkTrb.Control = (TRB_LINK << TRB_TYPE_SHIFT) | TRB_ENT;
|
||||
// Toggle Cycle bit is bit 1 in the Link TRB control - use TRB_ENT which is bit 1
|
||||
|
||||
// Write CRCR = command ring physical address | cycle bit 1
|
||||
uint64_t crcr = g_cmdRingPhys | TRB_CYCLE_BIT;
|
||||
WriteOp(OP_CRCR, (uint32_t)(crcr & 0xFFFFFFFF));
|
||||
WriteOp(OP_CRCR + 4, (uint32_t)(crcr >> 32));
|
||||
|
||||
g_cmdRingCCS = true;
|
||||
g_cmdRingEnqueue = 0;
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "Command ring at phys " << base::hex << g_cmdRingPhys;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 11: Event ring + ERST
|
||||
// -----------------------------------------------------------------
|
||||
g_evtRing = (TRB*)AllocateDmaBuffer(g_evtRingPhys);
|
||||
g_erst = (ERSTEntry*)AllocateDmaBuffer(g_erstPhys);
|
||||
|
||||
// Set up ERST entry 0
|
||||
g_erst[0].RingSegmentBase = g_evtRingPhys;
|
||||
g_erst[0].RingSegmentSize = EVT_RING_SIZE;
|
||||
g_erst[0].Reserved = 0;
|
||||
|
||||
// Write interrupter 0 registers
|
||||
// Order: ERSTSZ → ERDP → ERSTBA (ERSTBA triggers hardware read of ERST)
|
||||
WriteRt(IR0_ERSTSZ, 1);
|
||||
|
||||
WriteRt(IR0_ERDP, (uint32_t)(g_evtRingPhys & 0xFFFFFFFF));
|
||||
WriteRt(IR0_ERDP + 4, (uint32_t)(g_evtRingPhys >> 32));
|
||||
|
||||
// Write ERSTBA last (triggers hardware to read the ERST)
|
||||
WriteRt(IR0_ERSTBA, (uint32_t)(g_erstPhys & 0xFFFFFFFF));
|
||||
WriteRt(IR0_ERSTBA + 4, (uint32_t)(g_erstPhys >> 32));
|
||||
|
||||
g_evtRingCCS = true;
|
||||
g_evtRingDequeue = 0;
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "Event ring at phys " << base::hex << g_evtRingPhys;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 12: MSI setup
|
||||
// -----------------------------------------------------------------
|
||||
if (!SetupMsi(foundDev->Bus, foundDev->Device, foundDev->Function)) {
|
||||
KernelLogStream(WARNING, "xHCI") << "MSI not available, using poll mode";
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 13: Enable interrupter 0
|
||||
// -----------------------------------------------------------------
|
||||
WriteRt(IR0_IMAN, IMAN_IE);
|
||||
WriteRt(IR0_IMOD, 0); // No moderation
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 14: Start controller
|
||||
// -----------------------------------------------------------------
|
||||
WriteOp(OP_USBCMD, USBCMD_RS | USBCMD_INTE | USBCMD_HSEE);
|
||||
|
||||
// Wait for controller to start (HCH should clear)
|
||||
for (uint32_t i = 0; i < 100000; i++) {
|
||||
if (!(ReadOp(OP_USBSTS) & USBSTS_HCH)) {
|
||||
break;
|
||||
}
|
||||
for (int j = 0; j < 10; j++) {
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "Controller started";
|
||||
|
||||
g_initialized = true;
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 14.5: Power on all ports
|
||||
// -----------------------------------------------------------------
|
||||
for (uint32_t port = 0; port < g_maxPorts; port++) {
|
||||
uint32_t portsc = ReadOp(OP_PORTSC_BASE + port * OP_PORTSC_STRIDE);
|
||||
if (!(portsc & PORTSC_PP)) {
|
||||
WriteOp(OP_PORTSC_BASE + port * OP_PORTSC_STRIDE, PORTSC_PP);
|
||||
}
|
||||
}
|
||||
// Wait for port power to stabilize (~20ms)
|
||||
for (int i = 0; i < 20000000; i++) {
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
KernelLogStream(OK, "xHCI") << "All ports powered";
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// Step 15: Port scanning
|
||||
// -----------------------------------------------------------------
|
||||
for (uint32_t port = 0; port < g_maxPorts; port++) {
|
||||
uint32_t portsc = ReadOp(OP_PORTSC_BASE + port * OP_PORTSC_STRIDE);
|
||||
|
||||
// Check if device is connected (CCS)
|
||||
if (!(portsc & PORTSC_CCS)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
KernelLogStream(INFO, "xHCI") << "Port " << base::dec << (uint64_t)(port + 1)
|
||||
<< ": device connected, PORTSC=" << base::hex << (uint64_t)portsc;
|
||||
|
||||
// Reset the port: preserve power, clear change bits, set port reset
|
||||
WriteOp(OP_PORTSC_BASE + port * OP_PORTSC_STRIDE,
|
||||
(portsc & PORTSC_PRESERVE) | PORTSC_PR | PORTSC_CHANGE_BITS);
|
||||
|
||||
// Wait for Port Reset Change (PRC) to be set
|
||||
bool resetDone = false;
|
||||
for (uint32_t i = 0; i < 100000; i++) {
|
||||
PollEvents();
|
||||
uint32_t ps = ReadOp(OP_PORTSC_BASE + port * OP_PORTSC_STRIDE);
|
||||
if (ps & PORTSC_PRC) {
|
||||
resetDone = true;
|
||||
break;
|
||||
}
|
||||
for (int j = 0; j < 100; j++) {
|
||||
asm volatile("" ::: "memory");
|
||||
}
|
||||
}
|
||||
|
||||
if (!resetDone) {
|
||||
KernelLogStream(WARNING, "xHCI") << "Port " << base::dec << (uint64_t)(port + 1) << " reset timeout";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Re-read PORTSC after reset
|
||||
portsc = ReadOp(OP_PORTSC_BASE + port * OP_PORTSC_STRIDE);
|
||||
uint32_t speed = (portsc >> 10) & 0xF;
|
||||
|
||||
// Clear change bits
|
||||
WriteOp(OP_PORTSC_BASE + port * OP_PORTSC_STRIDE,
|
||||
(portsc & PORTSC_PRESERVE) | PORTSC_CHANGE_BITS);
|
||||
|
||||
const char* speedStr = "Unknown";
|
||||
switch (speed) {
|
||||
case SPEED_FULL: speedStr = "Full (12 Mbps)"; break;
|
||||
case SPEED_LOW: speedStr = "Low (1.5 Mbps)"; break;
|
||||
case SPEED_HIGH: speedStr = "High (480 Mbps)"; break;
|
||||
case SPEED_SUPER: speedStr = "Super (5 Gbps)"; break;
|
||||
}
|
||||
|
||||
KernelLogStream(OK, "xHCI") << "Port " << base::dec << (uint64_t)(port + 1)
|
||||
<< ": reset complete, speed=" << speedStr;
|
||||
|
||||
// Enumerate the device (port IDs are 1-based)
|
||||
UsbDevice::EnumerateDevice(port + 1, speed);
|
||||
}
|
||||
|
||||
g_bootScanComplete = true;
|
||||
KernelLogStream(OK, "xHCI") << "Initialization complete";
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Xhci.hpp
|
||||
* xHCI (USB 3.x) Host Controller driver
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Drivers::USB::Xhci {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
constexpr uint32_t MAX_SLOTS = 16;
|
||||
constexpr uint32_t MAX_PORTS = 16;
|
||||
constexpr uint32_t CMD_RING_SIZE = 64;
|
||||
constexpr uint32_t EVT_RING_SIZE = 64;
|
||||
constexpr uint32_t XFER_RING_SIZE = 32;
|
||||
|
||||
// MSI configuration (E1000E uses IRQ 24/vector 56, we use 25/57)
|
||||
constexpr uint8_t MSI_IRQ = 25;
|
||||
constexpr uint32_t MSI_VECTOR = 57;
|
||||
constexpr uint32_t MSI_ADDR_BASE = 0xFEE00000;
|
||||
|
||||
// PCI class/subclass/progif for xHCI
|
||||
constexpr uint8_t PCI_CLASS_SERIAL = 0x0C;
|
||||
constexpr uint8_t PCI_SUBCLASS_USB = 0x03;
|
||||
constexpr uint8_t PCI_PROGIF_XHCI = 0x30;
|
||||
|
||||
// PCI config space offsets
|
||||
constexpr uint8_t PCI_REG_BAR0 = 0x10;
|
||||
constexpr uint8_t PCI_REG_BAR1 = 0x14;
|
||||
constexpr uint8_t PCI_REG_COMMAND = 0x04;
|
||||
|
||||
// PCI command register bits
|
||||
constexpr uint16_t PCI_CMD_BUS_MASTER = (1 << 2);
|
||||
constexpr uint16_t PCI_CMD_MEM_SPACE = (1 << 1);
|
||||
constexpr uint16_t PCI_CMD_INTX_DISABLE = (1 << 10);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// xHCI Capability Register offsets (from BAR0)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
constexpr uint32_t CAP_CAPLENGTH = 0x00; // 1 byte
|
||||
constexpr uint32_t CAP_HCIVERSION = 0x02; // 2 bytes
|
||||
constexpr uint32_t CAP_HCSPARAMS1 = 0x04; // 4 bytes
|
||||
constexpr uint32_t CAP_HCSPARAMS2 = 0x08; // 4 bytes
|
||||
constexpr uint32_t CAP_HCSPARAMS3 = 0x0C; // 4 bytes
|
||||
constexpr uint32_t CAP_HCCPARAMS1 = 0x10; // 4 bytes
|
||||
constexpr uint32_t CAP_DBOFF = 0x14; // 4 bytes
|
||||
constexpr uint32_t CAP_RTSOFF = 0x18; // 4 bytes
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// xHCI Operational Register offsets (from BAR0 + CAPLENGTH)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
constexpr uint32_t OP_USBCMD = 0x00;
|
||||
constexpr uint32_t OP_USBSTS = 0x04;
|
||||
constexpr uint32_t OP_PAGESIZE = 0x08;
|
||||
constexpr uint32_t OP_DNCTRL = 0x14;
|
||||
constexpr uint32_t OP_CRCR = 0x18; // 8 bytes
|
||||
constexpr uint32_t OP_DCBAAP = 0x30; // 8 bytes
|
||||
constexpr uint32_t OP_CONFIG = 0x38;
|
||||
constexpr uint32_t OP_PORTSC_BASE = 0x400;
|
||||
constexpr uint32_t OP_PORTSC_STRIDE = 0x10;
|
||||
|
||||
// USBCMD bits
|
||||
constexpr uint32_t USBCMD_RS = (1 << 0); // Run/Stop
|
||||
constexpr uint32_t USBCMD_HCRST = (1 << 1); // Host Controller Reset
|
||||
constexpr uint32_t USBCMD_INTE = (1 << 2); // Interrupter Enable
|
||||
constexpr uint32_t USBCMD_HSEE = (1 << 3); // Host System Error Enable
|
||||
|
||||
// USBSTS bits
|
||||
constexpr uint32_t USBSTS_HCH = (1 << 0); // HC Halted
|
||||
constexpr uint32_t USBSTS_HSE = (1 << 2); // Host System Error
|
||||
constexpr uint32_t USBSTS_EINT = (1 << 3); // Event Interrupt
|
||||
constexpr uint32_t USBSTS_PCD = (1 << 4); // Port Change Detect
|
||||
constexpr uint32_t USBSTS_CNR = (1 << 11); // Controller Not Ready
|
||||
|
||||
// PORTSC bits
|
||||
constexpr uint32_t PORTSC_CCS = (1 << 0); // Current Connect Status
|
||||
constexpr uint32_t PORTSC_PED = (1 << 1); // Port Enabled/Disabled
|
||||
constexpr uint32_t PORTSC_PR = (1 << 4); // Port Reset
|
||||
constexpr uint32_t PORTSC_PLS_MASK = (0xF << 5); // Port Link State
|
||||
constexpr uint32_t PORTSC_PP = (1 << 9); // Port Power
|
||||
constexpr uint32_t PORTSC_SPEED_MASK = (0xF << 10); // Port Speed
|
||||
constexpr uint32_t PORTSC_PRC = (1 << 21); // Port Reset Change
|
||||
constexpr uint32_t PORTSC_CSC = (1 << 17); // Connect Status Change
|
||||
constexpr uint32_t PORTSC_PEC = (1 << 18); // Port Enabled/Disabled Change
|
||||
constexpr uint32_t PORTSC_WRC = (1 << 19); // Warm Port Reset Change
|
||||
constexpr uint32_t PORTSC_OCC = (1 << 20); // Over-current Change
|
||||
constexpr uint32_t PORTSC_PLC = (1 << 22); // Port Link State Change
|
||||
constexpr uint32_t PORTSC_CEC = (1 << 23); // Port Config Error Change
|
||||
// Write-1-to-clear change bits
|
||||
constexpr uint32_t PORTSC_CHANGE_BITS = PORTSC_CSC | PORTSC_PEC | PORTSC_WRC
|
||||
| PORTSC_OCC | PORTSC_PRC | PORTSC_PLC | PORTSC_CEC;
|
||||
// Bits that must be preserved when writing PORTSC (RW1S/RW1CS excluded)
|
||||
constexpr uint32_t PORTSC_PRESERVE = PORTSC_PP;
|
||||
|
||||
// Port speed values (from PORTSC bits 13:10)
|
||||
constexpr uint32_t SPEED_FULL = 1;
|
||||
constexpr uint32_t SPEED_LOW = 2;
|
||||
constexpr uint32_t SPEED_HIGH = 3;
|
||||
constexpr uint32_t SPEED_SUPER = 4;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Runtime Register offsets (from BAR0 + RTSOFF)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Interrupter 0 registers
|
||||
constexpr uint32_t IR0_IMAN = 0x20; // Interrupter Management
|
||||
constexpr uint32_t IR0_IMOD = 0x24; // Interrupter Moderation
|
||||
constexpr uint32_t IR0_ERSTSZ = 0x28; // Event Ring Segment Table Size
|
||||
constexpr uint32_t IR0_ERSTBA = 0x30; // Event Ring Segment Table Base Address (8 bytes)
|
||||
constexpr uint32_t IR0_ERDP = 0x38; // Event Ring Dequeue Pointer (8 bytes)
|
||||
|
||||
// IMAN bits
|
||||
constexpr uint32_t IMAN_IP = (1 << 0); // Interrupt Pending
|
||||
constexpr uint32_t IMAN_IE = (1 << 1); // Interrupt Enable
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TRB (Transfer Request Block) - 16 bytes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct TRB {
|
||||
uint32_t Parameter0;
|
||||
uint32_t Parameter1;
|
||||
uint32_t Status;
|
||||
uint32_t Control;
|
||||
} __attribute__((packed));
|
||||
|
||||
// TRB type field (bits 15:10 of Control)
|
||||
constexpr uint32_t TRB_TYPE_SHIFT = 10;
|
||||
constexpr uint32_t TRB_TYPE_MASK = (0x3F << TRB_TYPE_SHIFT);
|
||||
|
||||
// TRB types
|
||||
constexpr uint32_t TRB_NORMAL = 1;
|
||||
constexpr uint32_t TRB_SETUP_STAGE = 2;
|
||||
constexpr uint32_t TRB_DATA_STAGE = 3;
|
||||
constexpr uint32_t TRB_STATUS_STAGE = 4;
|
||||
constexpr uint32_t TRB_LINK = 6;
|
||||
constexpr uint32_t TRB_ENABLE_SLOT = 9;
|
||||
constexpr uint32_t TRB_DISABLE_SLOT = 10;
|
||||
constexpr uint32_t TRB_ADDRESS_DEVICE = 11;
|
||||
constexpr uint32_t TRB_CONFIGURE_ENDPOINT = 12;
|
||||
constexpr uint32_t TRB_EVALUATE_CONTEXT = 13;
|
||||
constexpr uint32_t TRB_RESET_ENDPOINT = 14;
|
||||
constexpr uint32_t TRB_NOOP_CMD = 23;
|
||||
constexpr uint32_t TRB_TRANSFER_EVENT = 32;
|
||||
constexpr uint32_t TRB_COMMAND_COMPLETION = 33;
|
||||
constexpr uint32_t TRB_PORT_STATUS_CHANGE = 34;
|
||||
|
||||
// TRB control field bits
|
||||
constexpr uint32_t TRB_CYCLE_BIT = (1 << 0);
|
||||
constexpr uint32_t TRB_IOC = (1 << 5); // Interrupt On Completion
|
||||
constexpr uint32_t TRB_IDT = (1 << 6); // Immediate Data
|
||||
constexpr uint32_t TRB_BSR = (1 << 9); // Block Set Address Request
|
||||
constexpr uint32_t TRB_DIR_IN = (1 << 16); // Direction: 1=IN (device to host)
|
||||
constexpr uint32_t TRB_TRT_IN = (3 << 16); // Transfer Type: IN
|
||||
constexpr uint32_t TRB_TRT_OUT = (2 << 16); // Transfer Type: OUT
|
||||
constexpr uint32_t TRB_TRT_NODATA = (0 << 16); // Transfer Type: No Data Stage
|
||||
constexpr uint32_t TRB_ISP = (1 << 2); // Interrupt on Short Packet
|
||||
constexpr uint32_t TRB_CHAIN = (1 << 4); // Chain bit
|
||||
constexpr uint32_t TRB_ENT = (1 << 1); // Evaluate Next TRB
|
||||
|
||||
// Completion codes (from Status field bits 31:24)
|
||||
constexpr uint32_t CC_SUCCESS = 1;
|
||||
constexpr uint32_t CC_SHORT_PACKET = 13;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Event Ring Segment Table Entry
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct ERSTEntry {
|
||||
uint64_t RingSegmentBase;
|
||||
uint32_t RingSegmentSize;
|
||||
uint32_t Reserved;
|
||||
} __attribute__((packed));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Device Context structures (xHCI spec section 6.2)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct SlotContext {
|
||||
uint32_t Field0; // Route String, Speed, MTT, Hub, Context Entries
|
||||
uint32_t Field1; // Max Exit Latency, Root Hub Port Number, Num Ports
|
||||
uint32_t Field2; // TT Hub Slot ID, TT Port Number, Interrupter Target
|
||||
uint32_t Field3; // Device Address, Slot State
|
||||
uint32_t Reserved[4];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct EndpointContext {
|
||||
uint32_t Field0; // EP State, Mult, MaxPStreams, Interval, LSA
|
||||
uint32_t Field1; // CErr, EP Type, HID, Max Burst Size, Max Packet Size
|
||||
uint64_t TRDequeuePtr; // TR Dequeue Pointer (with DCS at bit 0)
|
||||
uint32_t Field2; // Average TRB Length, Max ESIT Payload Lo
|
||||
uint32_t Reserved[3];
|
||||
} __attribute__((packed));
|
||||
|
||||
// Endpoint types (bits 5:3 of EP Field1)
|
||||
constexpr uint32_t EP_TYPE_ISOCH_OUT = 1;
|
||||
constexpr uint32_t EP_TYPE_BULK_OUT = 2;
|
||||
constexpr uint32_t EP_TYPE_INTERRUPT_OUT = 3;
|
||||
constexpr uint32_t EP_TYPE_CONTROL = 4;
|
||||
constexpr uint32_t EP_TYPE_ISOCH_IN = 5;
|
||||
constexpr uint32_t EP_TYPE_BULK_IN = 6;
|
||||
constexpr uint32_t EP_TYPE_INTERRUPT_IN = 7;
|
||||
|
||||
struct InputControlContext {
|
||||
uint32_t DropFlags;
|
||||
uint32_t AddFlags;
|
||||
uint32_t Reserved[5];
|
||||
uint8_t ConfigValue;
|
||||
uint8_t InterfaceNumber;
|
||||
uint8_t AlternateSetting;
|
||||
uint8_t Reserved2;
|
||||
} __attribute__((packed));
|
||||
|
||||
// Full InputContext: InputControlContext + SlotContext + 31 EndpointContexts
|
||||
// (but we only use EP0 + a few endpoints)
|
||||
struct InputContext {
|
||||
InputControlContext ICC;
|
||||
SlotContext Slot;
|
||||
EndpointContext EP[31];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct DeviceContext {
|
||||
SlotContext Slot;
|
||||
EndpointContext EP[31];
|
||||
} __attribute__((packed));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Per-device tracking
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
struct UsbDeviceInfo {
|
||||
bool Active;
|
||||
uint8_t PortId;
|
||||
uint32_t Speed;
|
||||
uint16_t VendorId;
|
||||
uint16_t ProductId;
|
||||
uint8_t InterfaceClass;
|
||||
uint8_t InterfaceSubClass;
|
||||
uint8_t InterfaceProtocol;
|
||||
|
||||
// Interrupt IN endpoint
|
||||
uint8_t InterruptEpNum; // Endpoint number (1-15)
|
||||
uint16_t InterruptMaxPacket;
|
||||
uint8_t InterruptInterval;
|
||||
|
||||
// Transfer ring for Interrupt IN endpoint
|
||||
TRB* InterruptRing;
|
||||
uint64_t InterruptRingPhys;
|
||||
uint32_t InterruptRingEnqueue;
|
||||
bool InterruptRingCCS; // Current Cycle State
|
||||
|
||||
// EP0 transfer ring
|
||||
TRB* EP0Ring;
|
||||
uint64_t EP0RingPhys;
|
||||
uint32_t EP0RingEnqueue;
|
||||
bool EP0RingCCS;
|
||||
|
||||
// Device context (output)
|
||||
DeviceContext* OutputContext;
|
||||
uint64_t OutputContextPhys;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public API
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void Initialize();
|
||||
bool IsInitialized();
|
||||
|
||||
// Deferred hot-plug processing (call from timer tick, not interrupt context)
|
||||
void ProcessDeferredWork();
|
||||
|
||||
// Send a command on the command ring, wait for completion.
|
||||
// Returns completion code.
|
||||
uint32_t SendCommand(const TRB& trb);
|
||||
|
||||
// Perform a control transfer on slot's EP0.
|
||||
// setup: 8 bytes of USB setup packet (packed into TRB params)
|
||||
// data: optional data buffer (virtual address), dataLen: length
|
||||
// dirIn: true = device-to-host
|
||||
// Returns completion code.
|
||||
uint32_t ControlTransfer(uint8_t slotId, uint8_t bmRequestType, uint8_t bRequest,
|
||||
uint16_t wValue, uint16_t wIndex, uint16_t wLength,
|
||||
void* data, bool dirIn);
|
||||
|
||||
// Queue an interrupt IN transfer on a device's interrupt endpoint
|
||||
void QueueInterruptTransfer(uint8_t slotId);
|
||||
|
||||
// Ring a doorbell
|
||||
void RingDoorbell(uint8_t slotId, uint8_t target);
|
||||
|
||||
// Access device info
|
||||
UsbDeviceInfo* GetDevice(uint8_t slotId);
|
||||
|
||||
// Poll event ring (called from interrupt handler or during init)
|
||||
void PollEvents();
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user