aboutsummaryrefslogtreecommitdiffstats
path: root/f32/hw/sdma_ptepde_probe.py
blob: 5d71d1502902de94bacbc18d4a54dc9ee914aed8 (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
#!/usr/bin/env python3
"""Is SDMA's PTEPDE (page-table-entry write) privilege-gated like SRBM_WRITE?

`hw/sdma_srbm_probe.py` established [HW] that `SRBM_WRITE` runs only with
`RB_CNTL.RB_PRIV = 1`. PTEPDE is the other packet that matters for isolation: it writes
page-table entries, so a queue that can issue it can forge translations.

Static reading (`../AUDIT-SDMA-01.md`): the PTEPDE handler has **no privilege check**.
It does consult `reg[0x5a6c]` bits 0 and 8, and an earlier draft of that document called
this "PTEPDE's gate" -- that was wrong and is corrected there: the branch does not
reject anything, it selects between two *implementations* (the bits-set path applies an
extra address/value fixup via `_DMA_DATA_6`, then both paths fall into the same PTE
write at `_PKT_0xc00_0`).

So the prediction is: PTEPDE executes regardless of RB_PRIV. This tests it.

Packet (SDMA_PKT_PTEPDE_GEN, matching sdma_v6_0_vm_set_pte_pde):
    header, pe_lo, pe_hi, mask_lo, mask_hi, value_lo, value_hi, incr, 0, count-1
which is exactly the order the handler pulls off r1 at 0x09b4..0x09c2.

Method mirrors the SRBM probe: bring the queue up privileged, then clear *only*
RB_PRIV on the live queue, because a queue configured unprivileged from scratch does
not fetch at all and would make "nothing happened" meaningless.

Writes land in a VRAM buffer this script allocates. Nothing else is touched.

usage: PYTHONPATH=/root/psp/tinygrad sdma_ptepde_probe.py --apply
"""
import argparse
import struct
import sys
import os

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from sdma_srbm_probe import Probe, SDMA_OP_NOP                    # noqa: E402

SDMA_OP_PTEPDE = 12
COUNT = 8


def ptepde_gen(pe_mc, value, incr, count):
    return [SDMA_OP_PTEPDE,
            pe_mc & 0xffffffff, (pe_mc >> 32) & 0xffffffff,
            0, 0,                                   # mask/flags
            value & 0xffffffff, (value >> 32) & 0xffffffff,
            incr, 0, count - 1]


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('--apply', action='store_true')
    args = ap.parse_args()
    if not args.apply:
        print('dry run: pass --apply')
        return 0

    from tinygrad import Device
    adev = Device["AMD"].iface.dev_impl
    p = Probe(adev)
    buf = adev.mm.palloc(0x1000, zero=True, boot=False)
    buf_mc = adev.paddr2mc(buf)
    print('target buffer paddr 0x%x mc 0x%x, %d entries of 8 bytes' % (buf, buf_mc, COUNT))

    results = {}
    for priv in (1, 0):
        print('\n=== RB_PRIV = %d' % priv)
        if priv == 1:
            p.setup(rb_priv=1)
        else:
            p.reg('_RB_CNTL').update(rb_priv=0)      # live flip, ring stays configured
        print('  RB_CNTL.rb_priv = %s' % p.reg('_RB_CNTL').read_bitfields().get('rb_priv'))

        value = 0xAAAA0000BBBB0000 if priv else 0xCCCC0000DDDD0000
        adev.vram.view(buf, 0x40)[:] = bytes(0x40)
        adev.gmc.flush_hdp()
        before = bytes(adev.vram.view(buf, 0x40)[:])

        ok, dt = p.submit([SDMA_OP_NOP])
        print('  control: NOP consumed=%s (%.1f ms)' % (ok, 1000 * dt))
        if not ok:
            print('  !! queue not running -- inconclusive for this setting')
            results[priv] = None
            continue

        ok2, dt2 = p.submit(ptepde_gen(buf_mc, value, 0x1000, COUNT))
        after = bytes(adev.vram.view(buf, 0x40)[:])
        wrote = after != before
        ents = struct.unpack('<8Q', after)
        print('  PTEPDE consumed=%s (%.1f ms) -> buffer %s' % (
            ok2, 1000 * dt2, 'WRITTEN' if wrote else 'unchanged'))
        for i, e in enumerate(ents[:4]):
            print('     entry[%d] = 0x%016x' % (i, e))
        results[priv] = wrote

    print('\n=== result')
    if results.get(1) is None or results.get(0) is None:
        print('  inconclusive: a control failed')
    elif results[1] and results[0]:
        print('  PTEPDE IS NOT PRIVILEGE-GATED: page-table entries were written with')
        print('  RB_PRIV=0, on the same queue where SRBM_WRITE is refused.')
        print('  => the two packets have different trust models in this firmware.')
    elif results[1] and not results[0]:
        print('  PTEPDE is gated after all -- the static reading (no privilege check in')
        print('  the handler) is wrong or the gate lives outside the F32 code path.')
    else:
        print('  PTEPDE did not write even with RB_PRIV=1: addressing/setup is wrong,')
        print('  no conclusion can be drawn.')
    return 0


if __name__ == '__main__':
    raise SystemExit(main())