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]