Skip to content

USB Implementation Plan

Living document — tracks design decisions, architecture, progress, and open questions for Asuro's USB subsystem.

Table of Contents


1. Overview

Implement a full USB subsystem for Asuro, supporting all four host controller types (UHCI → OHCI → EHCI → xHCI), all four transfer types (Control, Interrupt, Bulk, Isochronous), full hub support, and a class-driver model integrated with drivermanagement. Initial class drivers: USB Keyboard and USB Mouse.

Target environment: VirtualBox (PIIX3 = UHCI, ICH9 = OHCI + EHCI, optional xHCI), bare-metal i386, ring 0, Standard Pascal (FPC cross-compiler), no runtime library.


2. Design Decisions

Decision Choice Rationale
HC implementation order UHCI → OHCI → EHCI → xHCI UHCI first (VirtualBox PIIX3 default), then OHCI, then EHCI for USB 2.0, xHCI last
Transfer types All four (Control, Interrupt, Bulk, Isochronous) Full spec compliance over time
Hub support Full from the start Needed for real hardware; root hub + external hubs
USB device ↔ class driver matching drivermanagement with biUSB bus type Reuse existing driver lifecycle, no parallel systems
Completion model Interrupt-driven (ISR on PCI IRQ, PollBusy guard) Inline polling fallback in usb_control_msg; ISR fires completion hooks for HID drivers
DMA memory Existing pmemorymanager / vmemorymanager (force_alloc_block / map_page) Already proven in OHCI stub; identity-mapped pages for DMA
File layout src/driver/bus/usb/ subdirectory Keep HC logic together; class drivers in src/driver/hid/ and src/driver/storage/
Data structures lists.pas linked lists + manual kalloc/kfree records Avoid static arrays, use existing infrastructure; class drivers (keyboard, mouse, storage) all use dynamic lists for unlimited device tracking

3. Architecture

┌──────────────────────────────────────────────────────────────────┐
│                      drivermanagement                            │
│  register_driver(biUSB, class/sub/proto) ←→ register_device()   │
├──────────────────────────────────────────────────────────────────┤
│                        USB Class Drivers                         │
│   ┌──────────────┐  ┌──────────────┐  ┌────────────────────┐   │
│   │ USB Keyboard  │  │  USB Mouse   │  │  USB Mass Storage  │   │
│   │ (src/hid/)    │  │ (src/hid/)   │  │  (src/storage/)    │   │
│   └──────┬───────┘  └──────┬───────┘  └────────┬───────────┘   │
│          │                  │                    │                │
├──────────┴──────────────────┴────────────────────┴───────────────┤
│                         usbcore.pas                              │
│   Device enumeration, configuration, pipe management,            │
│   control transfers, descriptor parsing, address assignment      │
├──────────────────────────────────────────────────────────────────┤
│                         usbhub.pas                               │
│   Hub driver, port status/change, reset sequencing,              │
│   topology tracking, split transaction TT info                   │
├──────────────────────────────────────────────────────────────────┤
│              Host Controller Interface (HCI) Abstraction         │
│                        usbtypes.pas                              │
│   TUSBHCDriver record with function pointers:                    │
│     init, reset, start, stop, submit_transfer, poll              │
├─────────┬──────────┬──────────┬──────────┬───────────────────────┤
│  UHCI   │   OHCI   │   EHCI   │   xHCI   │  Host Controller     │
│  .pas   │   .pas   │   .pas   │   .pas   │  Drivers             │
├─────────┴──────────┴──────────┴──────────┴───────────────────────┤
│                           PCI Bus                                │
│                         (PCI.pas)                                 │
└──────────────────────────────────────────────────────────────────┘

* = future, not in initial implementation

4. File Layout

src/driver/bus/usb/
├── usbtypes.pas       # Shared types, constants, HC abstraction record
├── usbcore.pas        # Enumeration, configuration, control transfers, pipe API
├── usbhub.pas         # Hub driver (root + external)
├── UHCI.pas           # UHCI host controller driver (moved from bus/)
├── OHCI.pas           # OHCI host controller driver (moved from bus/)
├── EHCI.pas           # EHCI host controller driver (moved from bus/)
├── XHCI.pas           # xHCI host controller driver (moved from bus/)
└── USB.pas            # Init entry point, PCI registration (moved from bus/)

src/driver/hid/
├── usb_keyboard.pas   # USB HID keyboard class driver
└── usb_mouse.pas      # USB HID mouse class driver

src/driver/storage/
└── usb_storage.pas    # USB Mass Storage class driver (BOT + SCSI)

Migration: Existing UHCI.pas, OHCI.pas, EHCI.pas, XHCI.pas, USB.pas move from src/driver/bus/ into src/driver/bus/usb/. Build scripts will need path updates.


5. Shared Types (usbtypes.pas)

5.1 USB Descriptor Types

{ Standard descriptor type constants }
const
    USB_DESC_DEVICE        = $01;
    USB_DESC_CONFIGURATION = $02;
    USB_DESC_STRING        = $03;
    USB_DESC_INTERFACE     = $04;
    USB_DESC_ENDPOINT      = $05;
    USB_DESC_HUB           = $29;

{ USB Request Types }
    USB_REQ_GET_STATUS     = $00;
    USB_REQ_CLEAR_FEATURE  = $01;
    USB_REQ_SET_FEATURE    = $03;
    USB_REQ_SET_ADDRESS    = $05;
    USB_REQ_GET_DESCRIPTOR = $06;
    USB_REQ_SET_CONFIG     = $09;

{ Endpoint direction }
    USB_DIR_OUT = $00;
    USB_DIR_IN  = $80;

{ Transfer types }
    USB_TRANSFER_CONTROL     = 0;
    USB_TRANSFER_ISOCHRONOUS = 1;
    USB_TRANSFER_BULK        = 2;
    USB_TRANSFER_INTERRUPT   = 3;

{ USB speeds }
    USB_SPEED_LOW   = 0;  { 1.5 Mbps  }
    USB_SPEED_FULL  = 1;  { 12 Mbps   }
    USB_SPEED_HIGH  = 2;  { 480 Mbps  }
    USB_SPEED_SUPER = 3;  { 5 Gbps    }

{ Max devices per controller }
    USB_MAX_DEVICES = 127;
    USB_MAX_ENDPOINTS = 32;

5.2 Standard Descriptors (packed records)

type
    PUSBDeviceDescriptor = ^TUSBDeviceDescriptor;
    TUSBDeviceDescriptor = packed record
        bLength            : uint8;
        bDescriptorType    : uint8;
        bcdUSB             : uint16;
        bDeviceClass       : uint8;
        bDeviceSubClass    : uint8;
        bDeviceProtocol    : uint8;
        bMaxPacketSize0    : uint8;
        idVendor           : uint16;
        idProduct          : uint16;
        bcdDevice          : uint16;
        iManufacturer      : uint8;
        iProduct           : uint8;
        iSerialNumber      : uint8;
        bNumConfigurations : uint8;
    end;

    PUSBConfigDescriptor = ^TUSBConfigDescriptor;
    TUSBConfigDescriptor = packed record
        bLength             : uint8;
        bDescriptorType     : uint8;
        wTotalLength        : uint16;
        bNumInterfaces      : uint8;
        bConfigurationValue : uint8;
        iConfiguration      : uint8;
        bmAttributes        : uint8;
        bMaxPower           : uint8;
    end;

    PUSBInterfaceDescriptor = ^TUSBInterfaceDescriptor;
    TUSBInterfaceDescriptor = packed record
        bLength            : uint8;
        bDescriptorType    : uint8;
        bInterfaceNumber   : uint8;
        bAlternateSetting  : uint8;
        bNumEndpoints      : uint8;
        bInterfaceClass    : uint8;
        bInterfaceSubClass : uint8;
        bInterfaceProtocol : uint8;
        iInterface         : uint8;
    end;

    PUSBEndpointDescriptor = ^TUSBEndpointDescriptor;
    TUSBEndpointDescriptor = packed record
        bLength          : uint8;
        bDescriptorType  : uint8;
        bEndpointAddress : uint8;
        bmAttributes     : uint8;
        wMaxPacketSize   : uint16;
        bInterval        : uint8;
    end;

    PUSBHubDescriptor = ^TUSBHubDescriptor;
    TUSBHubDescriptor = packed record
        bDescLength         : uint8;
        bDescriptorType     : uint8;
        bNbrPorts           : uint8;
        wHubCharacteristics : uint16;
        bPwrOn2PwrGood      : uint8;
        bHubContrCurrent    : uint8;
        { Variable-length DeviceRemovable + PortPwrCtrlMask follow }
    end;

5.3 Setup Packet

    PUSBSetupPacket = ^TUSBSetupPacket;
    TUSBSetupPacket = packed record
        bmRequestType : uint8;
        bRequest      : uint8;
        wValue        : uint16;
        wIndex        : uint16;
        wLength       : uint16;
    end;

5.4 USB Device / Endpoint / Pipe State

    { Transfer completion status }
    TUSBTransferStatus = (
        tsSuccess,
        tsStall,
        tsNAK,
        tsBabble,
        tsDataBufferError,
        tsCRCError,
        tsBitStuffError,
        tsTimeout,
        tsNotStarted,
        tsInProgress
    );

    { Transfer direction }
    TUSBDirection = (dirOut, dirIn, dirSetup);

    { Pipe type matches transfer type }
    TUSBPipeType = (ptControl, ptIsochronous, ptBulk, ptInterrupt);

    PUSBEndpoint = ^TUSBEndpoint;
    TUSBEndpoint = record
        Address      : uint8;      { Endpoint number (0-15) }
        Direction    : TUSBDirection;
        PipeType     : TUSBPipeType;
        MaxPacket    : uint16;
        Interval     : uint8;      { Polling interval (interrupt/iso) }
        Toggle       : uint8;      { Data toggle state: 0 or 1 }
    end;

    PUSBDevice = ^TUSBDevice;

    { Forward declaration for HC driver }
    PUSBHCDriver = ^TUSBHCDriver;

    PUSBTransfer = ^TUSBTransfer;
    TUSBTransfer = record
        Device      : PUSBDevice;
        Endpoint    : PUSBEndpoint;
        Direction   : TUSBDirection;
        PipeType    : TUSBPipeType;
        Buffer      : Pointer;
        BufferLen   : uint32;
        ActualLen   : uint32;     { Bytes actually transferred }
        Status      : TUSBTransferStatus;
        Setup       : TUSBSetupPacket; { For control transfers }
        HCPriv      : Pointer;    { HC-specific data (TD chain, etc.) }
    end;

    TUSBDevice = record
        Address     : uint8;       { Assigned USB address (1-127) }
        Speed       : uint8;       { USB_SPEED_* }
        MaxPacket0  : uint8;       { EP0 max packet size }
        HC          : PUSBHCDriver;
        HCPort      : uint8;       { Root hub port or hub port }
        ParentHub   : PUSBDevice;  { nil for root-hub-attached devices }
        ParentPort  : uint8;       { Port on parent hub }
        DevDesc     : TUSBDeviceDescriptor;
        Endpoints   : array[0..USB_MAX_ENDPOINTS-1] of TUSBEndpoint;
        NumEndpoints: uint8;
        Configured  : boolean;
    end;

5.5 Host Controller Abstraction

    { HC operations - each HC driver fills in these function pointers }
    TUSBHCInit     = function(dev : PPCI_Device) : boolean;
    TUSBHCReset    = function(hc : PUSBHCDriver) : boolean;
    TUSBHCStart    = function(hc : PUSBHCDriver) : boolean;
    TUSBHCStop     = procedure(hc : PUSBHCDriver);
    TUSBHCSubmit   = function(hc : PUSBHCDriver; transfer : PUSBTransfer) : boolean;
    TUSBHCPoll     = procedure(hc : PUSBHCDriver);
    TUSBHCPortReset = function(hc : PUSBHCDriver; port : uint8) : boolean;
    TUSBHCPortStatus = function(hc : PUSBHCDriver; port : uint8) : uint32;

    TUSBHCDriver = record
        Name         : PChar;
        HCType       : uint8;       { 0=UHCI, 1=OHCI, 2=EHCI, 3=xHCI }
        NumPorts     : uint8;
        PCIDev       : TPCI_Device; { Owning PCI device }
        BaseAddr     : uint32;      { I/O base (UHCI) or MMIO base (OHCI/EHCI/xHCI) }
        PrivData     : Pointer;     { HC-specific state (frame list, HCCA, etc.) }
        Devices      : PLinkedListBase; { List of TUSBDevice attached to this HC }
        NextAddress  : uint8;       { Next available USB address }
        { Operations }
        fnReset      : TUSBHCReset;
        fnStart      : TUSBHCStart;
        fnStop       : TUSBHCStop;
        fnSubmit     : TUSBHCSubmit;
        fnPoll       : TUSBHCPoll;
        fnPortReset  : TUSBHCPortReset;
        fnPortStatus : TUSBHCPortStatus;
    end;

6. USB Core (usbcore.pas)

The core module orchestrates enumeration and provides the public transfer API used by class drivers.

6.1 Responsibilities

  1. Maintain a global list of HC drivers (HCList : PLinkedListBase)
  2. Register/unregister HC drivers — called by each HC's load function
  3. Device enumeration sequence (called when hub reports a new device):
  4. Port reset (via HC abstraction)
  5. Create TUSBDevice with address 0, EP0 at 8 bytes
  6. GET_DESCRIPTOR(Device, 8 bytes) → read bMaxPacketSize0
  7. SET_ADDRESS(N) → assign unique address
  8. GET_DESCRIPTOR(Device, 18 bytes) → full device descriptor
  9. GET_DESCRIPTOR(Configuration) → all configs, interfaces, endpoints
  10. Parse interface descriptors → populate Endpoints[]
  11. SET_CONFIGURATION(1)
  12. Call drivermanagement.register_device() with biUSB bus identifier
  13. Control transfer helpers:
  14. usb_control_msg(dev, requestType, request, value, index, buf, len) : TUSBTransferStatus
  15. usb_get_descriptor(dev, descType, descIdx, buf, len) : TUSBTransferStatus
  16. usb_set_address(dev, addr) : TUSBTransferStatus
  17. usb_set_configuration(dev, config) : TUSBTransferStatus
  18. Interrupt/Bulk transfer helpers:
  19. usb_interrupt_transfer(dev, endpoint, buf, len) : TUSBTransferStatus
  20. usb_bulk_transfer(dev, endpoint, buf, len) : TUSBTransferStatus
  21. Polling loop — called from timer/scheduler tick:
  22. Iterate all HC drivers → call fnPoll
  23. HC poll checks for completed transfers and port status changes
  24. Port status changes trigger hub logic → enumeration

6.2 Device Identifier Encoding for drivermanagement

When a USB device is enumerated, we register it via:

DevID.Bus := biUSB;
DevID.id0 := (idVendor SHL 16) OR idProduct;  { Vendor:Product }
DevID.id1 := bDeviceClass;
DevID.id2 := bInterfaceClass;      { From first/matching interface }
DevID.id3 := bInterfaceSubClass;
DevID.id4 := bInterfaceProtocol;
DevID.ex  := nil;  { Could chain additional interface descriptors }

Class drivers register with wildcards where appropriate:

{ HID Keyboard driver registers: }
DevID.Bus := biUSB;
DevID.id0 := idANY;               { Any vendor/product }
DevID.id1 := idANY;               { Any device class }
DevID.id2 := $03;                  { HID class }
DevID.id3 := $01;                  { Boot interface subclass }
DevID.id4 := $01;                  { Keyboard protocol }

The TDriverLoadCallback receives a pointer to a PUSBDevice, giving the class driver full access to endpoints and the HC transfer API.


7. Host Controller Drivers

7.1 UHCI (Universal Host Controller Interface)

Priority: First
Spec: Intel UHCI Design Guide, Rev 1.1
I/O model: Port I/O (not MMIO)
PCI prog_if: $00

Registers (I/O port offsets from BAR4)

Offset Name Size Description
$00 USBCMD 16-bit Command register
$02 USBSTS 16-bit Status register
$04 USBINTR 16-bit Interrupt enable
$06 FRNUM 16-bit Frame number
$08 FRBASEADD 32-bit Frame list base address
$0C SOFMOD 8-bit Start of frame modify
$10 PORTSC1 16-bit Port 1 status/control
$12 PORTSC2 16-bit Port 2 status/control

Key Data Structures (all physically contiguous, 16-byte aligned)

  • Frame List: 1024 × 32-bit pointers (4KB page-aligned)
  • Queue Head (QH): 8 bytes — QH_Link_Pointer, QH_Element_Pointer
  • Transfer Descriptor (TD): 16 bytes — TD_Link_Pointer, TD_Control_Status, TD_Token, TD_Buffer_Pointer

Implementation Steps

  1. Read BAR4 from PCI config → I/O base address
  2. Stop controller (clear USBCMD.RS)
  3. Global reset (USBCMD.GRESET), wait 10ms, clear
  4. Allocate frame list (4KB, page-aligned, identity-mapped)
  5. Initialize all 1024 frame list entries to terminate (T-bit set)
  6. Write frame list physical address to FRBASEADD
  7. Set FRNUM = 0
  8. Create skeleton QH schedule:
  9. Control QH → Bulk QH → Interrupt QHs (at frame list intervals)
  10. Start controller (USBCMD.RS = 1)
  11. For each port: check PORTSC, detect device, trigger enumeration
  12. Poll: check USBSTS for completion, walk TD chains for completed transfers

UHCI Private Data

PUHCIData = ^TUHCIData;
TUHCIData = record
    IOBase       : uint16;
    FrameList    : Pointer;    { 4KB-aligned physical address }
    ControlQH    : Pointer;    { Control transfer queue head }
    BulkQH       : Pointer;    { Bulk transfer queue head }
    IntQH        : array[0..7] of Pointer; { Interrupt QHs at different intervals }
end;

7.2 OHCI (Open Host Controller Interface)

Priority: Second
Spec: OHCI Rev 1.0a
I/O model: MMIO (BAR0)
PCI prog_if: $10

Key Registers (already partially in existing TOHCI_MMR)

Already have the MMIO register record. Need to extend with: - Operational registers (HcControl, HcCommandStatus, etc.) - HcHCCA → points to 256-byte Host Controller Communications Area - Port status registers (HcRhPortStatus[1..N])

Key Data Structures

  • HCCA: 256 bytes, 256-byte aligned — contains interrupt table (32 × ED pointers) and frame counter
  • Endpoint Descriptor (ED): 16 bytes — links to TD list
  • Transfer Descriptor (TD): 16 bytes (General) or 32 bytes (Isochronous)

Implementation Steps

  1. Map BAR0 MMIO region (existing code does this)
  2. Reset: write OHCI_RESET to HcCommandStatus, wait for completion
  3. Allocate HCCA (256-byte aligned)
  4. Set HcHCCA register
  5. Build ED skeleton schedule (Control, Bulk, Interrupt, Isochronous lists)
  6. Set HcControl to OPERATIONAL state
  7. Enable relevant interrupts in HcIntEnable
  8. Set HcPeriodicStart
  9. Power on root hub ports via HcRhPortStatus
  10. Detect devices, trigger enumeration
  11. Poll: read HcDoneHead for completed TDs

7.3 EHCI (Enhanced Host Controller Interface)

Priority: Third
Spec: EHCI Rev 1.0
I/O model: MMIO (BAR0)
PCI prog_if: $20

Key Concepts

  • Capability Registers (read-only, offset 0): CAPLENGTH, HCSPARAMS, HCCPARAMS
  • Operational Registers (offset CAPLENGTH): USBCMD, USBSTS, USBINTR, FRINDEX, etc.
  • Periodic Frame List: 1024 × 32-bit frame list entries (4KB aligned)
  • Async Schedule: Circular linked list of Queue Heads (QHs)
  • Queue Head (QH): 48 bytes — contains overlay area for current TD
  • Queue Transfer Descriptor (qTD): 32 bytes, 32-byte aligned

Companion Controller Handling

EHCI routes full/low-speed devices to companion controllers (UHCI/OHCI) via port routing logic. We must: - Read HCSPARAMS for N_PORTS and N_CC (companion controllers) - Handle port owner change (CONFIGFLAG, PORTSC[n].PortOwner)

Implementation Steps

  1. Map BAR0, read capability registers
  2. Stop controller (USBCMD.RS = 0), wait for halt
  3. Reset (USBCMD.HCRESET), wait for clear
  4. Allocate periodic frame list + async QH
  5. Set PERIODICLISTBASE, ASYNCLISTADDR
  6. Set CONFIGFLAG = 1 (claim all ports from companions)
  7. Enable async + periodic schedules
  8. Start controller
  9. Port detection, reset (50ms PORTRESET), enumeration
  10. Poll: check USBSTS for async/periodic completion

7.4 xHCI (eXtensible Host Controller Interface)

Priority: Fourth (last)
Spec: xHCI Rev 1.2
I/O model: MMIO (BAR0, 64-bit capable)
PCI prog_if: $30

Key Concepts (significantly different from UHCI/OHCI/EHCI)

  • No frame list — uses ring-based command/transfer/event model
  • Device Context: per-device state maintained by hardware
  • Command Ring: kernel submits commands (enable slot, address device, etc.)
  • Transfer Ring: per-endpoint ring of TRBs (Transfer Request Blocks)
  • Event Ring: hardware posts completions
  • Doorbell Registers: notify controller of new TRBs
  • Scratchpad Buffers: required by some controllers

Implementation Steps

  1. Map BAR0 (may need 64-bit BAR handling)
  2. Read capability registers (CAPLENGTH, HCSPARAMS1/2/3, HCCPARAMS1)
  3. Stop controller, wait for halt
  4. Reset (USBCMD.HCRST)
  5. Program MaxSlots, MaxIntrs
  6. Allocate DCBAA (Device Context Base Address Array)
  7. Allocate Command Ring, Event Ring(s), Scratchpad
  8. Set DCBAAP, CRCR registers
  9. Define Interrupter 0 event ring
  10. Start controller
  11. For each port: check PORTSC, issue Port Reset, Enable Slot, Address Device
  12. Poll: read Event Ring for completion TRBs, advance dequeue pointer

8. Hub Driver (usbhub.pas)

8.1 Hub Detection

Hubs are detected during enumeration when bDeviceClass = $09 (Hub) or bInterfaceClass = $09. The hub driver is registered as a USB class driver via drivermanagement:

DevID.Bus := biUSB;
DevID.id0 := idANY;
DevID.id1 := $09;      { Hub device class }
DevID.id2 := $09;      { Hub interface class }
DevID.id3 := idANY;    { Subclass }
DevID.id4 := idANY;    { Protocol }

8.2 Hub Responsibilities

  1. GET_HUB_DESCRIPTOR → determine number of downstream ports
  2. SET_PORT_POWER for each port (if not ganged-power)
  3. Poll hub status endpoint (interrupt IN endpoint) for port status change bitmap
  4. On port change:
  5. GET_PORT_STATUS → check PORT_CONNECTION, PORT_ENABLE, PORT_RESET
  6. If new device: SET_PORT_FEATURE(PORT_RESET), wait, CLEAR_PORT_FEATURE(C_PORT_RESET)
  7. Call usbcore enumeration for the new device (with ParentHub set)
  8. If device removed: tear down device, notify class driver, free resources
  9. Topology tracking — maintain parent/child relationships for split transactions (EHCI TT)

8.3 Root Hub Emulation

Each HC driver emulates root hub ports using its native port status registers. The HC abstraction (fnPortReset, fnPortStatus) provides this. usbcore treats root hub ports uniformly by calling these functions during initial scan.


9. USB Class Drivers

9.1 USB Keyboard (usb_keyboard.pas)

USB HID Boot Protocol Keyboard

  • Registers with drivermanagement for biUSB, class=$03, subclass=$01, protocol=$01
  • load callback receives PUSBDevice
  • SET_PROTOCOL(0) → Boot protocol (simpler 8-byte reports)
  • SET_IDLE(0) → Only report on change
  • Find interrupt IN endpoint from device's endpoint list
  • Poll endpoint periodically (using interrupt transfer API)
  • Parse 8-byte boot report:
  • Byte 0: modifier keys (Ctrl, Shift, Alt, GUI)
  • Byte 1: reserved
  • Bytes 2-7: keycodes (up to 6 simultaneous)
  • Translate keycodes to existing TKeyInfo format
  • Feed into existing keyboard.hook mechanism

9.2 USB Mouse (usb_mouse.pas)

USB HID Boot Protocol Mouse

  • Registers for biUSB, class=$03, subclass=$01, protocol=$02
  • SET_PROTOCOL(0) → Boot protocol
  • Find interrupt IN endpoint
  • Poll for 3-byte (or 4-byte with scroll) boot reports:
  • Byte 0: buttons (bit0=left, bit1=right, bit2=middle)
  • Byte 1: X displacement (signed)
  • Byte 2: Y displacement (signed)
  • Byte 3: wheel (optional)
  • Feed into existing mousestate / mouse driver system

10. Integration with drivermanagement

10.1 PCI → HC Driver Loading (existing pattern, refined)

The existing USB.init procedure already registers four PCI device identifiers with drivermanagement. When PCI scan finds a USB controller, drivermanagement.register_device triggers the matching HC driver's load callback. This stays the same.

10.2 HC Driver → USB Core

Each HC driver's load function: 1. Initializes hardware 2. Fills in a TUSBHCDriver record (kalloc'd) 3. Calls usbcore.register_hc(@hcdriver) to add to the global HC list 4. Calls usbcore.scan_ports(@hcdriver) to enumerate root hub ports

10.3 USB Core → Class Driver Loading

When usbcore completes device enumeration: 1. For each interface on the device, create a TDeviceIdentifier with biUSB and class/subclass/protocol 2. Call drivermanagement.register_device('USB Device', @DevID, @usbdev) 3. drivermanagement matches against registered class drivers and calls their load callback 4. Class driver receives PUSBDevice via the ptr parameter, begins operation

10.4 Lifecycle Flow

Boot
  → kernel.init
    → drivermanagement.init
    → PCI.init  (registers PCI driver, force_load)
      → PCI.load  (scans all buses)
        → For each PCI device: drivermanagement.register_device(biPCI, ...)
          → Matches USB-UHCI/OHCI/EHCI/XHCI drivers registered by USB.init
            → UHCI.load / OHCI.load / EHCI.load / XHCI.load
              → HC hardware init
              → usbcore.register_hc(hc)
              → usbcore.scan_ports(hc)
                → For each port with a device:
                  → port reset
                  → enumeration (GET_DESCRIPTOR, SET_ADDRESS, SET_CONFIG)
                  → drivermanagement.register_device(biUSB, class/sub/proto, @usbdev)
                    → Matches usb_keyboard / usb_mouse / usbhub drivers
                      → Class driver load callback
                        → Begin polling / operation

11. Memory & DMA Strategy

11.1 Requirements

USB host controllers require physically-contiguous, properly-aligned memory that must be accessible by the hardware (DMA). In Asuro's identity-mapped kernel space, virtual = physical (for pages set up via force_alloc_block + map_page).

11.2 Allocation Patterns

Structure Size Alignment Allocator
UHCI Frame List 4096 bytes 4KB (page) force_alloc_block + map_page (full page)
UHCI TD 32 bytes 16-byte kalloc(32) — ensure returned address is 16-byte aligned (may need alignment wrapper)
UHCI QH 16 bytes 16-byte kalloc(16) + alignment
OHCI HCCA 256 bytes 256-byte Need aligned alloc helper
OHCI ED 16 bytes 16-byte kalloc(16) + alignment
OHCI TD 16 bytes 16-byte kalloc(16) + alignment
EHCI Periodic Frame List 4096 bytes 4KB force_alloc_block + map_page
EHCI QH 64 bytes 32-byte kalloc(64) + alignment
EHCI qTD 32 bytes 32-byte kalloc(32) + alignment
xHCI rings 4096+ bytes 64-byte (TRBs are 16B) Page allocation
xHCI DCBAA N × 8 bytes 64-byte Page allocation
Transfer buffers Variable 4KB for bulk/iso kalloc or page alloc

11.3 Alignment Helper

{ Allocate size bytes with specified alignment }
function kalloc_aligned(size : uint32; alignment : uint32) : Pointer;
var
    raw      : Pointer;
    aligned  : uint32;
    overhead : uint32;
begin
    overhead := alignment + sizeof(uint32);
    raw := Pointer(kalloc(size + overhead));
    aligned := (uint32(raw) + overhead + alignment - 1) AND (NOT (alignment - 1));
    { Store original pointer just before aligned address for kfree }
    PUint32(aligned - 4)^ := uint32(raw);
    kalloc_aligned := Pointer(aligned);
end;

procedure kfree_aligned(p : Pointer);
var
    raw : Pointer;
begin
    raw := Pointer(PUint32(uint32(p) - 4)^);
    kfree(void(raw));
end;

This utility should go in usbtypes.pas or a dedicated usbmem.pas.


12. Transfer Scheduling

12.1 Polled Model

All four HC types use a polled completion model initially:

  1. A timer callback (registered with the existing timer/scheduler system) fires periodically
  2. Calls usbcore.poll_all() which iterates all registered HCs and calls hc^.fnPoll(hc)
  3. Each HC's poll function:
  4. Checks hardware status register for completion events
  5. Walks completed TD/TRB chains
  6. Updates TUSBTransfer.Status and TUSBTransfer.ActualLen
  7. Frees completed TDs/TRBs
  8. For hub interrupt endpoints: checks for port status changes, triggers enumeration/removal

12.2 Transfer Submission Flow

Class Driver                    usbcore                     HC Driver
    │                              │                            │
    ├─ usb_interrupt_transfer() ──►│                            │
    │                              ├─ build TUSBTransfer ──────►│
    │                              │   hc^.fnSubmit(transfer)   │
    │                              │                            ├─ Build TD/QH/TRB chain
    │                              │                            ├─ Link into schedule
    │                              │                            ├─ Ring doorbell (xHCI)
    │                              │◄──────────── true ─────────┤
    │◄──── tsInProgress ───────────┤                            │
    │                              │                            │
    │    ... time passes ...       │                            │
    │                              │                            │
    │                              ├─ poll_all() ──────────────►│
    │                              │                            ├─ Check status register
    │                              │                            ├─ Walk done list
    │                              │                            ├─ Update transfer status
    │                              │◄──────────────────────────┤
    │                              │                            │
    │  (Class driver checks        │                            │
    │   transfer.Status on next    │                            │
    │   poll cycle)                │                            │

12.3 Interrupt Transfer Resubmission

HID devices need periodic interrupt transfers. After a successful completion, the class driver (or a helper in usbcore) automatically resubmits the interrupt transfer to maintain continuous polling. The poll interval is derived from bInterval in the endpoint descriptor.


13. Unit Testing Strategy

All phases must include unit tests where appropriate, following the pattern established in strings.pas:

  • Each unit that has testable logic exposes a procedure UnitTest; in its interface
  • The UnitTest procedure uses a local Assert(condition, testName) nested procedure
  • Tests log via syslog with the unit name as tag
  • A PrintSummary nested procedure reports passed/failed counts
  • Tests are called from kernel.pas after init (alongside strings.UnitTest)
  • Tests must properly kfree any allocated memory to avoid leaks
  • Since this is bare-metal, tests run at boot — focus on logic that can be validated without hardware (descriptor parsing, data structure manipulation, encoding/decoding, identifier matching)

What to test per phase: | Phase | Testable Items | |-------|---------------| | 0 (Foundation) | usbtypes: kalloc_aligned/kfree_aligned, setup packet building, descriptor size constants | | 1 (Core) | usbcore: descriptor parsing, address assignment, identifier encoding, endpoint extraction | | 2-3 (UHCI/OHCI) | TD/QH/ED construction helpers, frame list setup validation, register bitmask helpers | | 4 (Hub) | Port status bitmap parsing, topology depth calculation | | 5 (HID) | Boot report parsing (keyboard modifier decode, mouse delta extraction) | | 6-7 (EHCI/xHCI) | QH/qTD/TRB construction, capability register parsing |


14. Implementation Phases

Phase 0: Foundation (Prerequisites)

  • [x] Create src/driver/bus/usb/ directory structure
  • [x] Move existing USB/UHCI/OHCI/EHCI/XHCI .pas files to new location
  • [x] Update build scripts (compile_sources.sh) for new paths
  • [x] Implement usbtypes.pas — all shared types, constants, HC abstraction record
  • [x] Implement alignment helper (kalloc_aligned / kfree_aligned)
  • [x] Implement usbtypes.UnitTest — test alignment helpers, setup packet building, constants
  • [x] Create usbcore.pas skeleton — HC registration, device list, poll stubs
  • [x] Verify build still succeeds with moved files

Phase 1: USB Core Skeleton

  • [x] Implement usb_control_msg() — builds setup packet, submits via HC abstraction
  • [x] Implement descriptor parsing helpers (device, config, interface, endpoint)
  • [x] Implement address assignment logic
  • [x] Implement full enumeration sequence (reset → get desc → set addr → get full desc → set config → register device)
  • [x] ~~Register a timer/scheduler callback for poll_all()~~ — DEFERRED: hooking poll_all into TMR_0_ISR caused severe UI lag (runs inside IRQ0 with interrupts disabled). Removed for now. usb_control_msg polls inline. A non-ISR polling approach (e.g. main loop or scheduler task) is needed later.
  • [x] Implement usbcore.UnitTest — descriptor parsing, identifier encoding, endpoint extraction (47 tests)

Phase 2: UHCI Host Controller

  • [x] UHCI register definitions and private data structure
  • [x] UHCI init: read BAR4, global reset, allocate frame list
  • [x] UHCI skeleton schedule: create QH chain (control, bulk, interrupt)
  • [x] UHCI start: write FRBASEADD, set RS bit
  • [x] UHCI port detection: read PORTSC, detect connected devices
  • [x] UHCI port reset sequence
  • [x] UHCI submit_transfer: build TD chain, link to appropriate QH
  • [x] UHCI poll: check USBSTS, walk TDs for completion, update transfer status
  • [x] UHCI control transfer: implemented in uhci_submit_control (live test needs real UHCI hardware)
  • [x] UHCI interrupt transfer: implemented via uhci_submit_async (live test needs real UHCI hardware)
  • [x] UHCI bulk transfer: implemented via uhci_submit_async (live test needs real UHCI hardware)
  • [ ] UHCI isochronous transfer support (low priority — no current use case)
  • [x] UHCI unit tests: TD/QH construction helpers, bitmask encoding (47 tests)
  • [ ] Test: enumerate a USB device on real UHCI hardware — VirtualBox never emulates UHCI (always uses OHCI for USB 1.1)

Phase 3: OHCI Host Controller

  • [x] Extend existing TOHCI_MMR with full register set
  • [x] OHCI init: map MMIO, reset, allocate HCCA (256-byte aligned)
  • [x] OHCI ED/TD management: create, link, free
  • [x] OHCI skeleton schedule: control/bulk/interrupt/isochronous ED lists
  • [x] OHCI start: set operational, enable port power
  • [x] OHCI port detection and reset
  • [x] OHCI submit_transfer: build TD chain, attach to ED, place in appropriate list
  • [x] OHCI poll: read HcDoneHead, process completed TDs
  • [x] OHCI control, interrupt, bulk, isochronous transfers
  • [x] OHCI unit tests: ED/TD construction, register bitmask helpers (78/78 pass)
  • [x] Test: enumerate device on VirtualBox (ICH9 chipset) — VID=80EE PID=0021, address 1
  • [x] Fix: DMA virtual→physical address conversion via vtop() for all HC-visible pointers

Phase 4: Hub Support

  • [x] Implement usbhub.pas — hub class driver
  • [x] Hub detection during enumeration (class $09) via drivermanagement (biUSB, id1=$09)
  • [x] GET_HUB_DESCRIPTOR, port power on (SET_FEATURE PORT_POWER per port)
  • [x] Hub interrupt IN endpoint polling (status change bitmap)
  • [x] Port status change handling (connect, disconnect, reset, overcurrent, enable, suspend)
  • [x] Recursive enumeration for devices behind hubs (depth-limited to 5)
  • [x] Device removal / cleanup path — usb_remove_device() with fnDisconnect callback (implemented in Phase 8)
  • [x] Topology tracking (parent hub, port, depth via TUSBDevice.ParentHub/ParentPort)
  • [x] Root hub emulation consistency — enumerate_device skips HC port_reset when parentHub<>nil
  • [x] Hub unit tests: port status bitmap parsing, speed detection, constants (44/44 pass)

Phase 5: USB HID Class Drivers

  • [x] usb_keyboard.pas — register with drivermanagement (biUSB, $03/$01/$01)
  • [x] Keyboard: SET_PROTOCOL(boot), SET_IDLE(0)
  • [x] Keyboard: find interrupt IN endpoint, begin polling
  • [x] Keyboard: parse 8-byte boot report, translate to TKeyInfo via HID usage tables
  • [x] Keyboard: integrate with existing keyboard hook system (captin_hook, modifier globals)
  • [x] usb_mouse.pas — register with drivermanagement (biUSB, $03/$01/$02)
  • [x] Mouse: SET_PROTOCOL(boot), find interrupt IN endpoint
  • [x] Mouse: parse 3/4-byte boot report (buttons, X, Y, wheel) with signed delta extraction
  • [x] Mouse: integrate with existing mousestate system (setMousePos, fireMouseEvent, addScroll)
  • [x] HID unit tests: boot report parsing — keyboard 62 tests, mouse 33 tests (modifier keys, usage-to-ASCII, deltas, buttons, scroll)
  • [x] Test: USB keyboard in VirtualBox (xHCI) — boot protocol keyboard on slot 1, EP1 interrupt transfers, input working

Phase 6: EHCI Host Controller

  • [x] EHCI capability + operational register mapping
  • [x] EHCI init: reset, halt, allocate periodic frame list + async QH
  • [x] EHCI QH/qTD management
  • [x] EHCI async schedule (control + bulk)
  • [x] EHCI periodic schedule (interrupt + isochronous)
  • [x] EHCI port routing: CONFIGFLAG, companion controller handoff
  • [x] EHCI split transactions for full/low-speed devices behind hubs (TT)
  • [x] EHCI start, port detection, reset, enumeration
  • [x] EHCI poll: USBSTS, async/periodic doorbell, qTD completion
  • [x] EHCI unit tests: QH/qTD construction, capability parsing (90 tests pass)
  • [x] Test: USB 2.0 device on VirtualBox (ICH9) — Intel 8086:265C detected, companion routing works

Phase 7: xHCI Host Controller

  • [x] xHCI capability register parsing (CAPLENGTH, HCSPARAMS1/2, HCCPARAMS1, DBOFF, RTSOFF)
  • [x] xHCI init: reset, halt, MaxSlots, DCBAA, scratchpad buffers
  • [x] xHCI Command Ring, Event Ring, Transfer Ring allocation (page-aligned, Link TRBs)
  • [x] xHCI Interrupter 0 setup (ERST, ERDP, polling mode — no hardware interrupts)
  • [x] xHCI start controller (RS, HSEE, no INTE)
  • [x] xHCI BIOS/OS handoff (USBLEGSUP extended capability)
  • [x] xHCI port detection: PORTSC scan, speed mapping
  • [x] xHCI Enable Slot → Address Device commands (Input Context, Slot+EP0 contexts)
  • [x] xHCI Configure Endpoint: lazy per-endpoint ring allocation + command for non-EP0 endpoints
  • [x] xHCI transfer submission: control (Setup/Data/Status TRBs, SET_ADDRESS interception), bulk/interrupt (Normal TRBs)
  • [x] xHCI poll: dequeue Event Ring, process Transfer Events + Command Completion + Port Status Change
  • [x] xHCI unit tests: TRB construction, DCI calculation, speed mapping, EP type mapping (119 tests pass)
  • [x] Test: USB device on VirtualBox (xHCI enabled) — Intel 8086:1E31 detected, keyboard enumerated + input working

Phase 8: Hardening & Extras

  • [x] Error recovery: stall handling (CLEAR_FEATURE ENDPOINT_HALT) — usb_clear_halt() in usbcore
  • [x] Transfer timeouts — usb_control_msg uses bios_data_area.Counters.c32 (1024 Hz) with 5-second real-time timeout instead of spin-loop
  • [x] Device disconnect cleanup (free TDs, endpoints, device record) — usb_remove_device() in usbcore
  • [x] USB terminal command for USB device information
  • [x] Interrupt-driven completion (ISR registered for each HC on PCI interrupt line)
  • [x] USB Mass Storage (Bulk-Only Transport) class driver — usb_storage.pas with CBW/CSW, SCSI (INQUIRY, TEST_UNIT_READY, READ_CAPACITY, READ_10, WRITE_10), storagemanagement integration
  • [x] Hotplug: ISR-driven detection (OHCI RHSC, EHCI PCD, xHCI Port Status Change TRB) → deferred usb_check_hotplug in main loop; HotplugArmed guard prevents spurious boot-time events
  • [x] Disconnect callbacks: TUSBDisconnectCallback on TUSBDevice.fnDisconnect, called by usb_remove_device before freeing — keyboard/mouse/storage drivers clean up transfers and deactivate
  • [x] Dynamic device lists: keyboard, mouse, storage drivers use lists.pas linked lists (no static array limits); devices freely plug/unplug without slot exhaustion
  • [x] HID fail-count guard: 3 consecutive transfer failures → self-deactivate (handles race between unplug and deferred hotplug processing)
  • [x] usb_bulk_transfer_wait — blocking bulk helper with timer-based timeout, used by mass storage BOT
  • [ ] Code audit: verify all kalloc/kfree pairs, no leaks

14. Progress Tracker

Phase Status Notes
Phase 0: Foundation Complete Files moved to src/driver/bus/usb/, usbtypes.pas + usbcore.pas created, build scripts updated, 71 unit tests passing (51 usbtypes + 20 usbcore)
Phase 1: USB Core Complete Descriptor parsing helpers, control/bulk/interrupt transfer API, inline polling
Phase 2: UHCI Complete Full UHCI driver with QH/TD, registers, schedule, control+bulk+interrupt submit, poll — 47 unit tests pass. Live testing requires real UHCI hardware (VirtualBox always uses OHCI for USB 1.1, never UHCI). Isochronous not yet implemented (low priority).
Phase 3: OHCI Complete Full OHCI driver: HCCA+ED+TD, MMIO, reset/start/stop, control+async submit, poll with TD chain cleanup, vtop() DMA fix — 78 unit tests pass, VirtualBox ICH9 enumerates VID=80EE PID=0021 at address 1
Phase 4: Hub Support Complete usbhub.pas hub class driver: hub descriptor, port power, port reset, status change polling, recursive enumeration, depth limit — 44 unit tests pass. Device removal via usb_remove_device() + fnDisconnect (Phase 8).
Phase 5: HID Drivers Complete usb_keyboard.pas (62 tests) + usb_mouse.pas (33 tests): HID boot protocol, usage-to-ASCII tables, signed delta extraction, mousestate/keyboard hook integration. VBox live test pending (no HID keyboard/mouse hardware attached).
Phase 6: EHCI Complete Full EHCI driver: capability/operational registers, QH+qTD 32-byte aligned, async+periodic schedule, BIOS legacy handoff (EECP), port reset with companion controller routing (low-speed/not-enabled → OHCI), control+bulk+interrupt submit, poll with qTD chain completion — 90 unit tests pass. VBox ICH9 live: Intel 8086:265C detected, CONFIGFLAG routes ports, full-speed devices correctly handed off to companion OHCI.
Phase 7: xHCI Complete Full xHCI driver: capability parsing, ring-based model (Cmd/Evt/Transfer rings), BIOS handoff, DCBAA, scratchpad, polling mode (no IRQ), Enable Slot + Address Device + Configure Endpoint, control+bulk+interrupt submit, event ring poll — 119 unit tests pass. VBox xHCI live: Intel 8086:1E31, keyboard on slot 1 with lazy EP configuration, input working.
Phase 8: Hardening Complete usb_clear_halt() stall recovery, usb_remove_device() with fnDisconnect callback, USB terminal command, interrupt-driven completion for all 4 HCs, completion hooks for HID drivers, polling removed from kernel main loop. Timer-based transfer timeouts (5s via bios_data_area.Counters.c32). USB Mass Storage BOT driver (usb_storage.pas) with SCSI over bulk endpoints + storagemanagement registration. Hotplug: ISR sets PortChangePending (guarded by HotplugArmed), deferred usb_check_hotplug in main loop. Disconnect callbacks clean up class driver state. Dynamic linked lists replace static arrays — unlimited plug/unplug cycles. HID fail-count self-deactivation (3 strikes). Remaining: kalloc/kfree audit.

Appendix A: USB Class Codes (Reference)

Class Description Subclass examples
$00 Use interface descriptors —
$03 HID $01 Boot, Protocol: $01 Keyboard, $02 Mouse
$08 Mass Storage $06 SCSI, $50 Bulk-Only
$09 Hub $00 Full-speed, $01 Hi-speed single TT, $02 Hi-speed multi TT

Appendix B: VirtualBox USB Testing Notes

  • PIIX3 chipset → emulates OHCI + EHCI (VirtualBox never emulates UHCI)
  • ICH9 chipset → emulates OHCI + EHCI companion pair
  • xHCI → optional, enable in VM settings (USB 3.0 controller)
  • USB device passthrough or VirtualBox's emulated USB tablet/keyboard for testing
  • Serial output via Get-Content "C:\Temp\asuro.log" for debug logging
  • VirtualBox USB filters can attach host USB devices to the VM

Appendix C: Known Issues & Future Work

  • UHCI live-hardware testing: VirtualBox never emulates UHCI (always uses OHCI for USB 1.1 regardless of PIIX3/ICH9 chipset). UHCI driver code is complete with 47 unit tests but needs real Intel ICH southbridge hardware (Pentium 4 era) for end-to-end testing.