pyGdbToolkit 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyGdbToolkit/__init__.py +55 -0
- pyGdbToolkit/arch/__init__.py +79 -0
- pyGdbToolkit/arch/arm/__init__.py +17 -0
- pyGdbToolkit/arch/arm/coresight.py +427 -0
- pyGdbToolkit/arch/arm/cortex_m.py +504 -0
- pyGdbToolkit/arch/arm/fault.py +629 -0
- pyGdbToolkit/arch/arm/models.py +60 -0
- pyGdbToolkit/arch/arm/mpu.py +556 -0
- pyGdbToolkit/arch/arm/probe.py +10 -0
- pyGdbToolkit/arch/arm/providers/__init__.py +16 -0
- pyGdbToolkit/arch/arm/providers/base.py +77 -0
- pyGdbToolkit/arch/arm/providers/manufacturers/__init__.py +4 -0
- pyGdbToolkit/arch/arm/providers/manufacturers/stm32.py +680 -0
- pyGdbToolkit/arch/arm/sau.py +253 -0
- pyGdbToolkit/arch/arm/security.py +816 -0
- pyGdbToolkit/arch/arm/session_state.py +96 -0
- pyGdbToolkit/arch/arm/target.py +32 -0
- pyGdbToolkit/arch/base.py +143 -0
- pyGdbToolkit/arch/diagnostics.py +300 -0
- pyGdbToolkit/arch/registry.py +43 -0
- pyGdbToolkit/cmd_faultinfo.py +99 -0
- pyGdbToolkit/cmd_lscpu.py +116 -0
- pyGdbToolkit/cmd_profile.py +1402 -0
- pyGdbToolkit/cmd_rtos.py +298 -0
- pyGdbToolkit/cmd_secscan.py +466 -0
- pyGdbToolkit/cmd_svd.py +1208 -0
- pyGdbToolkit/diagnostic_runtime.py +52 -0
- pyGdbToolkit/rtos/__init__.py +8 -0
- pyGdbToolkit/rtos/camelot.py +513 -0
- pyGdbToolkit/session.py +233 -0
- pyGdbToolkit/svd.py +1194 -0
- pyGdbToolkit/target_memory.py +190 -0
- pygdbtoolkit-0.1.0.dist-info/METADATA +586 -0
- pygdbtoolkit-0.1.0.dist-info/RECORD +36 -0
- pygdbtoolkit-0.1.0.dist-info/WHEEL +5 -0
- pygdbtoolkit-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 H2Lab Development Team
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Cortex-M PMSAv7 and PMSAv8 Memory Protection Unit inspection."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import IntEnum, StrEnum, unique
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from ...target_memory import TargetReadError, TargetWriteError, WritableTargetMemory
|
|
13
|
+
from ..base import RegisterValue
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from .cortex_m import CortexMTargetDescription
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
MPU_BASE_ADDRESS = 0xE000ED90
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@unique
|
|
23
|
+
class MpuArchitecture(StrEnum):
|
|
24
|
+
"""Protected Memory System Architecture versions supported by this module."""
|
|
25
|
+
|
|
26
|
+
PMSA_V7 = "PMSAv7"
|
|
27
|
+
PMSA_V8 = "PMSAv8"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@unique
|
|
31
|
+
class MpuCommonRegister(IntEnum):
|
|
32
|
+
"""MPU register offsets shared by PMSAv7 and PMSAv8."""
|
|
33
|
+
|
|
34
|
+
TYPE = 0x000
|
|
35
|
+
CTRL = 0x004
|
|
36
|
+
RNR = 0x008
|
|
37
|
+
RBAR = 0x00C
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@unique
|
|
41
|
+
class MpuV7Register(IntEnum):
|
|
42
|
+
"""PMSAv7-specific MPU register offsets."""
|
|
43
|
+
|
|
44
|
+
RASR = 0x010
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@unique
|
|
48
|
+
class MpuV8Register(IntEnum):
|
|
49
|
+
"""PMSAv8-specific MPU register offsets."""
|
|
50
|
+
|
|
51
|
+
RLAR = 0x010
|
|
52
|
+
RBAR_A1 = 0x014
|
|
53
|
+
RLAR_A1 = 0x018
|
|
54
|
+
RBAR_A2 = 0x01C
|
|
55
|
+
RLAR_A2 = 0x020
|
|
56
|
+
RBAR_A3 = 0x024
|
|
57
|
+
RLAR_A3 = 0x028
|
|
58
|
+
MAIR0 = 0x030
|
|
59
|
+
MAIR1 = 0x034
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@unique
|
|
63
|
+
class MpuAccessPermission(StrEnum):
|
|
64
|
+
"""Normalized MPU access permissions."""
|
|
65
|
+
|
|
66
|
+
NO_ACCESS = "no-access"
|
|
67
|
+
PRIVILEGED_READ_WRITE = "privileged-read-write"
|
|
68
|
+
PRIVILEGED_READ_WRITE_USER_READ = "privileged-read-write-user-read"
|
|
69
|
+
READ_WRITE = "read-write"
|
|
70
|
+
PRIVILEGED_READ_ONLY = "privileged-read-only"
|
|
71
|
+
READ_ONLY = "read-only"
|
|
72
|
+
RESERVED = "reserved"
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@unique
|
|
76
|
+
class MpuMemoryType(StrEnum):
|
|
77
|
+
"""Memory types represented by PMSA MPU attributes."""
|
|
78
|
+
|
|
79
|
+
STRONGLY_ORDERED = "strongly-ordered"
|
|
80
|
+
DEVICE = "device"
|
|
81
|
+
NORMAL = "normal"
|
|
82
|
+
UNKNOWN = "unknown"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
|
+
class MpuDescription:
|
|
87
|
+
"""Static MPU interface description for one Cortex-M core."""
|
|
88
|
+
|
|
89
|
+
architecture: MpuArchitecture
|
|
90
|
+
registers: tuple[MpuCommonRegister | MpuV7Register | MpuV8Register, ...]
|
|
91
|
+
supports_pxn: bool
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True)
|
|
95
|
+
class MpuMemoryAttributes:
|
|
96
|
+
"""Decoded memory-type and cacheability attributes for one MPU region."""
|
|
97
|
+
|
|
98
|
+
memory_type: MpuMemoryType
|
|
99
|
+
shareable: bool | None
|
|
100
|
+
cacheable: bool | None
|
|
101
|
+
bufferable: bool | None
|
|
102
|
+
attribute_index: int | None
|
|
103
|
+
raw: int
|
|
104
|
+
description: str
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass(frozen=True)
|
|
108
|
+
class MpuRegion:
|
|
109
|
+
"""One decoded MPU region configuration."""
|
|
110
|
+
|
|
111
|
+
index: int
|
|
112
|
+
enabled: bool
|
|
113
|
+
start_address: int
|
|
114
|
+
end_address: int | None
|
|
115
|
+
size_bytes: int | None
|
|
116
|
+
access: MpuAccessPermission
|
|
117
|
+
privileged_executable: bool
|
|
118
|
+
unprivileged_executable: bool
|
|
119
|
+
attributes: MpuMemoryAttributes
|
|
120
|
+
subregion_disable_mask: int | None
|
|
121
|
+
raw_rbar: int
|
|
122
|
+
raw_attributes: int
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def executable(self) -> bool:
|
|
126
|
+
"""Whether either privileged or unprivileged code can execute the region."""
|
|
127
|
+
return self.privileged_executable or self.unprivileged_executable
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(frozen=True)
|
|
131
|
+
class MpuRegionResult:
|
|
132
|
+
"""A decoded MPU region or its explicit selection/read failure."""
|
|
133
|
+
|
|
134
|
+
index: int
|
|
135
|
+
region: MpuRegion | None = None
|
|
136
|
+
unavailable_reason: str | None = None
|
|
137
|
+
|
|
138
|
+
def __post_init__(self) -> None:
|
|
139
|
+
"""Require exactly one region result state."""
|
|
140
|
+
if (self.region is None) == (self.unavailable_reason is None):
|
|
141
|
+
raise ValueError("an MPU region result must be available or unavailable")
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def is_available(self) -> bool:
|
|
145
|
+
"""Whether the selected region was read and decoded."""
|
|
146
|
+
return self.region is not None
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def unavailable(cls, index: int, reason: str) -> MpuRegionResult:
|
|
150
|
+
"""Create an unavailable region result."""
|
|
151
|
+
return cls(index=index, unavailable_reason=reason)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@dataclass(frozen=True)
|
|
155
|
+
class MpuDump:
|
|
156
|
+
"""MPU configuration and decoded region results for one Cortex-M target."""
|
|
157
|
+
|
|
158
|
+
description: MpuDescription | None
|
|
159
|
+
registers: tuple[RegisterValue, ...]
|
|
160
|
+
region_count: int | None
|
|
161
|
+
regions: tuple[MpuRegionResult, ...]
|
|
162
|
+
unavailable_reason: str | None = None
|
|
163
|
+
restore_error: str | None = None
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def is_available(self) -> bool:
|
|
167
|
+
"""Whether the target's MPU configuration was inspected."""
|
|
168
|
+
return self.unavailable_reason is None
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def unavailable(
|
|
172
|
+
cls,
|
|
173
|
+
description: MpuDescription | None,
|
|
174
|
+
reason: str,
|
|
175
|
+
registers: tuple[RegisterValue, ...] = (),
|
|
176
|
+
) -> MpuDump:
|
|
177
|
+
"""Create an explicit unavailable MPU inspection result."""
|
|
178
|
+
return cls(
|
|
179
|
+
description=description,
|
|
180
|
+
registers=registers,
|
|
181
|
+
region_count=None,
|
|
182
|
+
regions=(),
|
|
183
|
+
unavailable_reason=reason,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def get_register(self, name: str) -> RegisterValue | None:
|
|
187
|
+
"""Find a captured MPU register by case-insensitive name."""
|
|
188
|
+
normalized_name = name.upper()
|
|
189
|
+
for register in self.registers:
|
|
190
|
+
if register.name.upper() == normalized_name:
|
|
191
|
+
return register
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
PMSA_V7_MPU = MpuDescription(
|
|
196
|
+
MpuArchitecture.PMSA_V7,
|
|
197
|
+
(
|
|
198
|
+
MpuCommonRegister.TYPE,
|
|
199
|
+
MpuCommonRegister.CTRL,
|
|
200
|
+
MpuCommonRegister.RNR,
|
|
201
|
+
MpuCommonRegister.RBAR,
|
|
202
|
+
MpuV7Register.RASR,
|
|
203
|
+
),
|
|
204
|
+
False,
|
|
205
|
+
)
|
|
206
|
+
PMSA_V8_MPU = MpuDescription(
|
|
207
|
+
MpuArchitecture.PMSA_V8,
|
|
208
|
+
(
|
|
209
|
+
MpuCommonRegister.TYPE,
|
|
210
|
+
MpuCommonRegister.CTRL,
|
|
211
|
+
MpuCommonRegister.RNR,
|
|
212
|
+
MpuCommonRegister.RBAR,
|
|
213
|
+
MpuV8Register.RLAR,
|
|
214
|
+
MpuV8Register.RBAR_A1,
|
|
215
|
+
MpuV8Register.RLAR_A1,
|
|
216
|
+
MpuV8Register.RBAR_A2,
|
|
217
|
+
MpuV8Register.RLAR_A2,
|
|
218
|
+
MpuV8Register.RBAR_A3,
|
|
219
|
+
MpuV8Register.RLAR_A3,
|
|
220
|
+
MpuV8Register.MAIR0,
|
|
221
|
+
MpuV8Register.MAIR1,
|
|
222
|
+
),
|
|
223
|
+
False,
|
|
224
|
+
)
|
|
225
|
+
PMSA_V8_PXN_MPU = MpuDescription(
|
|
226
|
+
MpuArchitecture.PMSA_V8,
|
|
227
|
+
(
|
|
228
|
+
MpuCommonRegister.TYPE,
|
|
229
|
+
MpuCommonRegister.CTRL,
|
|
230
|
+
MpuCommonRegister.RNR,
|
|
231
|
+
MpuCommonRegister.RBAR,
|
|
232
|
+
MpuV8Register.RLAR,
|
|
233
|
+
MpuV8Register.RBAR_A1,
|
|
234
|
+
MpuV8Register.RLAR_A1,
|
|
235
|
+
MpuV8Register.RBAR_A2,
|
|
236
|
+
MpuV8Register.RLAR_A2,
|
|
237
|
+
MpuV8Register.RBAR_A3,
|
|
238
|
+
MpuV8Register.RLAR_A3,
|
|
239
|
+
MpuV8Register.MAIR0,
|
|
240
|
+
MpuV8Register.MAIR1,
|
|
241
|
+
),
|
|
242
|
+
True,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def dump_mpu_regions(reader: WritableTargetMemory, target: CortexMTargetDescription) -> MpuDump:
|
|
247
|
+
"""Read and decode MPU region configuration for a known Cortex-M core.
|
|
248
|
+
|
|
249
|
+
The function writes each region index to ``MPU_RNR`` and restores the
|
|
250
|
+
target's original selector value before returning. Region read failures are
|
|
251
|
+
retained per region; a selector restoration failure is retained in
|
|
252
|
+
:attr:`MpuDump.restore_error`.
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
reader
|
|
257
|
+
Target-memory reader and writer used for MPU registers.
|
|
258
|
+
target
|
|
259
|
+
Known Cortex-M target description returned by :func:`decode_cpuid`.
|
|
260
|
+
|
|
261
|
+
Returns
|
|
262
|
+
-------
|
|
263
|
+
MpuDump
|
|
264
|
+
MPU register values, decoded regions, or an explicit unavailable reason.
|
|
265
|
+
"""
|
|
266
|
+
description = target.core.mpu if target.core is not None else None
|
|
267
|
+
if description is None:
|
|
268
|
+
return MpuDump.unavailable(
|
|
269
|
+
None,
|
|
270
|
+
"this Cortex-M core has no PMSAv7 or PMSAv8 MPU description",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
registers: list[RegisterValue] = []
|
|
274
|
+
mpu_type = _read_register(reader, MpuCommonRegister.TYPE)
|
|
275
|
+
registers.append(mpu_type)
|
|
276
|
+
if not mpu_type.is_available:
|
|
277
|
+
assert mpu_type.unavailable_reason is not None
|
|
278
|
+
return MpuDump.unavailable(description, mpu_type.unavailable_reason, tuple(registers))
|
|
279
|
+
|
|
280
|
+
assert mpu_type.value is not None
|
|
281
|
+
region_count = (mpu_type.value >> 8) & 0xFF
|
|
282
|
+
mpu_control = _read_register(reader, MpuCommonRegister.CTRL)
|
|
283
|
+
registers.append(mpu_control)
|
|
284
|
+
if region_count == 0:
|
|
285
|
+
return MpuDump(description, tuple(registers), 0, ())
|
|
286
|
+
|
|
287
|
+
mair_values: tuple[RegisterValue, RegisterValue] | None = None
|
|
288
|
+
if description.architecture is MpuArchitecture.PMSA_V8:
|
|
289
|
+
mair0 = _read_register(reader, MpuV8Register.MAIR0)
|
|
290
|
+
mair1 = _read_register(reader, MpuV8Register.MAIR1)
|
|
291
|
+
registers.extend((mair0, mair1))
|
|
292
|
+
mair_values = (mair0, mair1)
|
|
293
|
+
|
|
294
|
+
original_rnr = _read_register(reader, MpuCommonRegister.RNR)
|
|
295
|
+
registers.append(original_rnr)
|
|
296
|
+
if not original_rnr.is_available:
|
|
297
|
+
assert original_rnr.unavailable_reason is not None
|
|
298
|
+
return MpuDump.unavailable(description, original_rnr.unavailable_reason, tuple(registers))
|
|
299
|
+
|
|
300
|
+
assert original_rnr.value is not None
|
|
301
|
+
regions: list[MpuRegionResult] = []
|
|
302
|
+
restore_error: str | None = None
|
|
303
|
+
try:
|
|
304
|
+
for index in range(region_count):
|
|
305
|
+
try:
|
|
306
|
+
reader.write_uint32(MPU_BASE_ADDRESS + MpuCommonRegister.RNR, index)
|
|
307
|
+
except TargetWriteError as error:
|
|
308
|
+
regions.append(MpuRegionResult.unavailable(index, str(error)))
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
regions.append(_read_region(reader, description, index, mair_values))
|
|
312
|
+
finally:
|
|
313
|
+
try:
|
|
314
|
+
reader.write_uint32(MPU_BASE_ADDRESS + MpuCommonRegister.RNR, original_rnr.value)
|
|
315
|
+
except TargetWriteError as error:
|
|
316
|
+
restore_error = str(error)
|
|
317
|
+
|
|
318
|
+
return MpuDump(
|
|
319
|
+
description,
|
|
320
|
+
tuple(registers),
|
|
321
|
+
region_count,
|
|
322
|
+
tuple(regions),
|
|
323
|
+
restore_error=restore_error,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _read_register(
|
|
328
|
+
reader: WritableTargetMemory,
|
|
329
|
+
register: MpuCommonRegister | MpuV7Register | MpuV8Register,
|
|
330
|
+
) -> RegisterValue:
|
|
331
|
+
"""Read one MPU register while preserving a target access failure."""
|
|
332
|
+
address = MPU_BASE_ADDRESS + register.value
|
|
333
|
+
try:
|
|
334
|
+
value = reader.read_uint32(address)
|
|
335
|
+
except TargetReadError as error:
|
|
336
|
+
return RegisterValue.unavailable(register.name, address, 32, str(error))
|
|
337
|
+
return RegisterValue.known(register.name, address, 32, value)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _read_region(
|
|
341
|
+
reader: WritableTargetMemory,
|
|
342
|
+
description: MpuDescription,
|
|
343
|
+
index: int,
|
|
344
|
+
mair_values: tuple[RegisterValue, RegisterValue] | None,
|
|
345
|
+
) -> MpuRegionResult:
|
|
346
|
+
"""Read and decode the currently selected MPU region."""
|
|
347
|
+
try:
|
|
348
|
+
rbar = reader.read_uint32(MPU_BASE_ADDRESS + MpuCommonRegister.RBAR)
|
|
349
|
+
attributes_offset = (
|
|
350
|
+
MpuV7Register.RASR
|
|
351
|
+
if description.architecture is MpuArchitecture.PMSA_V7
|
|
352
|
+
else MpuV8Register.RLAR
|
|
353
|
+
)
|
|
354
|
+
raw_attributes = reader.read_uint32(MPU_BASE_ADDRESS + attributes_offset)
|
|
355
|
+
except TargetReadError as error:
|
|
356
|
+
return MpuRegionResult.unavailable(index, str(error))
|
|
357
|
+
|
|
358
|
+
if description.architecture is MpuArchitecture.PMSA_V7:
|
|
359
|
+
return MpuRegionResult(
|
|
360
|
+
index=index,
|
|
361
|
+
region=_decode_pmsa_v7_region(index, rbar, raw_attributes),
|
|
362
|
+
)
|
|
363
|
+
assert mair_values is not None
|
|
364
|
+
return MpuRegionResult(
|
|
365
|
+
index=index,
|
|
366
|
+
region=_decode_pmsa_v8_region(description, index, rbar, raw_attributes, mair_values),
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _decode_pmsa_v7_region(index: int, rbar: int, rasr: int) -> MpuRegion:
|
|
371
|
+
"""Decode a PMSAv7 ``RBAR``/``RASR`` region pair."""
|
|
372
|
+
size_encoding = (rasr >> 1) & 0x1F
|
|
373
|
+
size_bytes = 1 << (size_encoding + 1) if size_encoding >= 4 else None
|
|
374
|
+
start_address = rbar & 0xFFFFFFE0
|
|
375
|
+
end_address = start_address + size_bytes - 1 if size_bytes is not None else None
|
|
376
|
+
ap = (rasr >> 24) & 0x7
|
|
377
|
+
tex = (rasr >> 19) & 0x7
|
|
378
|
+
shareable = bool(rasr & (1 << 18))
|
|
379
|
+
cacheable = bool(rasr & (1 << 17))
|
|
380
|
+
bufferable = bool(rasr & (1 << 16))
|
|
381
|
+
execute_never = bool(rasr & (1 << 28))
|
|
382
|
+
return MpuRegion(
|
|
383
|
+
index=index,
|
|
384
|
+
enabled=bool(rasr & 0x1),
|
|
385
|
+
start_address=start_address,
|
|
386
|
+
end_address=end_address,
|
|
387
|
+
size_bytes=size_bytes,
|
|
388
|
+
access=_decode_pmsa_v7_access(ap),
|
|
389
|
+
privileged_executable=not execute_never,
|
|
390
|
+
unprivileged_executable=not execute_never,
|
|
391
|
+
attributes=_decode_pmsa_v7_attributes(tex, shareable, cacheable, bufferable, rasr),
|
|
392
|
+
subregion_disable_mask=(rasr >> 8) & 0xFF,
|
|
393
|
+
raw_rbar=rbar,
|
|
394
|
+
raw_attributes=rasr,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _decode_pmsa_v8_region(
|
|
399
|
+
description: MpuDescription,
|
|
400
|
+
index: int,
|
|
401
|
+
rbar: int,
|
|
402
|
+
rlar: int,
|
|
403
|
+
mair_values: tuple[RegisterValue, RegisterValue],
|
|
404
|
+
) -> MpuRegion:
|
|
405
|
+
"""Decode a PMSAv8 ``RBAR``/``RLAR`` region pair."""
|
|
406
|
+
attribute_index = (rlar >> 1) & 0x7
|
|
407
|
+
start_address = rbar & 0xFFFFFFE0
|
|
408
|
+
end_address = (rlar & 0xFFFFFFE0) | 0x1F
|
|
409
|
+
size_bytes = end_address - start_address + 1 if end_address >= start_address else None
|
|
410
|
+
execute_never = bool(rbar & 0x1)
|
|
411
|
+
privileged_execute_never = description.supports_pxn and bool(rlar & (1 << 4))
|
|
412
|
+
return MpuRegion(
|
|
413
|
+
index=index,
|
|
414
|
+
enabled=bool(rlar & 0x1),
|
|
415
|
+
start_address=start_address,
|
|
416
|
+
end_address=end_address,
|
|
417
|
+
size_bytes=size_bytes,
|
|
418
|
+
access=_decode_pmsa_v8_access((rbar >> 1) & 0x3),
|
|
419
|
+
privileged_executable=not (execute_never or privileged_execute_never),
|
|
420
|
+
unprivileged_executable=not execute_never,
|
|
421
|
+
attributes=_decode_pmsa_v8_attributes(
|
|
422
|
+
attribute_index,
|
|
423
|
+
((rbar >> 3) & 0x3) in (0b10, 0b11),
|
|
424
|
+
mair_values,
|
|
425
|
+
),
|
|
426
|
+
subregion_disable_mask=None,
|
|
427
|
+
raw_rbar=rbar,
|
|
428
|
+
raw_attributes=rlar,
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _decode_pmsa_v7_access(ap: int) -> MpuAccessPermission:
|
|
433
|
+
"""Decode PMSAv7 three-bit access permission fields."""
|
|
434
|
+
permissions = {
|
|
435
|
+
0b000: MpuAccessPermission.NO_ACCESS,
|
|
436
|
+
0b001: MpuAccessPermission.PRIVILEGED_READ_WRITE,
|
|
437
|
+
0b010: MpuAccessPermission.PRIVILEGED_READ_WRITE_USER_READ,
|
|
438
|
+
0b011: MpuAccessPermission.READ_WRITE,
|
|
439
|
+
0b101: MpuAccessPermission.PRIVILEGED_READ_ONLY,
|
|
440
|
+
0b110: MpuAccessPermission.READ_ONLY,
|
|
441
|
+
}
|
|
442
|
+
return permissions.get(ap, MpuAccessPermission.RESERVED)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _decode_pmsa_v8_access(ap: int) -> MpuAccessPermission:
|
|
446
|
+
"""Decode PMSAv8 two-bit access permission fields."""
|
|
447
|
+
permissions = {
|
|
448
|
+
0b00: MpuAccessPermission.PRIVILEGED_READ_WRITE,
|
|
449
|
+
0b01: MpuAccessPermission.READ_WRITE,
|
|
450
|
+
0b10: MpuAccessPermission.PRIVILEGED_READ_ONLY,
|
|
451
|
+
0b11: MpuAccessPermission.READ_ONLY,
|
|
452
|
+
}
|
|
453
|
+
return permissions[ap]
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _decode_pmsa_v7_attributes(
|
|
457
|
+
tex: int,
|
|
458
|
+
shareable: bool,
|
|
459
|
+
cacheable: bool,
|
|
460
|
+
bufferable: bool,
|
|
461
|
+
rasr: int,
|
|
462
|
+
) -> MpuMemoryAttributes:
|
|
463
|
+
"""Decode the PMSAv7 TEX/S/C/B memory attribute fields."""
|
|
464
|
+
if tex == 0 and not cacheable and not bufferable:
|
|
465
|
+
return MpuMemoryAttributes(
|
|
466
|
+
MpuMemoryType.STRONGLY_ORDERED,
|
|
467
|
+
True,
|
|
468
|
+
False,
|
|
469
|
+
False,
|
|
470
|
+
None,
|
|
471
|
+
rasr,
|
|
472
|
+
"strongly ordered",
|
|
473
|
+
)
|
|
474
|
+
if (tex == 0 and not cacheable and bufferable) or (tex == 2 and not cacheable):
|
|
475
|
+
return MpuMemoryAttributes(
|
|
476
|
+
MpuMemoryType.DEVICE,
|
|
477
|
+
shareable,
|
|
478
|
+
False,
|
|
479
|
+
bufferable,
|
|
480
|
+
None,
|
|
481
|
+
rasr,
|
|
482
|
+
"device memory",
|
|
483
|
+
)
|
|
484
|
+
if tex in (0, 1) or tex >= 4:
|
|
485
|
+
return MpuMemoryAttributes(
|
|
486
|
+
MpuMemoryType.NORMAL,
|
|
487
|
+
shareable,
|
|
488
|
+
cacheable,
|
|
489
|
+
bufferable,
|
|
490
|
+
None,
|
|
491
|
+
rasr,
|
|
492
|
+
"normal memory",
|
|
493
|
+
)
|
|
494
|
+
return MpuMemoryAttributes(
|
|
495
|
+
MpuMemoryType.UNKNOWN,
|
|
496
|
+
shareable,
|
|
497
|
+
cacheable,
|
|
498
|
+
bufferable,
|
|
499
|
+
None,
|
|
500
|
+
rasr,
|
|
501
|
+
"reserved PMSAv7 TEX/S/C/B encoding",
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _decode_pmsa_v8_attributes(
|
|
506
|
+
attribute_index: int,
|
|
507
|
+
shareable: bool,
|
|
508
|
+
mair_values: tuple[RegisterValue, RegisterValue],
|
|
509
|
+
) -> MpuMemoryAttributes:
|
|
510
|
+
"""Resolve a PMSAv8 region attribute index through MAIR."""
|
|
511
|
+
mair_register = mair_values[attribute_index // 4]
|
|
512
|
+
if not mair_register.is_available:
|
|
513
|
+
assert mair_register.unavailable_reason is not None
|
|
514
|
+
return MpuMemoryAttributes(
|
|
515
|
+
MpuMemoryType.UNKNOWN,
|
|
516
|
+
shareable,
|
|
517
|
+
None,
|
|
518
|
+
None,
|
|
519
|
+
attribute_index,
|
|
520
|
+
0,
|
|
521
|
+
f"{mair_register.name} unavailable: {mair_register.unavailable_reason}",
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
assert mair_register.value is not None
|
|
525
|
+
attribute = (mair_register.value >> ((attribute_index % 4) * 8)) & 0xFF
|
|
526
|
+
outer = attribute >> 4
|
|
527
|
+
inner = attribute & 0xF
|
|
528
|
+
if outer == 0 and inner in (0x0, 0x4, 0x8, 0xC):
|
|
529
|
+
return MpuMemoryAttributes(
|
|
530
|
+
MpuMemoryType.DEVICE,
|
|
531
|
+
shareable,
|
|
532
|
+
False,
|
|
533
|
+
bool(inner & 0x4),
|
|
534
|
+
attribute_index,
|
|
535
|
+
attribute,
|
|
536
|
+
"device memory",
|
|
537
|
+
)
|
|
538
|
+
if outer != 0:
|
|
539
|
+
return MpuMemoryAttributes(
|
|
540
|
+
MpuMemoryType.NORMAL,
|
|
541
|
+
shareable,
|
|
542
|
+
outer != 0x4 or inner != 0x4,
|
|
543
|
+
None,
|
|
544
|
+
attribute_index,
|
|
545
|
+
attribute,
|
|
546
|
+
"normal memory",
|
|
547
|
+
)
|
|
548
|
+
return MpuMemoryAttributes(
|
|
549
|
+
MpuMemoryType.UNKNOWN,
|
|
550
|
+
shareable,
|
|
551
|
+
None,
|
|
552
|
+
None,
|
|
553
|
+
attribute_index,
|
|
554
|
+
attribute,
|
|
555
|
+
"reserved PMSAv8 MAIR attribute",
|
|
556
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 H2Lab Development Team
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Arm architecture probe entry points."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .cortex_m import CortexMProbe
|
|
9
|
+
|
|
10
|
+
DEFAULT_ARM_PROBES = (CortexMProbe(),)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 H2Lab Development Team
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Arm device-provider registry and manufacturer implementations."""
|
|
5
|
+
|
|
6
|
+
from .base import DeviceProvider, ProviderRegistry
|
|
7
|
+
from .manufacturers.stm32 import Stm32Provider
|
|
8
|
+
|
|
9
|
+
DEFAULT_PROVIDER_REGISTRY = ProviderRegistry((Stm32Provider(),))
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"DEFAULT_PROVIDER_REGISTRY",
|
|
13
|
+
"DeviceProvider",
|
|
14
|
+
"ProviderRegistry",
|
|
15
|
+
"Stm32Provider",
|
|
16
|
+
]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2026 H2Lab Development Team
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Manufacturer-agnostic Arm device-provider contracts and dispatch."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Protocol
|
|
9
|
+
|
|
10
|
+
from ..coresight import CoreSightDiscovery
|
|
11
|
+
from ..cortex_m import CortexMTargetDescription
|
|
12
|
+
from ..models import DeviceReport, FieldValue
|
|
13
|
+
from ....target_memory import TargetMemory
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DeviceProvider(Protocol):
|
|
17
|
+
"""A manufacturer-specific device recognizer."""
|
|
18
|
+
|
|
19
|
+
def inspect(
|
|
20
|
+
self,
|
|
21
|
+
reader: TargetMemory,
|
|
22
|
+
target: CortexMTargetDescription,
|
|
23
|
+
discovery: CoreSightDiscovery,
|
|
24
|
+
) -> DeviceReport | None:
|
|
25
|
+
"""Return a report if the provider recognizes the target."""
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ProviderRegistry:
|
|
30
|
+
"""Apply device providers in a defined order with a generic fallback."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, providers: tuple[DeviceProvider, ...]) -> None:
|
|
33
|
+
"""Create a registry from providers evaluated in tuple order."""
|
|
34
|
+
self._providers = providers
|
|
35
|
+
|
|
36
|
+
def inspect(
|
|
37
|
+
self,
|
|
38
|
+
reader: TargetMemory,
|
|
39
|
+
target: CortexMTargetDescription,
|
|
40
|
+
discovery: CoreSightDiscovery,
|
|
41
|
+
) -> DeviceReport:
|
|
42
|
+
"""Return the first matching provider report or a generic fallback."""
|
|
43
|
+
for provider in self._providers:
|
|
44
|
+
report = provider.inspect(reader, target, discovery)
|
|
45
|
+
if report is not None:
|
|
46
|
+
return report
|
|
47
|
+
return _generic_report(target, discovery)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _generic_report(
|
|
51
|
+
target: CortexMTargetDescription,
|
|
52
|
+
discovery: CoreSightDiscovery,
|
|
53
|
+
) -> DeviceReport:
|
|
54
|
+
"""Return a useful report when no registered manufacturer matched."""
|
|
55
|
+
if discovery.mcu_rom.table is None:
|
|
56
|
+
assert discovery.mcu_rom.unavailable_reason is not None
|
|
57
|
+
reason = f"MCU-ROM identity unavailable: {discovery.mcu_rom.unavailable_reason}"
|
|
58
|
+
else:
|
|
59
|
+
peripheral_id = discovery.mcu_rom.table.identity.peripheral_id
|
|
60
|
+
if peripheral_id.jep106 is None:
|
|
61
|
+
reason = "MCU-ROM root does not advertise a JEDEC manufacturer identity"
|
|
62
|
+
else:
|
|
63
|
+
reason = (
|
|
64
|
+
"no registered vendor profile matches MCU-ROM "
|
|
65
|
+
f"{peripheral_id.jep106.display()}, part 0x{peripheral_id.part_number:03X}"
|
|
66
|
+
)
|
|
67
|
+
return DeviceReport(
|
|
68
|
+
target=target,
|
|
69
|
+
discovery=discovery,
|
|
70
|
+
vendor="Generic Cortex-M",
|
|
71
|
+
product_line=FieldValue.unavailable(reason),
|
|
72
|
+
part_number=FieldValue.unavailable(reason),
|
|
73
|
+
ram=FieldValue.unavailable(reason),
|
|
74
|
+
flash=FieldValue.unavailable(reason),
|
|
75
|
+
package=FieldValue.unavailable(reason),
|
|
76
|
+
serial_number=FieldValue.unavailable(reason),
|
|
77
|
+
)
|