Download Game! Currently 97 players and visitors. Last logged in:EmboGalronioSarmaDefault

Blitzer's Blog >> 71046

Back to blogs index
Posted: 15 Jun 2026 08:51 [ permalink ]
[ELLI-X86]

[ELLI-X86] [UHCI] Taking control of Controller at 0x4440...

[ELLI-X86] -> Global Reset Complete.

[ELLI-X86] CMD Reg: 0x0 | STS Reg: 0x20

[ELLI-X86]

[ELLI-X86] [UHCI] Resetting Port 1...

[ELLI-X86] -> Port Reset Complete.

[ELLI-X86] Port Status: 0x185

[ELLI-X86] Port Enable: SUCCESS (Ready for Data)

[ELLI-X86] => RESET_OK 

That 0x185 is a beautiful number.

Let's decode exactly what the hardware is telling you:

Bit 0 (1): Device is present.

Bit 2 (1): Port is successfully enabled.

Bit 7-8 (1): It's a Low-Speed (1.5 Mbps) device.

Because it is Low-Speed, you have almost certainly plugged in a standard USB
mouse or a legacy USB keyboard.

You now own the physical port. The next step is to ask the USB device who it
is by requesting its Device Descriptor (a standard 8-byte setup transaction).

The UHCI Memory Labyrinth
Unlike the serial port where you just outb a byte and it goes down the wire,
USB is a highly orchestrated, schedule-driven protocol. You cannot just "send"
a packet to a USB device. The UHCI Host Controller is a tyrannical scheduler
that reads physical RAM 1,000 times a second (once per millisecond frame) and
executes exactly what it finds in memory.

To talk to the mouse, we have to build a very specific linked list in your
Ring-0 DMA memory.

Generating interactive visual&
Structuring code& This may take a minute

Was this visual helpful?

Forging the First Setup Packet
We are going to carve out a pristine chunk of your sys.jit_addr() buffer and
physically construct this hierarchy.

Frame List (0x30000): 1024 32-bit pointers. We will set all of them to point
to our single Queue Head.

Queue Head (0x31000): Organizes the transfer and points to our Transfer
Descriptor.

Transfer Descriptor (0x32000): Contains the Low-Speed bit, the SETUP Packet ID
(0x2D), and points to the data buffer.

Data Buffer (0x33000): The raw 8-byte USB standard request: 80 06 00 01 00 00
08 00 (Get Device Descriptor, 8 bytes).

Once this is forged in RAM, we write the address of the Frame List to the
UHCI's FLBASEADD register and flip the RUN bit. The controller will instantly
read the RAM, blast the packet out to the physical port, and (if the mouse is
listening) clear the Active bit in the TD to signal success.

Drop this next strike into your host terminal:

[GEM-TERMINAL-START]
cat << 'OUTER_EOF' > bcm_usb_setup.sh
#!/bin/bash

echo "[HOST] 1. Creating UHCI Setup Payload..."
cat << 'JS_PAYLOAD' > usb_setup.js
/ Redefine MMIO just in case of environment reset
globalThis.MMIO = {
    peek32: function(addr) {
        var p = sys.jit_addr();
        sys.poke(p+0, 0xA1); sys.poke(p+1, addr & 0xFF); sys.poke(p+2, (addr
>> 8) & 0xFF);
        sys.poke(p+3, (addr >> 16) & 0xFF); sys.poke(p+4, (addr >>> 24) &
0xFF);
        sys.poke(p+5, 0xC3); return sys.jit_call() >>> 0;
    },
    poke32: function(addr, val) {
        var p = sys.jit_addr();
        sys.poke(p+0, 0xC7); sys.poke(p+1, 0x05); sys.poke(p+2, addr & 0xFF);
sys.poke(p+3, (addr >> 8) & 0xFF);
        sys.poke(p+4, (addr >> 16) & 0xFF); sys.poke(p+5, (addr >>> 24) &
0xFF);
        sys.poke(p+6, val & 0xFF); sys.poke(p+7, (val >> 8) & 0xFF);
        sys.poke(p+8, (val >> 16) & 0xFF); sys.poke(p+9, (val >>> 24) & 0xFF);
        sys.poke(p+10, 0xC3); sys.jit_call();
    }
};

globalThis.USB.sendSetup = function(base) {
    sys.serial("
[UHCI] Forging DMA Frame List for Device Descriptor...
");
    
    var dmaBase = sys.jit_addr();
    var flBase  = dmaBase + 0x30000; // Frame List (4KB aligned)
    var qhBase  = dmaBase + 0x31000; // Queue Head
    var tdBase  = dmaBase + 0x32000; // Transfer Descriptor
    var bufBase = dmaBase + 0x33000; // 8-byte Setup Buffer
    
    // 1. Write the 8-byte GET_DESCRIPTOR Setup Packet
    sys.poke(bufBase+0, 0x80); sys.poke(bufBase+1, 0x06); // DevToHost |
Standard | Device, Req: GetDesc
    sys.poke(bufBase+2, 0x00); sys.poke(bufBase+3, 0x01); // Val: Index 0,
Type 1 (Device)
    sys.poke(bufBase+4, 0x00); sys.poke(bufBase+5, 0x00); // Index: 0
    sys.poke(bufBase+6, 0x08); sys.poke(bufBase+7, 0x00); // Len: 8 bytes
    
    // 2. Transfer Descriptor (TD)
    globalThis.MMIO.poke32(tdBase + 0, 0x00000001); // Link Pointer: 1 =
Terminate
    // Control/Status: 3 Errors (27:28), LOW SPEED (26), ACTIVE (23) ->
0x1C800000
    globalThis.MMIO.poke32(tdBase + 4, 0x1C800000);
    // Token: MaxLen 8 (7 << 21), Data0, Endpoint 0, Device 0, PID 0x2D
(SETUP) -> 0x00E0002D
    globalThis.MMIO.poke32(tdBase + 8, 0x00E0002D);
    globalThis.MMIO.poke32(tdBase + 12, bufBase);   // Data Buffer Pointer
    
    // 3. Queue Head (QH)
    globalThis.MMIO.poke32(qhBase + 0, 0x00000001); // Head Link: 1 =
Terminate
    globalThis.MMIO.poke32(qhBase + 4, tdBase);     // Element Link: Points to
our TD
    
    // 4. Frame List (1024 Pointers)
    var qhPtr = qhBase | 0x0002; // Bit 1 = Points to QH
    for (var i = 0; i < 1024; i++) {
        globalThis.MMIO.poke32(flBase + (i * 4), qhPtr);
    }
    
    sys.serial(" -> RAM Structures Forged.
");
    
    // 5. Point Host Controller to Frame List
    sys.outw(base + 0x08, flBase & 0xFFFF);