From d7b17d9a6194ed8955e20455d9d124cf88000fc8 Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Thu, 19 Feb 2026 23:06:18 +0100 Subject: [PATCH] feat(kernel): Add USB devicde and keyboard/mouse support --- .gitignore | 3 +- GNUmakefile | 36 +- kernel/src/Api/Syscall.cpp | 4 +- kernel/src/Api/Syscall.hpp | 1 + kernel/src/Drivers/PS2/Keyboard.cpp | 6 + kernel/src/Drivers/PS2/Keyboard.hpp | 3 + kernel/src/Drivers/PS2/Mouse.cpp | 17 + kernel/src/Drivers/PS2/Mouse.hpp | 3 + kernel/src/Drivers/USB/HidKeyboard.cpp | 350 +++++++++ kernel/src/Drivers/USB/HidKeyboard.hpp | 18 + kernel/src/Drivers/USB/HidMouse.cpp | 49 ++ kernel/src/Drivers/USB/HidMouse.hpp | 18 + kernel/src/Drivers/USB/UsbDevice.cpp | 410 +++++++++++ kernel/src/Drivers/USB/UsbDevice.hpp | 110 +++ kernel/src/Drivers/USB/Xhci.cpp | 973 +++++++++++++++++++++++++ kernel/src/Drivers/USB/Xhci.hpp | 305 ++++++++ kernel/src/Fs/Ramdisk.cpp | 4 - kernel/src/Main.cpp | 5 + kernel/src/Terminal/Terminal.cpp | 76 ++ kernel/src/Terminal/Terminal.hpp | 8 + kernel/src/Timekeeping/ApicTimer.cpp | 2 + 21 files changed, 2377 insertions(+), 24 deletions(-) create mode 100644 kernel/src/Drivers/USB/HidKeyboard.cpp create mode 100644 kernel/src/Drivers/USB/HidKeyboard.hpp create mode 100644 kernel/src/Drivers/USB/HidMouse.cpp create mode 100644 kernel/src/Drivers/USB/HidMouse.hpp create mode 100644 kernel/src/Drivers/USB/UsbDevice.cpp create mode 100644 kernel/src/Drivers/USB/UsbDevice.hpp create mode 100644 kernel/src/Drivers/USB/Xhci.cpp create mode 100644 kernel/src/Drivers/USB/Xhci.hpp diff --git a/.gitignore b/.gitignore index 7e88317..ffe1dfe 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ toolchain/build/ toolchain/src/ programs/bin/ programs/obj/ -programs/src/doom/obj/ \ No newline at end of file +programs/src/doom/obj/ +programs/gui/icons/ diff --git a/GNUmakefile b/GNUmakefile index b5d715b..4b1aa05 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -57,9 +57,9 @@ run-aarch64: $(IMAGE_NAME).iso -M virt \ -cpu cortex-a72 \ -device ramfb \ - -device qemu-xhci \ - -device usb-kbd \ - -device usb-mouse \ + -device qemu-xhci,id=xhci \ + -device usb-kbd,bus=xhci.0 \ + -device usb-mouse,bus=xhci.0 \ -bios /usr/share/ovmf/OVMF.fd \ -cdrom $(IMAGE_NAME).iso \ $(QEMUFLAGS) @@ -70,9 +70,9 @@ run-hdd-aarch64: $(IMAGE_NAME).hdd -M virt \ -cpu cortex-a72 \ -device ramfb \ - -device qemu-xhci \ - -device usb-kbd \ - -device usb-mouse \ + -device qemu-xhci,id=xhci \ + -device usb-kbd,bus=xhci.0 \ + -device usb-mouse,bus=xhci.0 \ -bios /usr/share/ovmf/OVMF.fd \ -hda $(IMAGE_NAME).hdd \ $(QEMUFLAGS) @@ -83,9 +83,9 @@ run-riscv64: $(IMAGE_NAME).iso -M virt \ -cpu rv64 \ -device ramfb \ - -device qemu-xhci \ - -device usb-kbd \ - -device usb-mouse \ + -device qemu-xhci,id=xhci \ + -device usb-kbd,bus=xhci.0 \ + -device usb-mouse,bus=xhci.0 \ -bios /usr/share/ovmf/OVMF.fd \ -cdrom $(IMAGE_NAME).iso \ $(QEMUFLAGS) @@ -96,9 +96,9 @@ run-hdd-riscv64: $(IMAGE_NAME).hdd -M virt \ -cpu rv64 \ -device ramfb \ - -device qemu-xhci \ - -device usb-kbd \ - -device usb-mouse \ + -device qemu-xhci,id=xhci \ + -device usb-kbd,bus=xhci.0 \ + -device usb-mouse,bus=xhci.0 \ -bios /usr/share/ovmf/OVMF.fd \ -hda $(IMAGE_NAME).hdd \ $(QEMUFLAGS) @@ -109,9 +109,9 @@ run-loongarch64: $(IMAGE_NAME).iso -M virt \ -cpu la464 \ -device ramfb \ - -device qemu-xhci \ - -device usb-kbd \ - -device usb-mouse \ + -device qemu-xhci,id=xhci \ + -device usb-kbd,bus=xhci.0 \ + -device usb-mouse,bus=xhci.0 \ -bios /usr/share/ovmf/OVMF.fd \ -cdrom $(IMAGE_NAME).iso \ $(QEMUFLAGS) @@ -122,9 +122,9 @@ run-hdd-loongarch64: $(IMAGE_NAME).hdd -M virt \ -cpu la464 \ -device ramfb \ - -device qemu-xhci \ - -device usb-kbd \ - -device usb-mouse \ + -device qemu-xhci,id=xhci \ + -device usb-kbd,bus=xhci.0 \ + -device usb-mouse,bus=xhci.0 \ -bios /usr/share/ovmf/OVMF.fd \ -hda $(IMAGE_NAME).hdd \ $(QEMUFLAGS) diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index 9beebc1..c406057 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -549,6 +549,8 @@ namespace Zenith { return Sys_Resolve((const char*)frame->arg1); case SYS_GETRANDOM: return Sys_GetRandom((uint8_t*)frame->arg1, frame->arg2); + case SYS_KLOG: + return Kt::ReadKernelLog((char*)frame->arg1, frame->arg2); default: return -1; } @@ -575,7 +577,7 @@ namespace Zenith { Hal::WriteMSR(Hal::IA32_FMASK, 0x200); Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR=" - << kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 46 syscalls)"; + << kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 47 syscalls)"; } } diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index dd9c207..14aadbe 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -57,6 +57,7 @@ namespace Zenith { static constexpr uint64_t SYS_TERMSCALE = 43; static constexpr uint64_t SYS_RESOLVE = 44; static constexpr uint64_t SYS_GETRANDOM = 45; + static constexpr uint64_t SYS_KLOG = 46; static constexpr int SOCK_TCP = 1; static constexpr int SOCK_UDP = 2; diff --git a/kernel/src/Drivers/PS2/Keyboard.cpp b/kernel/src/Drivers/PS2/Keyboard.cpp index 4a4b061..f8cc4a3 100644 --- a/kernel/src/Drivers/PS2/Keyboard.cpp +++ b/kernel/src/Drivers/PS2/Keyboard.cpp @@ -291,4 +291,10 @@ namespace Drivers::PS2::Keyboard { return g_Modifiers; } + void InjectKeyEvent(const KeyEvent& event) { + g_BufferLock.Acquire(); + BufferPush(event); + g_BufferLock.Release(); + } + }; \ No newline at end of file diff --git a/kernel/src/Drivers/PS2/Keyboard.hpp b/kernel/src/Drivers/PS2/Keyboard.hpp index b9c34e9..2695241 100644 --- a/kernel/src/Drivers/PS2/Keyboard.hpp +++ b/kernel/src/Drivers/PS2/Keyboard.hpp @@ -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); + }; \ No newline at end of file diff --git a/kernel/src/Drivers/PS2/Mouse.cpp b/kernel/src/Drivers/PS2/Mouse.cpp index 7f3acbb..2916e5a 100644 --- a/kernel/src/Drivers/PS2/Mouse.cpp +++ b/kernel/src/Drivers/PS2/Mouse.cpp @@ -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(); + } + }; \ No newline at end of file diff --git a/kernel/src/Drivers/PS2/Mouse.hpp b/kernel/src/Drivers/PS2/Mouse.hpp index 964d339..ea5514b 100644 --- a/kernel/src/Drivers/PS2/Mouse.hpp +++ b/kernel/src/Drivers/PS2/Mouse.hpp @@ -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); + }; \ No newline at end of file diff --git a/kernel/src/Drivers/USB/HidKeyboard.cpp b/kernel/src/Drivers/USB/HidKeyboard.cpp new file mode 100644 index 0000000..06f626a --- /dev/null +++ b/kernel/src/Drivers/USB/HidKeyboard.cpp @@ -0,0 +1,350 @@ +/* + * HidKeyboard.cpp + * USB HID Boot Protocol Keyboard driver + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "HidKeyboard.hpp" +#include +#include +#include + +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; + } + +} diff --git a/kernel/src/Drivers/USB/HidKeyboard.hpp b/kernel/src/Drivers/USB/HidKeyboard.hpp new file mode 100644 index 0000000..0f3fe10 --- /dev/null +++ b/kernel/src/Drivers/USB/HidKeyboard.hpp @@ -0,0 +1,18 @@ +/* + * HidKeyboard.hpp + * USB HID Boot Protocol Keyboard driver + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +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); + +}; diff --git a/kernel/src/Drivers/USB/HidMouse.cpp b/kernel/src/Drivers/USB/HidMouse.cpp new file mode 100644 index 0000000..6dfcd9b --- /dev/null +++ b/kernel/src/Drivers/USB/HidMouse.cpp @@ -0,0 +1,49 @@ +/* + * HidMouse.cpp + * USB HID Boot Protocol Mouse driver + * Copyright (c) 2025 Daniel Hammer +*/ + +#include "HidMouse.hpp" +#include +#include +#include + +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); + } + +} diff --git a/kernel/src/Drivers/USB/HidMouse.hpp b/kernel/src/Drivers/USB/HidMouse.hpp new file mode 100644 index 0000000..054e40a --- /dev/null +++ b/kernel/src/Drivers/USB/HidMouse.hpp @@ -0,0 +1,18 @@ +/* + * HidMouse.hpp + * USB HID Boot Protocol Mouse driver + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +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); + +}; diff --git a/kernel/src/Drivers/USB/UsbDevice.cpp b/kernel/src/Drivers/USB/UsbDevice.cpp new file mode 100644 index 0000000..b05e9a6 --- /dev/null +++ b/kernel/src/Drivers/USB/UsbDevice.cpp @@ -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 +#include +#include +#include +#include + +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; + } + +} diff --git a/kernel/src/Drivers/USB/UsbDevice.hpp b/kernel/src/Drivers/USB/UsbDevice.hpp new file mode 100644 index 0000000..4fffb80 --- /dev/null +++ b/kernel/src/Drivers/USB/UsbDevice.hpp @@ -0,0 +1,110 @@ +/* + * UsbDevice.hpp + * USB device enumeration and standard descriptors + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +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); + +}; diff --git a/kernel/src/Drivers/USB/Xhci.cpp b/kernel/src/Drivers/USB/Xhci.cpp new file mode 100644 index 0000000..03bcb7e --- /dev/null +++ b/kernel/src/Drivers/USB/Xhci.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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"; + } + +}; diff --git a/kernel/src/Drivers/USB/Xhci.hpp b/kernel/src/Drivers/USB/Xhci.hpp new file mode 100644 index 0000000..f977d0a --- /dev/null +++ b/kernel/src/Drivers/USB/Xhci.hpp @@ -0,0 +1,305 @@ +/* + * Xhci.hpp + * xHCI (USB 3.x) Host Controller driver + * Copyright (c) 2025 Daniel Hammer +*/ + +#pragma once +#include + +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(); + +}; diff --git a/kernel/src/Fs/Ramdisk.cpp b/kernel/src/Fs/Ramdisk.cpp index abc4172..9ded4f2 100644 --- a/kernel/src/Fs/Ramdisk.cpp +++ b/kernel/src/Fs/Ramdisk.cpp @@ -120,10 +120,6 @@ namespace Fs::Ramdisk { continue; } - Kt::KernelLogStream(Kt::INFO, "Ramdisk") << " " << entry.name - << " (" << entry.size << " bytes" - << (entry.isDirectory ? ", dir" : "") << ")"; - fileCount++; // Advance past header + data (rounded up to 512-byte blocks) diff --git a/kernel/src/Main.cpp b/kernel/src/Main.cpp index 2ae8fee..4d157fd 100644 --- a/kernel/src/Main.cpp +++ b/kernel/src/Main.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -198,6 +199,8 @@ extern "C" void kmain() { Drivers::PS2::Keyboard::Initialize(); Drivers::PS2::Mouse::Initialize(); + Drivers::USB::Xhci::Initialize(); + Drivers::Net::E1000::Initialize(); if (!Drivers::Net::E1000::IsInitialized()) { KernelLogStream(INFO, "Init") << "E1000 not found, trying E1000E..."; @@ -248,6 +251,8 @@ extern "C" void kmain() { Zenith::InitializeSyscalls(); Sched::Initialize(); + + Kt::SuppressKernelLog(); Sched::Spawn("0:/os/init.elf"); // Enable preemptive scheduling via the APIC timer diff --git a/kernel/src/Terminal/Terminal.cpp b/kernel/src/Terminal/Terminal.cpp index f0fddd2..de8980a 100644 --- a/kernel/src/Terminal/Terminal.cpp +++ b/kernel/src/Terminal/Terminal.cpp @@ -18,6 +18,39 @@ namespace Kt { flanterm_context *ctx; std::size_t g_terminal_width = 0; + // Kernel log depth counter and suppression flag + uint32_t g_kernelLogDepth = 0; + bool g_suppressKernelLog = false; + + // 64KB ring buffer for kernel log messages + static constexpr uint64_t KLOG_BUF_SIZE = 65536; + static char g_klogBuf[KLOG_BUF_SIZE]; + static uint64_t g_klogHead = 0; // next write position + static uint64_t g_klogCount = 0; // total chars stored (capped at KLOG_BUF_SIZE) + + // ANSI escape sequence filter state + static bool g_ansiEscape = false; + + static void RingBufferAppend(char c) { + // Strip ANSI escape sequences + if (c == '\033') { + g_ansiEscape = true; + return; + } + if (g_ansiEscape) { + if (c == 'm') { + g_ansiEscape = false; + } + return; + } + + g_klogBuf[g_klogHead] = c; + g_klogHead = (g_klogHead + 1) % KLOG_BUF_SIZE; + if (g_klogCount < KLOG_BUF_SIZE) { + g_klogCount++; + } + } + // Maximum grid cells allocated at init (scale 1,1). Used to validate // that a requested scale does not exceed the original buffer capacity. static std::size_t g_max_grid_cells = 0; @@ -182,6 +215,19 @@ namespace Kt { } void Putchar(char c) { + if (g_kernelLogDepth > 0) { + if (c == '\n') { + RingBufferAppend('\r'); + RingBufferAppend('\n'); + } else { + RingBufferAppend(c); + } + + if (g_suppressKernelLog) { + return; + } + } + if (c == '\n') { flanterm_write(ctx, "\r\n", 2); return; @@ -195,4 +241,34 @@ namespace Kt { } } + void SuppressKernelLog() { + g_suppressKernelLog = true; + } + + int64_t ReadKernelLog(char* buf, uint64_t size) { + if (buf == nullptr || size == 0) return 0; + + uint64_t toRead = g_klogCount; + if (toRead > size) toRead = size; + + // Start position: oldest character in the ring buffer + uint64_t start; + if (g_klogCount < KLOG_BUF_SIZE) { + start = 0; + } else { + start = g_klogHead; // head points to the oldest entry when full + } + + // Copy from oldest to newest, skipping entries if buffer too small + uint64_t skipCount = g_klogCount - toRead; + uint64_t readPos = (start + skipCount) % KLOG_BUF_SIZE; + + for (uint64_t i = 0; i < toRead; i++) { + buf[i] = g_klogBuf[readPos]; + readPos = (readPos + 1) % KLOG_BUF_SIZE; + } + + return (int64_t)toRead; + } + }; diff --git a/kernel/src/Terminal/Terminal.hpp b/kernel/src/Terminal/Terminal.hpp index a0fadb3..4c9447c 100644 --- a/kernel/src/Terminal/Terminal.hpp +++ b/kernel/src/Terminal/Terminal.hpp @@ -109,6 +109,12 @@ namespace Kt OK }; + extern uint32_t g_kernelLogDepth; + extern bool g_suppressKernelLog; + + void SuppressKernelLog(); + int64_t ReadKernelLog(char* buf, uint64_t size); + class KernelLogStream { KernelOutStream localStream{}; @@ -133,6 +139,7 @@ public: } KernelLogStream(KernelLogLevel level, const char* desiredComponentName) { /* level for potential future rich event logging */ + g_kernelLogDepth++; componentName = desiredComponentName; localStream << componentName << ": " << "[" << LogLevelToStringWithColor(level) << "] "; @@ -140,6 +147,7 @@ public: ~KernelLogStream() { localStream << newline; + g_kernelLogDepth--; } template diff --git a/kernel/src/Timekeeping/ApicTimer.cpp b/kernel/src/Timekeeping/ApicTimer.cpp index f08da4a..3ccb57e 100644 --- a/kernel/src/Timekeeping/ApicTimer.cpp +++ b/kernel/src/Timekeeping/ApicTimer.cpp @@ -12,6 +12,7 @@ #include #include #include +#include using namespace Kt; @@ -45,6 +46,7 @@ namespace Timekeeping { // In polling mode, drain the NIC's RX ring from timer context // (equivalent to a real NIC IRQ handler, runs with interrupts disabled) Drivers::Net::E1000E::Poll(); + Drivers::USB::Xhci::ProcessDeferredWork(); if (g_schedEnabled) { Sched::Tick();