aboutsummaryrefslogtreecommitdiffstats
path: root/bridge/README.md
blob: 86f4d04fa6e751af8633e4e5332598f76f7f10a4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# Bridge GCP cmd-buffer TOCTOU hammer (ASM2464PD, opcode 0xEB)

Custom bridge firmware that rapidly flips a single 32-bit PCIe target between two
values, to race the PSP GCP TA's triple-read of `cmdBuf[0x818]` (the double-fetch
in `fw/FINDING-GCP-FE09-CONFIGINIT-HOST-OFFSET-WRITE.md`, Add.70). The bridge is
the concurrent writer the host USB path can't be (submit-then-poll is too slow).

## What the bridge actually runs (IMPORTANT)

The box's bridge runs the **handmade** firmware from `tinygrad/asm2464pd-firmware`
built at HEAD `ed4e39b7` (clean) → USB id **`3801:0001 "custom ed4e39b7-CLEAN"`**,
~9 KB. tinygrad's `usb.py` asserts the product string starts with `"custom"`, and
`ops_amd.py` accepts VID `0x3801`/`0xADD1`.

The repo's prebuilt `fw.bin` / `fw_tinygrad.bin` (98 KB, product "USB 3.2 …") are a
DIFFERENT, protocol-incompatible variant — flashing them bricks the GPU link
(first `E4` control-read fails with a USB pipe error). **Do not flash those.**

- `fw_handmade_CLEAN.bin`  — exact restore baseline (`ed4e39b7-CLEAN`, sha `21d1fce4…`)
- `fw_handmade_HAMMER.bin` — the hammer build currently flashed (`ed4e39b7-DIRTY`, sha `f2cbf85b…`)
- `handmade_gcp_hammer_0xEB.patch` — the source change (apply to `handmade/src/main.c`)
- `eb_hammer_test.py` — on-box functional test (verified PASS)
- `_obsolete/` — the old binary-patch approach (built against the wrong 98 KB image)

## The handler (integrated in `handmade/src/main.c`, not binary-patched)

New vendor control command `0xEB`, reusing the firmware's own posted-MWr TLP
primitive (`REG_PCIE_*` @ 0xB210–0xB296; `STATUS=0x07; TRIGGER=0x0F`, fire-and-forget).
It **rings the PSP ring doorbell itself** (one MemWr), then immediately flips the
target between A and B — the single 8051 can't both service a host submit and hammer,
so the final doorbell must come from the bridge.

Config lives in a pinned firmware global `hammer_cfg` (XDATA, 32 B), poked by the
host via the `0xE5` XDATA-write path (no timing constraint). Read its address from
the build map (`_hammer_cfg`, currently **0x0009** — in the firmware's global RAM;
note `0x6000` is NOT backed by RAM). The `0xEB` trigger carries only the 4-byte
arming magic.

```
hammer_cfg (all little-endian):
  [0:8]   doorbell PCIe addr (0 => skip)   [8:12]  doorbell value
  [12:20] hammer target PCIe addr          [20:24] value A
  [24:28] value B                          [28:30] iterations (<=0x8000)
  [30]    fmt_type (0 => 0x60 MWr64)       [31]    byte_en (0 => 0x0F)
trigger: control_write(0xEB, data=b'HMR\x01')
```

Byte maps (match the proven host path): value DATA_0 = LE LSB (natural store);
addr ADDR_0<-byte3 .. ADDR_3<-byte0, HIGH_0<-byte7 .. HIGH_3<-byte4. The doorbell
reg `{reg_pref}_67` (regMP0_SMN_C2PMSG) resolves to PCIe addr `0x1005820c`.

Safety gates: distinct opcode; arming magic; doorbell skipped if addr==0; iter
clamp; always ACKs with a ZLP. Host side: `host_hammer.py` (arm/fire/capture_doorbell).

## Build / flash / restore

```sh
# build (from a clean clone of tinygrad/asm2464pd-firmware @ ed4e39b7)
cd handmade && patch -p1 < .../handmade_gcp_hammer_0xEB.patch   # or edit main.c
make wrapped            # -> build/firmware_wrapped.bin  ("custom ed4e39b7-DIRTY")

# flash (on the box; FTDI + bridge both on 192.168.1.200)
python ftdi_debug.py --bootloader --no-read     # -> ROM bootloader 174c:2463
#   wait until `lsusb | grep 174c:246` shows the bootloader, THEN:
PYTHONPATH=/root/psp/tinygrad python flash.py firmware_wrapped.bin
python ftdi_debug.py --reset --no-read

# restore the clean firmware the same way with fw_handmade_CLEAN.bin
```

## Recovery (PROVEN on this board)

The FTDI (`ftdi://ftdi:230x/1`, already EEPROM-provisioned CBUS=GPIO) can force the
ASM2464's mask-ROM bootloader via `--bootloader`, independent of flash contents, so
a bad image is always reflashable. Demonstrated repeatedly: running fw → bootloader
(`174c:2463`) → reflash → boot. The FT230X has no MPSSE, so the ONLY out-of-band
path is this strap; if it ever fails, recovery needs a SPI programmer on the chip.

## Status

- [x] handler integrated in source, asm-verified, flashed (`ed4e39b7-DIRTY`)
- [x] box fully functional on it: LINK UP, GPU init, vram `0x5ff000000`
- [x] **doorbell + hammer** handler built, asm-verified (FMT once, doorbell gated,
      target-addr set, A/B loop), flashed; config RAM at `0x0009` round-trips
- [x] exercised on HW (`mech2.py`): doorbell writes the configured value to its
      address (Test A: `0xD00DB011`); hammer runs the A/B loop (Test B: `0xBBBBBBBB`,
      last-write wins); arming-magic gate no-ops; identical behaviour to the proven
      `pcie_mem_write`
- [~] **VRAM read-back cannot verify distinct-address writes**: `pcie_mem_read` over
      the USB tunnel is cache-confounded (stale write-latch without flush, 0 with
      `flush_hdp`) — a read-tool limit, not a handler bug (positive control with
      `pcie_mem_write` fails identically). Distinct-address correctness must be
      settled by the PSP-side oracle in the chain.
- [x] **race RUN** (`race_experiment.py`, on-box `hw/race.py`): fault-free observability
      test against the live state-3 chain. Positive controls green (signal `0`/`256`;
      overlap plumbing completes). **24/24 the TA read the STAGED `0x818`, never the
      hammered value** (`stage 0x1000 flip->2 => {0:12}`; `stage 2 flip->0x1000 =>
      {256:12}`). => the cmd buffer is cached/snapshotted at dispatch; the double-fetch
      TOCTOU is **NOT exploitable by a concurrent PCIe writer** in this ordering. See
      FINDING Add.72.
- [~] bridge-issued doorbell is DEAD: a bridge single-TLP MWr to the wptr reg
      `0x1005820c` does not trigger PSP processing (host streaming write does). This
      blocks the one untested ordering (hammer live *before* dispatch snapshot).
- [ ] win condition (NOT achieved): a chosen 32-bit value at a far-OOB target with
      normal status. The write ceiling stands; the double-fetch does not lift it here.