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.
- tan/__init__.py +0 -0
- tan/__main__.py +120 -0
- tan/cli.py +939 -0
- tan/command_table.py +265 -0
- tan/commands/__init__.py +8 -0
- tan/commands/bootstrap_cmd.py +4856 -0
- tan/commands/bootstrap_patches.py +164 -0
- tan/commands/bootstrap_toolchain_fetch.py +155 -0
- tan/commands/bootstrap_toolchain_pin.py +36 -0
- tan/commands/build/__init__.py +1 -0
- tan/commands/build/configure_inputs.py +277 -0
- tan/commands/build/execute.py +1852 -0
- tan/commands/build/host_python.py +59 -0
- tan/commands/build/link_stale.py +78 -0
- tan/commands/build/manifest.py +428 -0
- tan/commands/build/materialise.py +81 -0
- tan/commands/build/token_substitution.py +381 -0
- tan/commands/build/toolchain.py +482 -0
- tan/commands/build/user_defines_wipe.py +115 -0
- tan/commands/build/workspace_patches.py +65 -0
- tan/commands/build_cmd.py +2236 -0
- tan/commands/build_output.py +331 -0
- tan/commands/clean_cmd.py +1183 -0
- tan/commands/completion_cmd.py +786 -0
- tan/commands/debug_config_cmd.py +2425 -0
- tan/commands/diff_cmd.py +940 -0
- tan/commands/doctor_cmd.py +4767 -0
- tan/commands/examples_cmd.py +644 -0
- tan/commands/explain_cmd.py +1319 -0
- tan/commands/faultdecode_cmd.py +931 -0
- tan/commands/flash_atoc_guard.py +240 -0
- tan/commands/flash_cmd.py +5731 -0
- tan/commands/flash_mram_guard.py +150 -0
- tan/commands/flash_ram.py +578 -0
- tan/commands/flash_raw.py +583 -0
- tan/commands/generate_cmd.py +1456 -0
- tan/commands/image_cmd.py +753 -0
- tan/commands/init_cmd.py +1740 -0
- tan/commands/inspect_cmd.py +377 -0
- tan/commands/kconfig_cmd.py +734 -0
- tan/commands/model_cmd.py +1514 -0
- tan/commands/model_device_cmd.py +351 -0
- tan/commands/model_device_live.py +234 -0
- tan/commands/model_host_cmd.py +430 -0
- tan/commands/model_zoo_cmd.py +363 -0
- tan/commands/monitor_actions.py +125 -0
- tan/commands/monitor_cmd.py +626 -0
- tan/commands/monitor_session.py +505 -0
- tan/commands/new_som_cmd.py +1522 -0
- tan/commands/pinmux_cmd.py +568 -0
- tan/commands/presets_cmd.py +1123 -0
- tan/commands/probe_cmd.py +421 -0
- tan/commands/reset_cmd.py +354 -0
- tan/commands/reset_confirm.py +168 -0
- tan/commands/run_cmd.py +637 -0
- tan/commands/scaffold_cmd.py +490 -0
- tan/commands/sdk_cmd.py +1723 -0
- tan/commands/size_cmd.py +1020 -0
- tan/commands/support_bundle_cmd.py +1073 -0
- tan/commands/trace_cmd.py +341 -0
- tan/commands/validate_cmd.py +2009 -0
- tan/commands/west_forward_cmd.py +736 -0
- tan/commands/workspace_patch_check.py +296 -0
- tan/core/__init__.py +1 -0
- tan/core/alp_conf_pregen.py +100 -0
- tan/core/artifact_provenance.py +240 -0
- tan/core/atoc_guard.py +276 -0
- tan/core/atoc_guard_messages.py +232 -0
- tan/core/atoc_replacement.py +118 -0
- tan/core/atomic_write.py +178 -0
- tan/core/board_context.py +351 -0
- tan/core/board_diagnostic.py +111 -0
- tan/core/board_files.py +221 -0
- tan/core/board_validator.py +542 -0
- tan/core/board_validator_compat.py +196 -0
- tan/core/board_validator_run.py +129 -0
- tan/core/board_validator_schema.py +186 -0
- tan/core/board_validator_skew.py +46 -0
- tan/core/board_yaml_edit.py +295 -0
- tan/core/board_yaml_pos.py +118 -0
- tan/core/bootstrap.py +2572 -0
- tan/core/build_plan.py +625 -0
- tan/core/consent.py +104 -0
- tan/core/console_filter.py +245 -0
- tan/core/debug_launch.py +1803 -0
- tan/core/devicetree_lint.py +168 -0
- tan/core/dir_removal.py +229 -0
- tan/core/doctor_git.py +172 -0
- tan/core/doctor_libraries.py +253 -0
- tan/core/doctor_render.py +185 -0
- tan/core/doctor_scope.py +66 -0
- tan/core/doctor_setools.py +237 -0
- tan/core/doctor_stale.py +180 -0
- tan/core/document_guards.py +723 -0
- tan/core/dp_id.py +97 -0
- tan/core/elf_load.py +63 -0
- tan/core/error_catalog.py +171 -0
- tan/core/example_catalog.py +499 -0
- tan/core/example_facets.py +151 -0
- tan/core/faultdecode.py +521 -0
- tan/core/flash_plan.py +2501 -0
- tan/core/flow_d_report.py +381 -0
- tan/core/flow_d_uptime.py +164 -0
- tan/core/fs_confine.py +130 -0
- tan/core/global_flags.py +230 -0
- tan/core/host_python.py +210 -0
- tan/core/image_bundle.py +247 -0
- tan/core/inert.py +147 -0
- tan/core/jlink_binary.py +128 -0
- tan/core/jlink_probe.py +481 -0
- tan/core/jsonc_splice.py +404 -0
- tan/core/launch_provenance.py +257 -0
- tan/core/link_refusal.py +76 -0
- tan/core/metadata_schema.py +376 -0
- tan/core/model_check.py +227 -0
- tan/core/model_device.py +449 -0
- tan/core/model_doctor.py +198 -0
- tan/core/model_host.py +48 -0
- tan/core/model_list.py +112 -0
- tan/core/model_zoo.py +353 -0
- tan/core/module_template.py +340 -0
- tan/core/mram_link.py +87 -0
- tan/core/new_som_ospi.py +94 -0
- tan/core/os_class.py +159 -0
- tan/core/patch_paths.py +87 -0
- tan/core/pending.py +40 -0
- tan/core/plain_zephyr_plan.py +138 -0
- tan/core/plan_exec.py +373 -0
- tan/core/plan_tokens.py +600 -0
- tan/core/probe.py +155 -0
- tan/core/probe_plan.py +237 -0
- tan/core/proxy.py +168 -0
- tan/core/publish.py +31 -0
- tan/core/python_floor.py +178 -0
- tan/core/ram_run.py +694 -0
- tan/core/ram_watch.py +231 -0
- tan/core/raw_write.py +160 -0
- tan/core/reset_plan.py +56 -0
- tan/core/run.py +161 -0
- tan/core/scaffold.py +2203 -0
- tan/core/scaffold_selftest_identity.py +225 -0
- tan/core/sdk_default_registry.py +565 -0
- tan/core/sdk_discovery.py +996 -0
- tan/core/sdk_removal.py +465 -0
- tan/core/serial_actions.py +175 -0
- tan/core/serial_capture.py +185 -0
- tan/core/serial_url.py +45 -0
- tan/core/setools.py +608 -0
- tan/core/setools_scratch.py +243 -0
- tan/core/shapes.py +194 -0
- tan/core/size.py +610 -0
- tan/core/som_buildability.py +204 -0
- tan/core/som_schema_version.py +62 -0
- tan/core/subprocess_env.py +201 -0
- tan/core/sysbuild_image.py +94 -0
- tan/core/system_manifest.py +827 -0
- tan/core/text_layout.py +73 -0
- tan/core/timestamp.py +182 -0
- tan/core/tool_lookup.py +383 -0
- tan/core/toolchain_pin.py +117 -0
- tan/core/toolchain_provision.py +1224 -0
- tan/core/trusted_path.py +134 -0
- tan/core/uboot_breakin.py +154 -0
- tan/core/uri_reference.py +415 -0
- tan/core/user_defines.py +152 -0
- tan/core/venv.py +332 -0
- tan/core/west_patches.py +143 -0
- tan/core/west_workspace_refusal.py +159 -0
- tan/core/zephyr_env.py +124 -0
- tan/env.py +185 -0
- tan/envelope.py +647 -0
- tan/exit_codes.py +12 -0
- tan/lazy_typer.py +185 -0
- tan/model/__init__.py +2 -0
- tan/model/_gen_fixture.py +132 -0
- tan/model/adapters/__init__.py +116 -0
- tan/model/adapters/cpu.py +26 -0
- tan/model/adapters/deepx.py +94 -0
- tan/model/adapters/drpai.py +242 -0
- tan/model/adapters/ethos_u.py +951 -0
- tan/model/adapters/executorch.py +55 -0
- tan/model/analyze.py +694 -0
- tan/model/build.py +304 -0
- tan/model/check.py +682 -0
- tan/model/manifest.py +625 -0
- tan/model/measure.py +216 -0
- tan/model/npu_ops_validate.py +130 -0
- tan/model/package.py +125 -0
- tan/model/perf.py +635 -0
- tan/model/perf_apply.py +721 -0
- tan/model/prep.py +193 -0
- tan/model/sram_fit.py +502 -0
- tan/model/targets.py +374 -0
- tan/model/tensorio.py +287 -0
- tan/net.py +73 -0
- tan/output_format.py +213 -0
- tan/planner/__init__.py +219 -0
- tan/planner/aperture.py +202 -0
- tan/planner/buildplan.py +1092 -0
- tan/planner/camera_owner.py +192 -0
- tan/planner/cameras.py +132 -0
- tan/planner/carveout.py +755 -0
- tan/planner/cli.py +274 -0
- tan/planner/headers.py +315 -0
- tan/planner/kconfig.py +2435 -0
- tan/planner/kconfig_symbols.py +473 -0
- tan/planner/libraries.py +677 -0
- tan/planner/link_target.py +237 -0
- tan/planner/linux_ownership.py +216 -0
- tan/planner/loader.py +1525 -0
- tan/planner/manifest.py +116 -0
- tan/planner/memory.py +161 -0
- tan/planner/memregion.py +56 -0
- tan/planner/models.py +533 -0
- tan/planner/orchestrator.py +874 -0
- tan/planner/ownership.py +203 -0
- tan/planner/partition.py +1160 -0
- tan/planner/paths.py +32 -0
- tan/planner/plain_slice.py +139 -0
- tan/planner/project_emit/__init__.py +60 -0
- tan/planner/project_emit/bom_netlist.py +375 -0
- tan/planner/project_emit/dts.py +580 -0
- tan/planner/project_emit/hw_info.py +221 -0
- tan/planner/project_emit/native_sim.py +136 -0
- tan/planner/project_emit/west_libs.py +213 -0
- tan/planner/project_loader.py +264 -0
- tan/planner/sdk_capability.py +203 -0
- tan/planner/sdk_compat.py +395 -0
- tan/planner/secure.py +350 -0
- tan/planner/slugs.py +394 -0
- tan/planner/som_metadata.py +325 -0
- tan/planner/strict_loaders.py +131 -0
- tan/planner/template.py +1037 -0
- tan/planner/template_pins.py +535 -0
- tan/planner/template_rewrite.py +714 -0
- tan/planner/topology.py +208 -0
- tan/planner/validate.py +365 -0
- tan/planner/whole_device_alias.py +67 -0
- tan/planner/zephyr_board.py +3510 -0
- tan/planner_cli.py +97 -0
- tan/planner_emit.py +617 -0
- tan/planner_root.py +119 -0
- tan/soc_ref.py +58 -0
- tan/templates/__init__.py +34 -0
- tan/templates/vendored/MANIFEST.md +1862 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
- tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
- tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
- tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
- tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
- tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
- tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
- tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
- tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
- tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
- tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
- tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
- tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
- tan/version.py +102 -0
- tan_cli-0.7.0.dist-info/METADATA +125 -0
- tan_cli-0.7.0.dist-info/RECORD +330 -0
- tan_cli-0.7.0.dist-info/WHEEL +5 -0
- tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
- tan_cli-0.7.0.dist-info/top_level.txt +1 -0
tan/core/faultdecode.py
ADDED
|
@@ -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
|
+
}
|