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
|
"""WPR2 / FWSEC state registers.
Evidence kind: **hardware register/field header + firmware use** (the boot
flow locates the firmware write-protected region from these registers on
pre-Hopper parts; the host/booter writes them, the firmware reads them).
Modelled registers (offsets in the FB/PFB register aperture):
* ``NV_PFB_PRI_MMU_WPR2_ADDR_LO`` ``0x1FA824``: WPR2 start, bits ``31:4``
shifted by ``NV_PFB_PRI_MMU_WPR2_ADDR_LO_ALIGNMENT = 12`` (4 KiB).
* ``NV_PFB_PRI_MMU_WPR2_ADDR_HI`` ``0x1FA828``: points at the start of the
last 128 KiB block of the WPR, same alignment; the end is
``(value << 12) + 0x20000``.
A profile that wants the firmware to see a WPR declares ``wpr2_start`` /
``wpr2_end``; without a declaration the window is unhandled and logged.
FWSEC-specific state is not modelled (no proven register use yet).
"""
from __future__ import annotations
from .base import Device
WPR2_ADDR_LO = 0x1FA824
WPR2_ADDR_HI = 0x1FA828
WPR2_ALIGNMENT = 12
WPR2_BLOCK = 0x20000
#: Block-relative offsets (a profile window that starts at ``+0x1FA000``
#: reaches the pair at ``0x824``/``0x828``; the device accepts both forms).
WPR2_ADDR_LO_REL = WPR2_ADDR_LO & 0xFFF
WPR2_ADDR_HI_REL = WPR2_ADDR_HI & 0xFFF
_KEYS = {
WPR2_ADDR_LO: WPR2_ADDR_LO,
WPR2_ADDR_HI: WPR2_ADDR_HI,
WPR2_ADDR_LO_REL: WPR2_ADDR_LO,
WPR2_ADDR_HI_REL: WPR2_ADDR_HI,
}
class Wpr2Block(Device):
"""WPR2 address pair as seen by the firmware (host/booter-written)."""
name = "fwsec"
def __init__(self, *, base: int = 0, size: int = 0x1000,
wpr2_start: int | None = None, wpr2_end: int | None = None):
super().__init__(base=base, size=size)
self.reset()
if wpr2_start is not None and wpr2_end is not None:
self.set_range(wpr2_start, wpr2_end)
def set_range(self, start: int, end: int) -> None:
if end <= start:
raise ValueError("WPR2 end must be greater than start")
self.regs[WPR2_ADDR_LO] = (start >> WPR2_ALIGNMENT) & 0xFFFFFFFF
self.regs[WPR2_ADDR_HI] = ((end - WPR2_BLOCK) >> WPR2_ALIGNMENT) & 0xFFFFFFFF
@property
def start(self) -> int | None:
"""WPR2 start derived from ADDR_LO (``None`` when unset)."""
lo = self.regs.get(WPR2_ADDR_LO)
return None if lo is None else lo << WPR2_ALIGNMENT
@property
def end(self) -> int | None:
"""WPR2 end derived from ADDR_HI (``None`` when unset)."""
hi = self.regs.get(WPR2_ADDR_HI)
return None if hi is None else (hi << WPR2_ALIGNMENT) + WPR2_BLOCK
def reset(self) -> None:
self.regs: dict[int, int] = {}
def read(self, offset: int, size: int) -> int | None:
key = _KEYS.get(offset)
if size == 4 and key is not None and key in self.regs:
return self.regs[key]
return None
def write(self, offset: int, size: int, value: int) -> bool:
key = _KEYS.get(offset)
if size == 4 and key is not None:
self.regs[key] = value & 0xFFFFFFFF
return True
return False
def state(self) -> dict:
return {"regs": {str(k): v for k, v in sorted(self.regs.items())},
"start": self.start, "end": self.end}
def load_state(self, data: dict) -> None:
# start/end are derived from regs; serialised copies are ignored so a
# restored block can never report a stale range.
self.regs = {int(k): int(v) for k, v in data.get("regs", {}).items()}
|