tan-cli 0.7.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.
Files changed (330) hide show
  1. tan/__init__.py +0 -0
  2. tan/__main__.py +120 -0
  3. tan/cli.py +939 -0
  4. tan/command_table.py +265 -0
  5. tan/commands/__init__.py +8 -0
  6. tan/commands/bootstrap_cmd.py +4856 -0
  7. tan/commands/bootstrap_patches.py +164 -0
  8. tan/commands/bootstrap_toolchain_fetch.py +155 -0
  9. tan/commands/bootstrap_toolchain_pin.py +36 -0
  10. tan/commands/build/__init__.py +1 -0
  11. tan/commands/build/configure_inputs.py +277 -0
  12. tan/commands/build/execute.py +1852 -0
  13. tan/commands/build/host_python.py +59 -0
  14. tan/commands/build/link_stale.py +78 -0
  15. tan/commands/build/manifest.py +428 -0
  16. tan/commands/build/materialise.py +81 -0
  17. tan/commands/build/token_substitution.py +381 -0
  18. tan/commands/build/toolchain.py +482 -0
  19. tan/commands/build/user_defines_wipe.py +115 -0
  20. tan/commands/build/workspace_patches.py +65 -0
  21. tan/commands/build_cmd.py +2236 -0
  22. tan/commands/build_output.py +331 -0
  23. tan/commands/clean_cmd.py +1183 -0
  24. tan/commands/completion_cmd.py +786 -0
  25. tan/commands/debug_config_cmd.py +2425 -0
  26. tan/commands/diff_cmd.py +940 -0
  27. tan/commands/doctor_cmd.py +4767 -0
  28. tan/commands/examples_cmd.py +644 -0
  29. tan/commands/explain_cmd.py +1319 -0
  30. tan/commands/faultdecode_cmd.py +931 -0
  31. tan/commands/flash_atoc_guard.py +240 -0
  32. tan/commands/flash_cmd.py +5731 -0
  33. tan/commands/flash_mram_guard.py +150 -0
  34. tan/commands/flash_ram.py +578 -0
  35. tan/commands/flash_raw.py +583 -0
  36. tan/commands/generate_cmd.py +1456 -0
  37. tan/commands/image_cmd.py +753 -0
  38. tan/commands/init_cmd.py +1740 -0
  39. tan/commands/inspect_cmd.py +377 -0
  40. tan/commands/kconfig_cmd.py +734 -0
  41. tan/commands/model_cmd.py +1514 -0
  42. tan/commands/model_device_cmd.py +351 -0
  43. tan/commands/model_device_live.py +234 -0
  44. tan/commands/model_host_cmd.py +430 -0
  45. tan/commands/model_zoo_cmd.py +363 -0
  46. tan/commands/monitor_actions.py +125 -0
  47. tan/commands/monitor_cmd.py +626 -0
  48. tan/commands/monitor_session.py +505 -0
  49. tan/commands/new_som_cmd.py +1522 -0
  50. tan/commands/pinmux_cmd.py +568 -0
  51. tan/commands/presets_cmd.py +1123 -0
  52. tan/commands/probe_cmd.py +421 -0
  53. tan/commands/reset_cmd.py +354 -0
  54. tan/commands/reset_confirm.py +168 -0
  55. tan/commands/run_cmd.py +637 -0
  56. tan/commands/scaffold_cmd.py +490 -0
  57. tan/commands/sdk_cmd.py +1723 -0
  58. tan/commands/size_cmd.py +1020 -0
  59. tan/commands/support_bundle_cmd.py +1073 -0
  60. tan/commands/trace_cmd.py +341 -0
  61. tan/commands/validate_cmd.py +2009 -0
  62. tan/commands/west_forward_cmd.py +736 -0
  63. tan/commands/workspace_patch_check.py +296 -0
  64. tan/core/__init__.py +1 -0
  65. tan/core/alp_conf_pregen.py +100 -0
  66. tan/core/artifact_provenance.py +240 -0
  67. tan/core/atoc_guard.py +276 -0
  68. tan/core/atoc_guard_messages.py +232 -0
  69. tan/core/atoc_replacement.py +118 -0
  70. tan/core/atomic_write.py +178 -0
  71. tan/core/board_context.py +351 -0
  72. tan/core/board_diagnostic.py +111 -0
  73. tan/core/board_files.py +221 -0
  74. tan/core/board_validator.py +542 -0
  75. tan/core/board_validator_compat.py +196 -0
  76. tan/core/board_validator_run.py +129 -0
  77. tan/core/board_validator_schema.py +186 -0
  78. tan/core/board_validator_skew.py +46 -0
  79. tan/core/board_yaml_edit.py +295 -0
  80. tan/core/board_yaml_pos.py +118 -0
  81. tan/core/bootstrap.py +2572 -0
  82. tan/core/build_plan.py +625 -0
  83. tan/core/consent.py +104 -0
  84. tan/core/console_filter.py +245 -0
  85. tan/core/debug_launch.py +1803 -0
  86. tan/core/devicetree_lint.py +168 -0
  87. tan/core/dir_removal.py +229 -0
  88. tan/core/doctor_git.py +172 -0
  89. tan/core/doctor_libraries.py +253 -0
  90. tan/core/doctor_render.py +185 -0
  91. tan/core/doctor_scope.py +66 -0
  92. tan/core/doctor_setools.py +237 -0
  93. tan/core/doctor_stale.py +180 -0
  94. tan/core/document_guards.py +723 -0
  95. tan/core/dp_id.py +97 -0
  96. tan/core/elf_load.py +63 -0
  97. tan/core/error_catalog.py +171 -0
  98. tan/core/example_catalog.py +499 -0
  99. tan/core/example_facets.py +151 -0
  100. tan/core/faultdecode.py +521 -0
  101. tan/core/flash_plan.py +2501 -0
  102. tan/core/flow_d_report.py +381 -0
  103. tan/core/flow_d_uptime.py +164 -0
  104. tan/core/fs_confine.py +130 -0
  105. tan/core/global_flags.py +230 -0
  106. tan/core/host_python.py +210 -0
  107. tan/core/image_bundle.py +247 -0
  108. tan/core/inert.py +147 -0
  109. tan/core/jlink_binary.py +128 -0
  110. tan/core/jlink_probe.py +481 -0
  111. tan/core/jsonc_splice.py +404 -0
  112. tan/core/launch_provenance.py +257 -0
  113. tan/core/link_refusal.py +76 -0
  114. tan/core/metadata_schema.py +376 -0
  115. tan/core/model_check.py +227 -0
  116. tan/core/model_device.py +449 -0
  117. tan/core/model_doctor.py +198 -0
  118. tan/core/model_host.py +48 -0
  119. tan/core/model_list.py +112 -0
  120. tan/core/model_zoo.py +353 -0
  121. tan/core/module_template.py +340 -0
  122. tan/core/mram_link.py +87 -0
  123. tan/core/new_som_ospi.py +94 -0
  124. tan/core/os_class.py +159 -0
  125. tan/core/patch_paths.py +87 -0
  126. tan/core/pending.py +40 -0
  127. tan/core/plain_zephyr_plan.py +138 -0
  128. tan/core/plan_exec.py +373 -0
  129. tan/core/plan_tokens.py +600 -0
  130. tan/core/probe.py +155 -0
  131. tan/core/probe_plan.py +237 -0
  132. tan/core/proxy.py +168 -0
  133. tan/core/publish.py +31 -0
  134. tan/core/python_floor.py +178 -0
  135. tan/core/ram_run.py +694 -0
  136. tan/core/ram_watch.py +231 -0
  137. tan/core/raw_write.py +160 -0
  138. tan/core/reset_plan.py +56 -0
  139. tan/core/run.py +161 -0
  140. tan/core/scaffold.py +2203 -0
  141. tan/core/scaffold_selftest_identity.py +225 -0
  142. tan/core/sdk_default_registry.py +565 -0
  143. tan/core/sdk_discovery.py +996 -0
  144. tan/core/sdk_removal.py +465 -0
  145. tan/core/serial_actions.py +175 -0
  146. tan/core/serial_capture.py +185 -0
  147. tan/core/serial_url.py +45 -0
  148. tan/core/setools.py +608 -0
  149. tan/core/setools_scratch.py +243 -0
  150. tan/core/shapes.py +194 -0
  151. tan/core/size.py +610 -0
  152. tan/core/som_buildability.py +204 -0
  153. tan/core/som_schema_version.py +62 -0
  154. tan/core/subprocess_env.py +201 -0
  155. tan/core/sysbuild_image.py +94 -0
  156. tan/core/system_manifest.py +827 -0
  157. tan/core/text_layout.py +73 -0
  158. tan/core/timestamp.py +182 -0
  159. tan/core/tool_lookup.py +383 -0
  160. tan/core/toolchain_pin.py +117 -0
  161. tan/core/toolchain_provision.py +1224 -0
  162. tan/core/trusted_path.py +134 -0
  163. tan/core/uboot_breakin.py +154 -0
  164. tan/core/uri_reference.py +415 -0
  165. tan/core/user_defines.py +152 -0
  166. tan/core/venv.py +332 -0
  167. tan/core/west_patches.py +143 -0
  168. tan/core/west_workspace_refusal.py +159 -0
  169. tan/core/zephyr_env.py +124 -0
  170. tan/env.py +185 -0
  171. tan/envelope.py +647 -0
  172. tan/exit_codes.py +12 -0
  173. tan/lazy_typer.py +185 -0
  174. tan/model/__init__.py +2 -0
  175. tan/model/_gen_fixture.py +132 -0
  176. tan/model/adapters/__init__.py +116 -0
  177. tan/model/adapters/cpu.py +26 -0
  178. tan/model/adapters/deepx.py +94 -0
  179. tan/model/adapters/drpai.py +242 -0
  180. tan/model/adapters/ethos_u.py +951 -0
  181. tan/model/adapters/executorch.py +55 -0
  182. tan/model/analyze.py +694 -0
  183. tan/model/build.py +304 -0
  184. tan/model/check.py +682 -0
  185. tan/model/manifest.py +625 -0
  186. tan/model/measure.py +216 -0
  187. tan/model/npu_ops_validate.py +130 -0
  188. tan/model/package.py +125 -0
  189. tan/model/perf.py +635 -0
  190. tan/model/perf_apply.py +721 -0
  191. tan/model/prep.py +193 -0
  192. tan/model/sram_fit.py +502 -0
  193. tan/model/targets.py +374 -0
  194. tan/model/tensorio.py +287 -0
  195. tan/net.py +73 -0
  196. tan/output_format.py +213 -0
  197. tan/planner/__init__.py +219 -0
  198. tan/planner/aperture.py +202 -0
  199. tan/planner/buildplan.py +1092 -0
  200. tan/planner/camera_owner.py +192 -0
  201. tan/planner/cameras.py +132 -0
  202. tan/planner/carveout.py +755 -0
  203. tan/planner/cli.py +274 -0
  204. tan/planner/headers.py +315 -0
  205. tan/planner/kconfig.py +2435 -0
  206. tan/planner/kconfig_symbols.py +473 -0
  207. tan/planner/libraries.py +677 -0
  208. tan/planner/link_target.py +237 -0
  209. tan/planner/linux_ownership.py +216 -0
  210. tan/planner/loader.py +1525 -0
  211. tan/planner/manifest.py +116 -0
  212. tan/planner/memory.py +161 -0
  213. tan/planner/memregion.py +56 -0
  214. tan/planner/models.py +533 -0
  215. tan/planner/orchestrator.py +874 -0
  216. tan/planner/ownership.py +203 -0
  217. tan/planner/partition.py +1160 -0
  218. tan/planner/paths.py +32 -0
  219. tan/planner/plain_slice.py +139 -0
  220. tan/planner/project_emit/__init__.py +60 -0
  221. tan/planner/project_emit/bom_netlist.py +375 -0
  222. tan/planner/project_emit/dts.py +580 -0
  223. tan/planner/project_emit/hw_info.py +221 -0
  224. tan/planner/project_emit/native_sim.py +136 -0
  225. tan/planner/project_emit/west_libs.py +213 -0
  226. tan/planner/project_loader.py +264 -0
  227. tan/planner/sdk_capability.py +203 -0
  228. tan/planner/sdk_compat.py +395 -0
  229. tan/planner/secure.py +350 -0
  230. tan/planner/slugs.py +394 -0
  231. tan/planner/som_metadata.py +325 -0
  232. tan/planner/strict_loaders.py +131 -0
  233. tan/planner/template.py +1037 -0
  234. tan/planner/template_pins.py +535 -0
  235. tan/planner/template_rewrite.py +714 -0
  236. tan/planner/topology.py +208 -0
  237. tan/planner/validate.py +365 -0
  238. tan/planner/whole_device_alias.py +67 -0
  239. tan/planner/zephyr_board.py +3510 -0
  240. tan/planner_cli.py +97 -0
  241. tan/planner_emit.py +617 -0
  242. tan/planner_root.py +119 -0
  243. tan/soc_ref.py +58 -0
  244. tan/templates/__init__.py +34 -0
  245. tan/templates/vendored/MANIFEST.md +1862 -0
  246. tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
  247. tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
  248. tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
  249. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
  250. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
  251. tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
  252. tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
  253. tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
  254. tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
  255. tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
  256. tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
  257. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
  258. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
  259. tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
  260. tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
  261. tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
  262. tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
  263. tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
  264. tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
  265. tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
  266. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
  267. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
  268. tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
  269. tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
  270. tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
  271. tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
  272. tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
  273. tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
  274. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
  275. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
  276. tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
  277. tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
  278. tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
  279. tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
  280. tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
  281. tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
  282. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
  283. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
  284. tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
  285. tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
  286. tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
  287. tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
  288. tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
  289. tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
  290. tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
  291. tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
  292. tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
  293. tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
  294. tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
  295. tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
  296. tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
  297. tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
  298. tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
  299. tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
  300. tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
  301. tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
  302. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
  303. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
  304. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
  305. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
  306. tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
  307. tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
  308. tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
  309. tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
  310. tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
  311. tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
  312. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
  313. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
  314. tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
  315. tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
  316. tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
  317. tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
  318. tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
  319. tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
  320. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
  321. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
  322. tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
  323. tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
  324. tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
  325. tan/version.py +102 -0
  326. tan_cli-0.7.0.dist-info/METADATA +125 -0
  327. tan_cli-0.7.0.dist-info/RECORD +330 -0
  328. tan_cli-0.7.0.dist-info/WHEEL +5 -0
  329. tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
  330. tan_cli-0.7.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,521 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Pure decode core for `tan faultdecode` -- ARM Cortex-M (ARMv8-M) fault
3
+ register arithmetic, register dump parsing, and rendering. No I/O.
4
+
5
+ Port of `alp_cli/faultdecode.py` (alp-sdk `scripts/alp_cli/faultdecode.py`),
6
+ minus its CLI plumbing and its `resolve_symbol` subprocess call, which live in
7
+ `tan.commands.faultdecode_cmd` -- this module stays pure so it unit-tests
8
+ without a process or a filesystem, matching the `tan/core/` convention (pure
9
+ logic; `tan/commands/*_cmd.py` owns IO/executor concerns).
10
+
11
+ **Register-level fidelity is the whole value of this port.** Every bit table
12
+ entry (register, bit, flag name, meaning) and every `_root_cause` branch below
13
+ is TRANSCRIBED verbatim from the SDK original, not retyped from memory --
14
+ a shifted bit or a dropped mask produces a confident wrong diagnosis of a
15
+ customer's crash. References: ARMv8-M Architecture Reference Manual, SCB
16
+ CFSR/HFSR/DFSR.
17
+
18
+ Targets the ARMv8-M fault model shared by the SoMs alp-sdk drives: Cortex-M55
19
+ (ARMv8.1-M, AEN `m55_hp`/`m55_he`) and Cortex-M33 (ARMv8-M, V2N CM33). These
20
+ are ARM ARCHITECTURAL register definitions -- CFSR/HFSR/DFSR bit layout is
21
+ fixed by the ARMv8-M Architecture Reference Manual for every Cortex-M55/M33
22
+ part in existence, not an Alp Lab SoM/vendor fact recorded once under
23
+ alp-sdk's `metadata/**`. Nothing here is keyed on a SKU, a part number, an I2C
24
+ address or a pin name, so invariant I-26 (ADR-0017: no SoM hardware fact
25
+ outside `metadata/**`) does not reach it -- there is no metadata source for
26
+ "what CFSR bit 9 means on any Cortex-M", the same way there is none for
27
+ "what a C `for` loop does".
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import re
33
+ from dataclasses import dataclass, field
34
+
35
+ # -------- bit tables ----------------------------------------------------------
36
+ #
37
+ # Each entry is (bit, flag-name, one-line plain-English meaning). Bit numbers
38
+ # for MMFSR/BFSR/UFSR are absolute positions inside the 32-bit CFSR word, so the
39
+ # same mask logic works whether the caller supplied a combined CFSR or the three
40
+ # sub-registers separately. References: ARMv8-M Architecture Reference Manual,
41
+ # SCB CFSR/HFSR/DFSR.
42
+
43
+ # MemManage Fault Status (CFSR bits 0-7).
44
+ MMFSR_BITS: tuple[tuple[int, str, str], ...] = (
45
+ (0, "IACCVIOL", "Instruction access violation -- the MPU blocked an instruction fetch "
46
+ "(executing from a no-execute / unprivileged region)."),
47
+ (1, "DACCVIOL", "Data access violation -- the MPU blocked a load/store; MMFAR holds the address."),
48
+ (3, "MUNSTKERR", "MemManage fault unstacking on exception return -- the exception frame sits "
49
+ "in MPU-protected memory (often a corrupted/overflowed stack)."),
50
+ (4, "MSTKERR", "MemManage fault stacking on exception entry -- the stack pointer points into "
51
+ "MPU-protected/invalid memory (a bad or overflowed stack)."),
52
+ (5, "MLSPERR", "MemManage fault during lazy floating-point state preservation."),
53
+ (7, "MMARVALID", "MMFAR holds a valid faulting data address (see below)."),
54
+ )
55
+
56
+ # BusFault Status (CFSR bits 8-15).
57
+ BFSR_BITS: tuple[tuple[int, str, str], ...] = (
58
+ (8, "IBUSERR", "Instruction bus error -- a prefetch faulted, usually a branch/call through a "
59
+ "bad function pointer into unmapped memory."),
60
+ (9, "PRECISERR", "Precise data bus error -- a load/store faulted and BFAR holds the exact "
61
+ "faulting address (commonly an unclocked/absent peripheral or a bad pointer)."),
62
+ (10, "IMPRECISERR", "Imprecise data bus error -- a buffered/late write faulted; the PC has moved "
63
+ "on, so BFAR is not reliable (look for an earlier bad store)."),
64
+ (11, "UNSTKERR", "Bus fault unstacking on exception return -- a corrupted stack pointer."),
65
+ (12, "STKERR", "Bus fault stacking on exception entry -- a bad or overflowed stack pointer."),
66
+ (13, "LSPERR", "Bus fault during lazy floating-point state preservation."),
67
+ (15, "BFARVALID", "BFAR holds a valid faulting data address (see below)."),
68
+ )
69
+
70
+ # UsageFault Status (CFSR bits 16-31).
71
+ UFSR_BITS: tuple[tuple[int, str, str], ...] = (
72
+ (16, "UNDEFINSTR", "Undefined instruction -- executed a bad/corrupted opcode (often a wild PC "
73
+ "or jumping into data)."),
74
+ (17, "INVSTATE", "Invalid state -- EPSR.T cleared or illegal IT state, classically a function "
75
+ "pointer called without the Thumb bit (bit 0) set."),
76
+ (18, "INVPC", "Invalid PC on exception return -- a bad EXC_RETURN or a corrupted stacked PC."),
77
+ (19, "NOCP", "No coprocessor -- access to a disabled/absent coprocessor, most often the FPU "
78
+ "used before CPACR enables it."),
79
+ (20, "STKOF", "Stack overflow -- the hardware stack-limit (PSPLIM/MSPLIM) tripped (ARMv8-M)."),
80
+ (24, "UNALIGNED", "Unaligned access -- an unaligned load/store while alignment trapping is on "
81
+ "(or an LDM/STM/LDRD that must be aligned)."),
82
+ (25, "DIVBYZERO", "Divide by zero -- SDIV/UDIV by zero with DIV_0_TRP enabled."),
83
+ )
84
+
85
+ # HardFault Status (HFSR).
86
+ HFSR_BITS: tuple[tuple[int, str, str], ...] = (
87
+ (1, "VECTTBL", "Vector-table read fault -- a bus error reading an exception vector."),
88
+ (30, "FORCED", "Forced HardFault -- a configurable fault (MemManage/BusFault/UsageFault) was "
89
+ "escalated; the real cause is in CFSR above."),
90
+ (31, "DEBUGEVT", "Debug event -- a breakpoint/watchpoint fired with no debugger attached."),
91
+ )
92
+
93
+ # Debug Fault Status (DFSR) -- optional, informational.
94
+ DFSR_BITS: tuple[tuple[int, str, str], ...] = (
95
+ (0, "HALTED", "Halt request (debugger single-step / halt)."),
96
+ (1, "BKPT", "Breakpoint -- a BKPT instruction or hardware breakpoint."),
97
+ (2, "DWTTRAP", "DWT watchpoint / debug-monitor trap."),
98
+ (3, "VCATCH", "Vector catch triggered."),
99
+ (4, "EXTERNAL", "External debug request (EDBGRQ)."),
100
+ )
101
+
102
+
103
+ # -------- result types --------------------------------------------------------
104
+
105
+
106
+ @dataclass(slots=True)
107
+ class DecodedFlag:
108
+ """One set status-register bit, with its register and plain-English meaning."""
109
+
110
+ reg: str # MMFSR | BFSR | UFSR | HFSR | DFSR
111
+ name: str
112
+ bit: int # absolute bit position within its register word
113
+ meaning: str
114
+
115
+
116
+ @dataclass(slots=True)
117
+ class FaultReport:
118
+ """The pure decode result: set flags, faulting addresses, and a root cause."""
119
+
120
+ flags: list[DecodedFlag] = field(default_factory=list)
121
+ bfar: int | None = None
122
+ bfar_valid: bool = False
123
+ mmfar: int | None = None
124
+ mmfar_valid: bool = False
125
+ root_cause: str = ""
126
+ inputs: dict[str, int] = field(default_factory=dict)
127
+
128
+ @property
129
+ def fault_detected(self) -> bool:
130
+ return bool(self.flags)
131
+
132
+ def has(self, name: str) -> bool:
133
+ return any(f.name == name for f in self.flags)
134
+
135
+
136
+ @dataclass(slots=True)
137
+ class Symbol:
138
+ addr: int
139
+ func: str
140
+ location: str # file:line, or "??:?" when unknown
141
+
142
+
143
+ # -------- the pure decode core ------------------------------------------------
144
+
145
+
146
+ def _scan(value: int, table: tuple[tuple[int, str, str], ...], reg: str) -> list[DecodedFlag]:
147
+ return [
148
+ DecodedFlag(reg=reg, name=name, bit=bit, meaning=meaning)
149
+ for bit, name, meaning in table
150
+ if value & (1 << bit)
151
+ ]
152
+
153
+
154
+ def decode(
155
+ *,
156
+ cfsr: int = 0,
157
+ hfsr: int = 0,
158
+ dfsr: int = 0,
159
+ bfar: int | None = None,
160
+ mmfar: int | None = None,
161
+ ) -> FaultReport:
162
+ """Decode ARMv8-M fault registers into a :class:`FaultReport`.
163
+
164
+ Pure function -- no I/O, no shelling out -- so it is trivially unit-testable.
165
+ ``cfsr`` carries MMFSR (bits 0-7), BFSR (bits 8-15) and UFSR (bits 16-31).
166
+ ``bfar``/``mmfar`` are only treated as authoritative when the matching VALID
167
+ bit is set in CFSR; an address supplied without its VALID bit is reported but
168
+ flagged as possibly stale.
169
+ """
170
+ report = FaultReport()
171
+ report.inputs = {
172
+ "cfsr": cfsr,
173
+ "hfsr": hfsr,
174
+ "dfsr": dfsr,
175
+ **({"bfar": bfar} if bfar is not None else {}),
176
+ **({"mmfar": mmfar} if mmfar is not None else {}),
177
+ }
178
+
179
+ report.flags.extend(_scan(cfsr, MMFSR_BITS, "MMFSR"))
180
+ report.flags.extend(_scan(cfsr, BFSR_BITS, "BFSR"))
181
+ report.flags.extend(_scan(cfsr, UFSR_BITS, "UFSR"))
182
+ report.flags.extend(_scan(hfsr, HFSR_BITS, "HFSR"))
183
+ report.flags.extend(_scan(dfsr, DFSR_BITS, "DFSR"))
184
+
185
+ report.bfar_valid = report.has("BFARVALID")
186
+ report.mmfar_valid = report.has("MMARVALID")
187
+ report.bfar = bfar
188
+ report.mmfar = mmfar
189
+
190
+ report.root_cause = _root_cause(report)
191
+ return report
192
+
193
+
194
+ def _addr_phrase(report: FaultReport) -> str:
195
+ if report.bfar_valid and report.bfar is not None:
196
+ return f" at 0x{report.bfar:08x} (BFAR)"
197
+ if report.mmfar_valid and report.mmfar is not None:
198
+ return f" at 0x{report.mmfar:08x} (MMFAR)"
199
+ return ""
200
+
201
+
202
+ #: CFSR bits that qualify an ADDRESS rather than name a fault. `MMARVALID`/
203
+ #: `BFARVALID` say only "the address register beside me is trustworthy" -- on
204
+ #: their own they describe nothing that broke, so they are never a root cause
205
+ #: and never count as CFSR "carrying a cause" for `_cfsr_names_a_cause` below.
206
+ _ADDRESS_VALID_FLAGS = frozenset({"MMARVALID", "BFARVALID"})
207
+
208
+ #: The three sub-registers packed into CFSR, i.e. the configurable faults
209
+ #: (MemManage/BusFault/UsageFault) that HFSR.FORCED escalates FROM.
210
+ _CFSR_REGISTERS = frozenset({"MMFSR", "BFSR", "UFSR"})
211
+
212
+
213
+ def _cfsr_names_a_cause(report: FaultReport) -> bool:
214
+ """Whether CFSR carries a configurable-fault CAUSE bit (tan-cli#616).
215
+
216
+ Keyed on the flag's REGISTER, not on a hand-kept list of names: a bit added
217
+ to `MMFSR_BITS`/`BFSR_BITS`/`UFSR_BITS` tomorrow counts as a cause the day
218
+ it lands, even before anyone gives it a branch in `_root_cause`. A
219
+ name-list would have to be edited in lockstep and, unedited, would silently
220
+ hand the headline back to `FORCED` -- which is exactly how LSPERR/MLSPERR
221
+ (in the bit tables since day one, in the ladder never) came to be reported
222
+ as "Forced HardFault" in the first place.
223
+ """
224
+ return any(
225
+ flag.reg in _CFSR_REGISTERS and flag.name not in _ADDRESS_VALID_FLAGS
226
+ for flag in report.flags
227
+ )
228
+
229
+
230
+ def _root_cause(report: FaultReport) -> str:
231
+ """Pick the single most likely root cause from the set flags.
232
+
233
+ Ordered most-specific-first: a precise address or a stack-overflow trap tells
234
+ you far more than the generic "forced HardFault" escalation bit, so those win.
235
+
236
+ **`HFSR.FORCED` is an escalation mechanism, not a fault** (tan-cli#616).
237
+ Bit 30 means a configurable fault could not be taken by its own handler and
238
+ was escalated to HardFault; WHAT faulted is in CFSR. So `FORCED` is the
239
+ headline only when CFSR names no cause at all -- see `_cfsr_names_a_cause`.
240
+ The escalation itself is not lost by that demotion: it is already reported,
241
+ verbatim and in the right place, as its own `[HFSR] FORCED (bit 30)` entry
242
+ in the "Set flags:" block, which is where a qualifier belongs. This line
243
+ answers "what broke", and `FORCED` never answers that when CFSR does.
244
+
245
+ This DIVERGES from alp-sdk's `scripts/alp_cli/faultdecode.py`, which this
246
+ module is otherwise a verbatim port of (see the module docstring and
247
+ `tests/core/test_faultdecode.py`'s golden). The divergence is deliberate,
248
+ bounded to the three branches below, and the upstream should follow --
249
+ alp-sdk carries the same defect.
250
+ """
251
+ if not report.flags:
252
+ return "No fault status bits are set -- nothing to decode."
253
+
254
+ addr = _addr_phrase(report)
255
+
256
+ if report.has("STKOF"):
257
+ return ("Stack overflow: the ARMv8-M stack-limit register (PSPLIM/MSPLIM) tripped. "
258
+ "Grow the offending thread/ISR stack or fix unbounded recursion / large stack buffers.")
259
+ if report.has("PRECISERR"):
260
+ return (f"Precise data bus fault{addr or ''} -- a load/store hit a faulting address, "
261
+ "commonly an access to an unclocked/absent peripheral or a bad/dangling pointer.")
262
+ if report.has("IMPRECISERR"):
263
+ return ("Imprecise data bus fault -- a buffered write faulted after the CPU moved on, so the "
264
+ "PC/BFAR do not pinpoint it. Suspect an earlier bad store; a DSB after suspect writes "
265
+ "makes it precise.")
266
+ if report.has("DACCVIOL"):
267
+ return (f"MPU data access violation{addr or ''} -- a load/store hit a region the MPU forbids "
268
+ "(wrong permissions, or an unmapped/unprivileged address).")
269
+ if report.has("IACCVIOL"):
270
+ return ("MPU instruction access violation -- the core tried to execute from a no-execute / "
271
+ "forbidden region (often a corrupted PC or a bad function pointer).")
272
+ if report.has("IBUSERR"):
273
+ return ("Instruction bus fault -- a fetch faulted, typically a branch/call through a bad "
274
+ "function pointer into unmapped memory.")
275
+ if report.has("MSTKERR") or report.has("STKERR"):
276
+ return ("Fault while stacking the exception frame on entry -- the stack pointer is bad or "
277
+ "overflowed (check SP/PSPLIM and the offending stack's size).")
278
+ if report.has("MUNSTKERR") or report.has("UNSTKERR"):
279
+ return ("Fault while unstacking on exception return -- the saved exception frame is corrupted "
280
+ "(a stack overwrite or a clobbered SP).")
281
+ if report.has("DIVBYZERO"):
282
+ return ("Divide by zero -- SDIV/UDIV with a zero divisor and DIV_0_TRP enabled. Guard the "
283
+ "divisor or disable the trap.")
284
+ if report.has("UNALIGNED"):
285
+ return ("Unaligned access fault -- an unaligned load/store with alignment trapping on (or an "
286
+ "LDM/STM/LDRD that requires alignment). Fix the pointer alignment or use packed access.")
287
+ if report.has("INVSTATE"):
288
+ return ("Invalid state (EPSR.T / IT) -- almost always a function pointer called without the "
289
+ "Thumb bit (bit 0) set, or a corrupted PSR.")
290
+ if report.has("INVPC"):
291
+ return ("Invalid PC on exception return -- a bad EXC_RETURN value or a corrupted stacked PC "
292
+ "(stack overflow / FNC return into a clobbered frame).")
293
+ if report.has("NOCP"):
294
+ return ("No-coprocessor fault -- code used a coprocessor that is disabled, most often the FPU "
295
+ "before CPACR (CP10/CP11) enables it. Enable the FPU in CPACR / Kconfig.")
296
+ if report.has("UNDEFINSTR"):
297
+ return ("Undefined instruction -- a corrupted/wild PC executed a bad opcode, or code was "
298
+ "built for a different ISA than the running core.")
299
+ if report.has("VECTTBL"):
300
+ return ("Vector-table read fault -- a bus error reading an exception vector (VTOR points at "
301
+ "bad memory, or the vector table is unmapped).")
302
+ if report.has("DEBUGEVT"):
303
+ return ("Debug event with no debugger attached -- a stray BKPT or a watchpoint firing in a "
304
+ "free-running build.")
305
+ # LAST of the CAUSES -- below VECTTBL and DEBUGEVT as well as below every
306
+ # CFSR branch, on purpose (tan-cli#616). The ladder is most-specific-first,
307
+ # and a lazy-FP-preservation fault names WHEN the fault was taken rather
308
+ # than WHAT the code did wrong, so it answers only when nothing else can.
309
+ # `VECTTBL` (VTOR points at bad memory, or the vector table is unmapped)
310
+ # and `DEBUGEVT` (a stray BKPT in a free-running build) are both more
311
+ # specific findings than "something faulted during the deferred FP push",
312
+ # so both keep their existing precedence over these two.
313
+ #
314
+ # That placement is also what keeps the divergence from alp-sdk minimal and
315
+ # checkable: it changes the answer for exactly the words upstream had no
316
+ # answer for (LSPERR/MLSPERR as the only cause bits, which fell through to
317
+ # `FORCED` or to the bare `<NAME> set (<REG>)` fallback) and leaves every
318
+ # existing precedence untouched -- a claim the live-oracle sweep in
319
+ # `tests/core/test_faultdecode.py` enforces rather than merely asserts.
320
+ #
321
+ # Two earlier drafts got this wrong in the same way, by inserting rather
322
+ # than deciding. The first placed these beside the MSTKERR/STKERR stacking
323
+ # family, which reordered LSPERR ahead of UNALIGNED/INVSTATE/NOCP; the
324
+ # sweep caught it. The second placed them here but ABOVE VECTTBL/DEBUGEVT,
325
+ # which silently overrode both on two-bit words like
326
+ # `cfsr=0x2000 hfsr=0x2` -- and the sweep did NOT catch that, because it
327
+ # never paired two bits. It does now (`_TWO_BIT_PRECEDENCE_CASES`).
328
+ if report.has("LSPERR"):
329
+ return (f"Bus fault while lazily preserving the floating-point context{addr or ''} -- the "
330
+ "deferred push of the FP registers into the space the exception frame reserved for "
331
+ "them hit a faulting address, so that stack memory is bad or absent (a corrupted or "
332
+ "overflowed stack pointer). Check SP/PSPLIM and that the stack fits the larger "
333
+ "FP-extended exception frame.")
334
+ if report.has("MLSPERR"):
335
+ return (f"MemManage fault while lazily preserving the floating-point context{addr or ''} -- "
336
+ "the MPU forbids the deferred push of the FP registers into the space the exception "
337
+ "frame reserved for them (wrong region permissions, or a stack that has overflowed "
338
+ "out of its region).")
339
+ if report.has("FORCED") and not _cfsr_names_a_cause(report):
340
+ return ("Forced HardFault -- a configurable fault escalated but its own status bits are clear; "
341
+ "the escalation usually means faults are disabled (SHCSR) or it faulted at priority -1.")
342
+
343
+ # Same rule as the `FORCED` guard, applied to the fallback: an address-VALID
344
+ # bit describes the register beside it, never the fault, so it must not be
345
+ # announced as the cause while a real one sits later in the list.
346
+ first = next(
347
+ (flag for flag in report.flags if flag.name not in _ADDRESS_VALID_FLAGS),
348
+ report.flags[0],
349
+ )
350
+ return f"{first.name} set ({first.reg}): {first.meaning}"
351
+
352
+
353
+ # -------- dump parsing --------------------------------------------------------
354
+ #
355
+ # Register tokens we will grep out of a pasted dump. Maps the token (matched
356
+ # case-insensitively) to a canonical key. Sub-registers (mmfsr/bfsr/ufsr) get
357
+ # composed back into CFSR. Order matters only for the regex alternation; the
358
+ # longer tokens are listed first so e.g. "mmfar" is not eaten by a shorter name.
359
+
360
+ _DUMP_TOKENS: tuple[tuple[str, str], ...] = (
361
+ ("cfsr", "cfsr"),
362
+ ("hfsr", "hfsr"),
363
+ ("dfsr", "dfsr"),
364
+ ("mmfar", "mmfar"),
365
+ ("bfar", "bfar"),
366
+ ("mmfsr", "mmfsr"),
367
+ ("bfsr", "bfsr"),
368
+ ("ufsr", "ufsr"),
369
+ ("pc", "pc"),
370
+ ("lr", "lr"),
371
+ )
372
+
373
+ # token, optional same-line noise (e.g. "Address:"), then a 0x or bare-hex value.
374
+ # The noise gap allows any non-newline chars (non-greedy) so hex-letter words
375
+ # like "Address" between the name and its value are skipped; the value still has
376
+ # to be a 0x literal or a word-bounded hex run, so it cannot land mid-word.
377
+ _DUMP_RE = re.compile(
378
+ r"(?i)\b(" + "|".join(re.escape(t) for t, _ in _DUMP_TOKENS) + r")\b"
379
+ r"[^\r\n]{0,24}?"
380
+ r"(0x[0-9A-Fa-f]+|\b[0-9A-Fa-f]{2,8}\b)"
381
+ )
382
+
383
+
384
+ def parse_dump(text: str) -> dict[str, int]:
385
+ """Grep known register names + values out of a pasted fault dump.
386
+
387
+ Recognises ``CFSR``/``HFSR``/``DFSR``/``BFAR``/``MMFAR`` plus the split
388
+ ``MMFSR``/``BFSR``/``UFSR`` (composed back into CFSR) and ``PC``/``LR``.
389
+ Accepts ``NAME: 0x..`` / ``NAME = 0x..`` / ``MMFAR Address: 0x..`` shapes and
390
+ bare hex. Last occurrence of a token wins.
391
+ """
392
+ canon = dict(_DUMP_TOKENS)
393
+ found: dict[str, int] = {}
394
+ for m in _DUMP_RE.finditer(text):
395
+ key = canon[m.group(1).lower()]
396
+ raw = m.group(2)
397
+ try:
398
+ value = int(raw, 16)
399
+ except ValueError: # pragma: no cover - regex already constrains this
400
+ continue
401
+ if value > 0xFFFFFFFF:
402
+ continue # not a 32-bit register word; never decode its low half
403
+ found[key] = value
404
+
405
+ # Compose CFSR from sub-registers if a combined CFSR was not given outright.
406
+ if "cfsr" not in found:
407
+ composed = 0
408
+ if "mmfsr" in found:
409
+ composed |= found["mmfsr"] & 0xFF
410
+ if "bfsr" in found:
411
+ composed |= (found["bfsr"] & 0xFF) << 8
412
+ if "ufsr" in found:
413
+ composed |= (found["ufsr"] & 0xFFFF) << 16
414
+ if composed:
415
+ found["cfsr"] = composed
416
+ for k in ("mmfsr", "bfsr", "ufsr"):
417
+ found.pop(k, None)
418
+ return found
419
+
420
+
421
+ # -------- rendering -----------------------------------------------------------
422
+ #
423
+ # Raw ANSI SGR codes, matching `tan.core.size`'s convention rather than pulling
424
+ # in colorama (not a dependency of this package -- see pyproject.toml's
425
+ # four/five-name ceiling). Values are colorama's `Fore`/`Style` equivalents for
426
+ # the exact hues the original used.
427
+ _MAGENTA = "\x1b[35m"
428
+ _RED = "\x1b[31m"
429
+ _YELLOW = "\x1b[33m"
430
+ _CYAN = "\x1b[36m"
431
+ _GREEN = "\x1b[32m"
432
+ _WHITE = "\x1b[37m"
433
+ _BRIGHT = "\x1b[1m"
434
+ _RESET = "\x1b[0m"
435
+
436
+ _REG_HUE = {
437
+ "MMFSR": _MAGENTA,
438
+ "BFSR": _RED,
439
+ "UFSR": _YELLOW,
440
+ "HFSR": _RED,
441
+ "DFSR": _CYAN,
442
+ }
443
+
444
+
445
+ def _paint(s: str, hue: str, color: bool) -> str:
446
+ return f"{hue}{s}{_RESET}" if color else s
447
+
448
+
449
+ def render_human(
450
+ report: FaultReport,
451
+ symbols: dict[str, Symbol] | None,
452
+ color: bool,
453
+ ) -> str:
454
+ lines: list[str] = []
455
+ head = "ARM Cortex-M (ARMv8-M) fault decode"
456
+ lines.append(_paint(head, _CYAN + _BRIGHT, color))
457
+
458
+ # Echo the registers we actually decoded.
459
+ reg_bits = [f"{k.upper()}=0x{v:08x}" for k, v in report.inputs.items()]
460
+ lines.append(" " + " ".join(reg_bits))
461
+ lines.append("")
462
+
463
+ if not report.fault_detected:
464
+ lines.append(_paint(" No fault flags set.", _GREEN, color))
465
+ lines.append(" " + report.root_cause)
466
+ return "\n".join(lines)
467
+
468
+ lines.append(_paint("Set flags:", _WHITE + _BRIGHT, color))
469
+ for f in report.flags:
470
+ tag = _paint(f"[{f.reg}] {f.name}", _REG_HUE.get(f.reg, ""), color)
471
+ lines.append(f" {tag} (bit {f.bit}): {f.meaning}")
472
+
473
+ # Faulting addresses.
474
+ if report.bfar is not None:
475
+ note = "" if report.bfar_valid else " (BFARVALID not set -- address may be stale)"
476
+ lines.append(f" Faulting address (BFAR): 0x{report.bfar:08x}{note}")
477
+ if report.mmfar is not None:
478
+ note = "" if report.mmfar_valid else " (MMARVALID not set -- address may be stale)"
479
+ lines.append(f" Faulting address (MMFAR): 0x{report.mmfar:08x}{note}")
480
+
481
+ lines.append("")
482
+ lines.append(_paint("Most likely cause:", _WHITE + _BRIGHT, color))
483
+ lines.append(" " + report.root_cause)
484
+
485
+ if symbols:
486
+ lines.append("")
487
+ lines.append(_paint("Symbolication:", _WHITE + _BRIGHT, color))
488
+ for which, sym in symbols.items():
489
+ lines.append(
490
+ f" {which.upper()} 0x{sym.addr:08x} -> {_paint(sym.func, _GREEN, color)} "
491
+ f"({sym.location})"
492
+ )
493
+
494
+ return "\n".join(lines)
495
+
496
+
497
+ def report_to_json(report: FaultReport, symbols: dict[str, Symbol] | None) -> dict:
498
+ """Machine-readable shape for the extension's troubleshooting panel."""
499
+ return {
500
+ "fault_detected": report.fault_detected,
501
+ "inputs": {k: f"0x{v:08x}" for k, v in report.inputs.items()},
502
+ "flags": [
503
+ {"reg": f.reg, "name": f.name, "bit": f.bit, "meaning": f.meaning}
504
+ for f in report.flags
505
+ ],
506
+ "addresses": {
507
+ "bfar": None if report.bfar is None else f"0x{report.bfar:08x}",
508
+ "bfar_valid": report.bfar_valid,
509
+ "mmfar": None if report.mmfar is None else f"0x{report.mmfar:08x}",
510
+ "mmfar_valid": report.mmfar_valid,
511
+ },
512
+ "root_cause": report.root_cause,
513
+ "symbols": (
514
+ None
515
+ if not symbols
516
+ else {
517
+ which: {"addr": f"0x{s.addr:08x}", "func": s.func, "location": s.location}
518
+ for which, s in symbols.items()
519
+ }
520
+ ),
521
+ }