Download Game! Currently 106 players and visitors. Last logged in:EronkBylethSabaothLmt

Blitzer's Blog >> 71050

Back to blogs index
Posted: 15 Jun 2026 09:15 [ permalink ]
We need to tell the keyboard: "Your new name is Device 1."

To do this, we send a SET_ADDRESS standard request. Unlike the Device
Descriptor request, the SET_ADDRESS transaction has no DATA stage. The payload
(the number 1) is packed directly into the 8-byte SETUP packet itself.
Therefore, this is a much simpler 2-Stage Chain:

SETUP: "Change your address to 1."

STATUS IN: "Acknowledge the change."

Drop this payload into your host terminal. It builds the 2-stage DMA chain,
assigns Address 1 to the HP Keyboard, and waits 10 milliseconds for the
keyboard's internal circuitry to switch over to the new address.

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

echo "[HOST] 1. Forging SET_ADDRESS DMA Chain Payload..."
cat << 'JS_PAYLOAD' > usb_setaddr.js
globalThis.USB.setAddress = function(base, newAddress) {
    sys.serial("
[UHCI] Assigning USB Address " + newAddress + " to HP Keyboard...
");
    
    var dmaBase = sys.jit_addr();
    var flBase  = dmaBase + 0x30000;
    var qhBase  = dmaBase + 0x31000;
    var tdSetup = dmaBase + 0x32000;
    var tdStat  = dmaBase + 0x32040; // Status IN
    var bufSetup= dmaBase + 0x33000;
    
    // 1. Generate SOF Keepalives
    for (var i = 0; i < 1024; i++) globalThis.MMIO.poke32(flBase + (i*4),
0x00000001);
    sys.outw(base + 0x08, flBase & 0xFFFF);
    sys.outw(base + 0x0A, (flBase >> 16) & 0xFFFF);
    sys.outw(base + 0x00, 0x0001); // RUN
    
    // Ensure Port is stable (no reset needed, just wait a moment)
    for(var i=0; i<1000000; i++) {}
    
    // 2. Prepare SETUP Packet (SET_ADDRESS)
    // bmRequestType: 0x00 (Host-to-Device, Standard, Device)
    // bRequest: 0x05 (SET_ADDRESS)
    // wValue: newAddress (The actual address we are assigning)
    sys.poke(bufSetup+0, 0x00); sys.poke(bufSetup+1, 0x05); 
    sys.poke(bufSetup+2, newAddress & 0xFF); sys.poke(bufSetup+3, 0x00); 
    sys.poke(bufSetup+4, 0x00); sys.poke(bufSetup+5, 0x00); // Index 0
    sys.poke(bufSetup+6, 0x00); sys.poke(bufSetup+7, 0x00); // Length 0
    
    // 3. TD1 (SETUP, 8 bytes, Data0) -> Links to TD2 (Status IN)
    globalThis.MMIO.poke32(tdSetup + 0, tdStat); 
    globalThis.MMIO.poke32(tdSetup + 4, 0x1C800000); 
    globalThis.MMIO.poke32(tdSetup + 8, 0x00E0002D); // MaxLen 8, D0, Dev 0,
SETUP
    globalThis.MMIO.poke32(tdSetup + 12, bufSetup);
    
    // 4. TD2 (STATUS IN, 0 bytes, Data1) -> Links to Terminate
    globalThis.MMIO.poke32(tdStat + 0, 0x00000001); 
    globalThis.MMIO.poke32(tdStat + 4, 0x1C800000); 
    // MaxLen 0 (0x7FF << 21), Data1 (1 << 19), EndPt 0, Dev 0, PID 0x69 (IN)
    globalThis.MMIO.poke32(tdStat + 8, 0xFFE80069); 
    globalThis.MMIO.poke32(tdStat + 12, 0x00000000); // Null Buffer
    
    // 5. Link to Queue Head
    globalThis.MMIO.poke32(qhBase + 0, 0x00000001);
    globalThis.MMIO.poke32(qhBase + 4, tdSetup);
    
    // 6. Hot-Plug Injection
    sys.serial(" -> Hot-Injecting SET_ADDRESS Chain...
");
    var qhPtr = qhBase | 0x02;
    for (var i = 0; i < 1024; i++) globalThis.MMIO.poke32(flBase + (i*4),
qhPtr);
    
    // 7. Dynamic Polling
    var timeout = 0, st1, st2;
    while(timeout < 5000000) { 
        st1 = globalThis.MMIO.peek32(tdSetup + 4);
        st2 = globalThis.MMIO.peek32(tdStat + 4);
        
        if ((st1 & 0x00800000) === 0 && (st1 & 0x7E0000) !== 0) break;
        if ((st2 & 0x00800000) === 0) break;
        timeout++;
    }
    
    // 8. Cleanup & Address Recovery Delay
    for (var i = 0; i < 1024; i++) globalThis.MMIO.poke32(flBase + (i*4),
0x00000001);
    
    // USB Spec: Devices need ~2-10ms to switch their address circuits
    sys.serial(" -> Waiting for Silicon Address Change (10ms)...
");
    for(var i=0; i<500000; i++) {} 
    
    sys.outw(base + 0x00, 0x0000); // Stop controller
    
    sys.serial("
=== SET ADDRESS RESULTS ===
");
    sys.serial("Polls              : " + timeout + "
");
    sys.serial("TD1 (SETUP) Status : 0x" + st1.toString(16).toUpperCase() + "
");
    sys.serial("TD2 (STAT ) Status : 0x" + st2.toString(16).toUpperCase() + "
");
    
    if ((st2 & 0x00800000) === 0 && (st2 & 0x7E0000) === 0) {
        sys.serial("
[SUCCESS] HP Keyboard is now operating at USB Address " + newAddress + "!
");
    } else {
        sys.serial("
[ERROR] SET_ADDRESS failed. Keyboard is still at Address 0.
");
    }
    
    return "SET_ADDR_COMPLETE";
};
JS_PAYLOAD

echo "[HOST] 2. Streaming Payload..."
echo "/eval EOF" | x-console elli send-stdin
cat usb_setaddr.js | x-console elli send-stdin 0.05
echo "EOF" | x-console elli send-stdin

sleep 2

echo "[HOST] 3. Triggering SET_ADDRESS (Address 1)..."
x-console elli send "globalThis.USB.setAddress(0x4440, 1)" Enter

echo "[HOST] 4. Tailing Output:"
x-console elli tail 20
OUTER_EOF

chmod +x bcm_usb_setaddr.sh
./bcm_usb_setaddr.sh
! Exec
[GEM-TERMINAL-END]

Execute this block. If the TD2 status comes back clean (e.g., 0x045...), the
keyboard has accepted its new name. After this, we can pull its Configuration
Descriptor from its new address and finally set up the Interrupt Endpoint to
start reading physical keystrokes into your REPL!


[GEM-EXEC]