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/ram_run.py
ADDED
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Pure logic for AEN Flow C -- a J-Link ITCM RAM-run (tan-cli#1313).
|
|
3
|
+
|
|
4
|
+
Flow C loads a Zephyr image into ITCM over SWD, points the core at its reset
|
|
5
|
+
handler, lets it run and reads `ram_console_buf` back. **Nothing touches MRAM.**
|
|
6
|
+
The proven raw helper is alp-sdk `scripts/bench/aen/ram-run.sh` (+ `reread.sh`);
|
|
7
|
+
this module is its decision half, with no IO beyond reading the ELF bytes it is
|
|
8
|
+
given:
|
|
9
|
+
|
|
10
|
+
* a minimal ELF32 reader (no `pyelftools`: tan declares no such dependency and
|
|
11
|
+
the toolchain's `readelf`/`nm` are not guaranteed to be on the host) -- the
|
|
12
|
+
LOAD segments and the symbol table;
|
|
13
|
+
* [`plan_ram_image`]: the load address DERIVED from the LOAD segment with the
|
|
14
|
+
lowest `p_paddr` among those with a nonzero `p_filesz` (never "the first LOAD
|
|
15
|
+
segment", which is often a zero-FileSiz `.bss` in DTCM -- loading there splats
|
|
16
|
+
live RAM), the refusals the script makes (MRAM-linked image, implausible base,
|
|
17
|
+
vector table that disagrees with the ELF entry), and the initial SP/PC read
|
|
18
|
+
from the vector table;
|
|
19
|
+
* the J-Link Commander scripts for the load session and the console read;
|
|
20
|
+
* [`decode_console`]: the `mem8` dump -> text, exactly the script's awk.
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import re
|
|
25
|
+
import struct
|
|
26
|
+
from collections.abc import Sequence
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
|
|
29
|
+
#: Registered issue codes (`contract/issue-codes.json`).
|
|
30
|
+
CODE_NOT_RAM_LINKED = "flash.ram-image-not-ram-linked"
|
|
31
|
+
CODE_CONSOLE_SYMBOL_MISSING = "flash.ram-console-symbol-missing"
|
|
32
|
+
CODE_FAILED = "flash.ram-failed"
|
|
33
|
+
CODE_CORE_UNSUPPORTED = "flash.ram-core-unsupported"
|
|
34
|
+
CODE_CORE_MISMATCH = "flash.ram-core-mismatch"
|
|
35
|
+
CODE_CORE_UNCONFIRMED = "flash.ram-core-unconfirmed"
|
|
36
|
+
|
|
37
|
+
#: The RAM console buffer symbol (`CONFIG_RAM_CONSOLE`).
|
|
38
|
+
#: SRAM0's local aperture base on the AEN parts (shared with `core.size`).
|
|
39
|
+
SRAM0_BASE = 0x02000000
|
|
40
|
+
|
|
41
|
+
CONSOLE_SYMBOL = "ram_console_buf"
|
|
42
|
+
|
|
43
|
+
#: Largest region one `mem8` command reads (bench-env.sh `bench_mem8_chunks`).
|
|
44
|
+
MEM8_CHUNK = 65536
|
|
45
|
+
#: Upper bound on the console read, so a corrupt symbol size cannot ask for MiBs.
|
|
46
|
+
MAX_CONSOLE_BYTES = 64 * 1024
|
|
47
|
+
#: ELF reader bounds: a symbol table this large, or one whose entries are smaller than
|
|
48
|
+
#: an Elf32_Sym, is not a Zephyr image and is not trusted for an address.
|
|
49
|
+
MAX_SYMBOLS = 200_000
|
|
50
|
+
MAX_SECTIONS = 4096
|
|
51
|
+
ELF32_SYM_SIZE = 16
|
|
52
|
+
#: What the script reads when the symbol carries no size.
|
|
53
|
+
DEFAULT_CONSOLE_BYTES = 0x600
|
|
54
|
+
|
|
55
|
+
MRAM_BASE = 0x80000000
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RamRunError(Exception):
|
|
59
|
+
"""A Flow C refusal. `code` is the registered issue code."""
|
|
60
|
+
|
|
61
|
+
def __init__(self, message: str, code: str = CODE_FAILED) -> None:
|
|
62
|
+
super().__init__(message)
|
|
63
|
+
self.code = code
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ── ELF32 ───────────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True)
|
|
70
|
+
class Segment:
|
|
71
|
+
paddr: int
|
|
72
|
+
vaddr: int
|
|
73
|
+
filesz: int
|
|
74
|
+
memsz: int
|
|
75
|
+
#: ELF `p_flags` (PF_X = 1). Defaults to R+X so hand-built segments stay executable.
|
|
76
|
+
flags: int = 5
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(frozen=True)
|
|
80
|
+
class ElfImage:
|
|
81
|
+
entry: int
|
|
82
|
+
segments: tuple[Segment, ...]
|
|
83
|
+
#: name -> (address, size)
|
|
84
|
+
symbols: dict[str, tuple[int, int]]
|
|
85
|
+
#: names the symbol table defines more than once at different addresses (static
|
|
86
|
+
#: symbols of the same name in several files); `symbols` keeps the first of them.
|
|
87
|
+
ambiguous: frozenset[str] = frozenset()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def parse_elf(data: bytes) -> ElfImage:
|
|
91
|
+
"""Parse the parts of a little-endian ELF32 that Flow C needs. Raises
|
|
92
|
+
`RamRunError` for anything else (a 64-bit or big-endian file, a truncated
|
|
93
|
+
one): guessing at a load address from a file we cannot read is how a
|
|
94
|
+
resident app's stack gets overwritten."""
|
|
95
|
+
if len(data) < 52 or data[:4] != b"\x7fELF":
|
|
96
|
+
raise RamRunError("not an ELF file")
|
|
97
|
+
if data[4] != 1 or data[5] != 1:
|
|
98
|
+
raise RamRunError("only little-endian ELF32 images are supported")
|
|
99
|
+
try:
|
|
100
|
+
(_t, _m, _v, entry, phoff, shoff, _f, _eh, phentsize, phnum, shentsize, shnum, _si) = (
|
|
101
|
+
struct.unpack_from("<HHIIIIIHHHHHH", data, 16)
|
|
102
|
+
)
|
|
103
|
+
segments = []
|
|
104
|
+
for i in range(phnum):
|
|
105
|
+
off = phoff + i * phentsize
|
|
106
|
+
p_type, _o, p_vaddr, p_paddr, p_filesz, p_memsz, p_flags = struct.unpack_from(
|
|
107
|
+
"<IIIIIII", data, off
|
|
108
|
+
)
|
|
109
|
+
if p_type == 1: # PT_LOAD
|
|
110
|
+
segments.append(Segment(p_paddr, p_vaddr, p_filesz, p_memsz, p_flags))
|
|
111
|
+
symbols, ambiguous = _symbols(data, shoff, shentsize, shnum)
|
|
112
|
+
except struct.error as err:
|
|
113
|
+
raise RamRunError(f"truncated ELF file ({err})") from err
|
|
114
|
+
return ElfImage(entry, tuple(segments), symbols, ambiguous)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _symbols(
|
|
118
|
+
data: bytes, shoff: int, shentsize: int, shnum: int
|
|
119
|
+
) -> tuple[dict[str, tuple[int, int]], frozenset[str]]:
|
|
120
|
+
out: dict[str, tuple[int, int]] = {}
|
|
121
|
+
ambiguous: set[str] = set()
|
|
122
|
+
if not shoff or not shnum:
|
|
123
|
+
return out, frozenset()
|
|
124
|
+
if shnum > MAX_SECTIONS:
|
|
125
|
+
raise RamRunError(f"the ELF declares {shnum} sections; refusing to parse it")
|
|
126
|
+
headers = [
|
|
127
|
+
struct.unpack_from("<IIIIIIIIII", data, shoff + i * shentsize) for i in range(shnum)
|
|
128
|
+
]
|
|
129
|
+
for _name, sh_type, _fl, _addr, sh_off, sh_size, sh_link, _info, _align, sh_entsize in headers:
|
|
130
|
+
if sh_type != 2: # SHT_SYMTAB
|
|
131
|
+
continue
|
|
132
|
+
if sh_entsize < ELF32_SYM_SIZE:
|
|
133
|
+
raise RamRunError(
|
|
134
|
+
f"the ELF symbol table declares an entry size of {sh_entsize} B "
|
|
135
|
+
f"(an Elf32_Sym is {ELF32_SYM_SIZE}); refusing to read symbols from it"
|
|
136
|
+
)
|
|
137
|
+
if sh_size // sh_entsize > MAX_SYMBOLS:
|
|
138
|
+
raise RamRunError(
|
|
139
|
+
f"the ELF symbol table has {sh_size // sh_entsize} entries (cap {MAX_SYMBOLS})"
|
|
140
|
+
)
|
|
141
|
+
if sh_link >= len(headers):
|
|
142
|
+
continue
|
|
143
|
+
str_off, str_size = headers[sh_link][4], headers[sh_link][5]
|
|
144
|
+
strtab = data[str_off : str_off + str_size]
|
|
145
|
+
for i in range(sh_size // sh_entsize):
|
|
146
|
+
st_name, st_value, st_size, _info2, _other, _shndx = struct.unpack_from(
|
|
147
|
+
"<IIIBBH", data, sh_off + i * sh_entsize
|
|
148
|
+
)
|
|
149
|
+
end = strtab.find(b"\0", st_name)
|
|
150
|
+
name = strtab[st_name : end if end >= 0 else None].decode("ascii", "replace")
|
|
151
|
+
if name:
|
|
152
|
+
if out.setdefault(name, (st_value, st_size)) != (st_value, st_size):
|
|
153
|
+
ambiguous.add(name)
|
|
154
|
+
return out, frozenset(ambiguous)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ── the image plan ──────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass(frozen=True)
|
|
161
|
+
class RamImage:
|
|
162
|
+
base: int
|
|
163
|
+
entry: int # PC to run from, thumb bit cleared
|
|
164
|
+
initial_sp: int
|
|
165
|
+
reset_vector: int
|
|
166
|
+
size: int
|
|
167
|
+
console: tuple[int, int] | None # (address, size) of ram_console_buf
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
#: The cores tan will RAM-run. The HP core's debug AP selection (AP 0x00200000 vs the
|
|
171
|
+
#: HE's 0x00300000 on evk-01) is not bench-proven through tan, so a slice other than
|
|
172
|
+
#: the HE core is refused rather than risking loading HE's recipe into the wrong core.
|
|
173
|
+
SUPPORTED_CORE = "M55_HE"
|
|
174
|
+
|
|
175
|
+
#: Per-core ITCM global aliases (the recipe's load bases): HE 0x58000000, HP 0x50000000.
|
|
176
|
+
_ITCM_GLOBAL = {"M55_HE": 0x58000000, "M55_HP": 0x50000000}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass(frozen=True)
|
|
180
|
+
class Apertures:
|
|
181
|
+
"""Where a core's code and data may live, with SIZES from the SoC metadata's
|
|
182
|
+
`sram_banks_kb` (never invented): `code` / `data` are `(base, size)` lists."""
|
|
183
|
+
|
|
184
|
+
code: tuple[tuple[int, int], ...]
|
|
185
|
+
data: tuple[tuple[int, int], ...]
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def check_core(core_id: str, base: int) -> None:
|
|
189
|
+
"""Refuse a slice that is not the HE core (`flash.ram-core-unsupported`), and a
|
|
190
|
+
base that is the OTHER core's ITCM alias (`flash.ram-core-mismatch`): an image
|
|
191
|
+
linked for the HP core's 0x50000000 aperture on the HE slice would be loaded into
|
|
192
|
+
whichever core the probe happens to be attached to."""
|
|
193
|
+
core = core_id.upper()
|
|
194
|
+
if core != SUPPORTED_CORE:
|
|
195
|
+
raise RamRunError(
|
|
196
|
+
f"tan flash --ram supports only the M55 HE core for now; '{core_id}' is not "
|
|
197
|
+
"it. The HP core's debug access port selection is not bench-proven through "
|
|
198
|
+
"tan, and loading the HE recipe into the wrong core is not recoverable by a "
|
|
199
|
+
"retry. Use the raw alp-sdk helper for the HP core.",
|
|
200
|
+
CODE_CORE_UNSUPPORTED,
|
|
201
|
+
)
|
|
202
|
+
for other, alias in _ITCM_GLOBAL.items():
|
|
203
|
+
if other != core and base == alias:
|
|
204
|
+
raise RamRunError(
|
|
205
|
+
f"the image is linked for {other}'s ITCM alias 0x{alias:X} but the slice is "
|
|
206
|
+
f"{core}: it would run on whichever core the probe is attached to. Rebuild "
|
|
207
|
+
"it for the HE core.",
|
|
208
|
+
CODE_CORE_MISMATCH,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def apertures_for(core_id: str, banks_kib: Sequence[tuple[str, float]]) -> Apertures | None:
|
|
213
|
+
"""The HE core's apertures from a SoC variant's `sram_banks_kb` (`(name, KiB)`):
|
|
214
|
+
its own ITCM bank at the local `0x0` and the global `0x58000000` alias, its DTCM
|
|
215
|
+
bank at the architectural local `0x20000000`, and `SRAM0` at `0x02000000`.
|
|
216
|
+
`None` when the metadata names no ITCM/DTCM bank for the core -- the caller then
|
|
217
|
+
REFUSES; a size is never guessed."""
|
|
218
|
+
token = core_id.upper()
|
|
219
|
+
itcm = dtcm = sram0 = None
|
|
220
|
+
for name, kib in banks_kib:
|
|
221
|
+
up = name.upper()
|
|
222
|
+
size = int(kib * 1024)
|
|
223
|
+
if token in up and "ITCM" in up and itcm is None:
|
|
224
|
+
itcm = size
|
|
225
|
+
elif token in up and "DTCM" in up and dtcm is None:
|
|
226
|
+
dtcm = size
|
|
227
|
+
elif up == "SRAM0" and sram0 is None:
|
|
228
|
+
sram0 = size
|
|
229
|
+
if itcm is None or dtcm is None or token not in _ITCM_GLOBAL:
|
|
230
|
+
return None
|
|
231
|
+
code = [(0x0, itcm), (_ITCM_GLOBAL[token], itcm)]
|
|
232
|
+
data = [(0x20000000, dtcm)]
|
|
233
|
+
if sram0 is not None:
|
|
234
|
+
code.append((SRAM0_BASE, sram0))
|
|
235
|
+
data.append((SRAM0_BASE, sram0))
|
|
236
|
+
return Apertures(tuple(code), tuple(data))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _within(address: int, length: int, spans: Sequence[tuple[int, int]]) -> bool:
|
|
240
|
+
return any(base <= address and address + length <= base + size for base, size in spans)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _plausible_base(base: int) -> bool:
|
|
244
|
+
return base in (0x0, 0x50000000, 0x58000000) or SRAM0_BASE <= base < 0x03000000
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def plan_ram_image(
|
|
248
|
+
elf: ElfImage,
|
|
249
|
+
binary: bytes,
|
|
250
|
+
*,
|
|
251
|
+
core_id: str | None = None,
|
|
252
|
+
apertures: Apertures | None = None,
|
|
253
|
+
) -> RamImage:
|
|
254
|
+
"""Decide whether `binary` (the raw `zephyr.bin` beside `elf`) can be
|
|
255
|
+
RAM-run and where. Refuses with [`CODE_NOT_RAM_LINKED`] when it is linked
|
|
256
|
+
for MRAM or any base that is not an ITCM/SRAM target, and with
|
|
257
|
+
[`CODE_FAILED`] when the pieces disagree."""
|
|
258
|
+
loadable = [s for s in elf.segments if s.filesz]
|
|
259
|
+
if not loadable:
|
|
260
|
+
raise RamRunError("the ELF has no LOAD segment with file content")
|
|
261
|
+
base = min(s.paddr for s in loadable)
|
|
262
|
+
if base >= MRAM_BASE:
|
|
263
|
+
raise RamRunError(
|
|
264
|
+
f"the image is slot0/MRAM-linked (LOAD segment at 0x{base:X}); loading it "
|
|
265
|
+
"re-enters the ALREADY-RESIDENT MRAM image, not the freshly built one. "
|
|
266
|
+
"Rebuild with the Flow C ITCM retarget (aen-flowc-itcm.conf + "
|
|
267
|
+
"aen-flowc-itcm.overlay) or flash it with Flow D.",
|
|
268
|
+
CODE_NOT_RAM_LINKED,
|
|
269
|
+
)
|
|
270
|
+
if not _plausible_base(base):
|
|
271
|
+
raise RamRunError(
|
|
272
|
+
f"derived load base 0x{base:X} is not a Flow C target (expected 0x0, the ITCM "
|
|
273
|
+
"global alias 0x50000000/0x58000000, or SRAM 0x02xxxxxx) -- a DTCM/data "
|
|
274
|
+
"segment was picked, which would corrupt live RAM. Refusing to load.",
|
|
275
|
+
CODE_NOT_RAM_LINKED,
|
|
276
|
+
)
|
|
277
|
+
if core_id is not None:
|
|
278
|
+
check_core(core_id, base)
|
|
279
|
+
if len(binary) < 8:
|
|
280
|
+
raise RamRunError("zephyr.bin is too small to hold a vector table")
|
|
281
|
+
if apertures is not None and not _within(base, len(binary), apertures.code):
|
|
282
|
+
raise RamRunError(
|
|
283
|
+
f"the {len(binary)} B image at 0x{base:X} does not fit any of the core's code "
|
|
284
|
+
"apertures (" + ", ".join(f"0x{b:X}+0x{n:X}" for b, n in apertures.code) + "; sizes "
|
|
285
|
+
"from the SoC metadata) -- loading it would run past the TCM into live memory.",
|
|
286
|
+
CODE_NOT_RAM_LINKED,
|
|
287
|
+
)
|
|
288
|
+
span = max(s.paddr + s.filesz for s in loadable) - base
|
|
289
|
+
if len(binary) < span:
|
|
290
|
+
raise RamRunError(
|
|
291
|
+
f"zephyr.bin is {len(binary)} B but the ELF's LOAD segments span {span} B from "
|
|
292
|
+
f"0x{base:X} -- the binary and the ELF are from different builds."
|
|
293
|
+
)
|
|
294
|
+
sp, reset = struct.unpack_from("<II", binary, 0)
|
|
295
|
+
entry = reset & ~1
|
|
296
|
+
if entry != elf.entry & ~1:
|
|
297
|
+
raise RamRunError(
|
|
298
|
+
f"the vector table's reset handler (0x{reset:X}) and the ELF entry point "
|
|
299
|
+
f"(0x{elf.entry:X}) disagree -- refusing to guess the PC."
|
|
300
|
+
)
|
|
301
|
+
if sp == 0 or sp >= MRAM_BASE:
|
|
302
|
+
raise RamRunError(f"the vector table's initial SP 0x{sp:X} is not a RAM address.")
|
|
303
|
+
console = None
|
|
304
|
+
sym = elf.symbols.get(CONSOLE_SYMBOL)
|
|
305
|
+
if sym is not None:
|
|
306
|
+
addr, size = sym
|
|
307
|
+
length = min(size or DEFAULT_CONSOLE_BYTES, MAX_CONSOLE_BYTES)
|
|
308
|
+
if apertures is not None and not _within(addr, length, apertures.data):
|
|
309
|
+
raise RamRunError(
|
|
310
|
+
f"{CONSOLE_SYMBOL} (0x{addr:X}+0x{length:X}) is outside the core's DTCM/SRAM "
|
|
311
|
+
"apertures (" + ", ".join(f"0x{b:X}+0x{n:X}" for b, n in apertures.data)
|
|
312
|
+
+ ") -- refusing to read an address the ELF should not have put there."
|
|
313
|
+
)
|
|
314
|
+
console = (addr, length)
|
|
315
|
+
return RamImage(base, entry, sp, reset, len(binary), console)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# ── J-Link scripts ──────────────────────────────────────────────────────────
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
_SAFE_TOKEN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def preamble(serial: str | None, speed: int, device: str) -> list[str]:
|
|
325
|
+
"""Everything up to and including `connect`, shared by both sessions. `serial` and
|
|
326
|
+
`device` must be plain tokens and `speed` an int, or this refuses -- they are
|
|
327
|
+
the only free-form values in either script."""
|
|
328
|
+
if not isinstance(speed, int) or isinstance(speed, bool) or speed <= 0:
|
|
329
|
+
raise RamRunError(f"J-Link speed {speed!r} is not a positive integer")
|
|
330
|
+
for label, value in (("serial", serial), ("device", device)):
|
|
331
|
+
if value is not None and not _SAFE_TOKEN.match(value):
|
|
332
|
+
raise RamRunError(f"the J-Link {label} {value!r} is not a plain token")
|
|
333
|
+
lines = [f"SelectEmuBySN {serial}"] if serial else []
|
|
334
|
+
return [*lines, "si SWD", f"speed {speed}", f"device {device}", "connect"]
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def load_script(
|
|
338
|
+
pre: Sequence[str],
|
|
339
|
+
binary_path: str,
|
|
340
|
+
image: RamImage,
|
|
341
|
+
after_go: Sequence[str] = (),
|
|
342
|
+
hold_ms: int = 0,
|
|
343
|
+
) -> str:
|
|
344
|
+
# Every address below is an `int` rendered with `0x%X`, never a string; the
|
|
345
|
+
# caller passes only a path it staged itself (and already validated).
|
|
346
|
+
"""`connect; halt; loadbin; setpc; go` -- the proven Flow C load session.
|
|
347
|
+
`loadbin` resets the core and re-reads the vector table (SP); `setpc` enters
|
|
348
|
+
the reset handler. No MRAM address appears anywhere. `after_go` are extra read-only
|
|
349
|
+
lines run in the same session once the image is going (`tan.core.ram_watch`).
|
|
350
|
+
|
|
351
|
+
`hold_ms > 0` (experimental hold mode, `TAN_FLASH_RAM_HOLD=1`, tan-cli#1372) keeps the
|
|
352
|
+
session open for that long after `go` with a plain `Sleep <ms>`: no halt, no register
|
|
353
|
+
or DEMCR/DWT write follows `go` (a halt after go+Sleep leaves the core incoherent,
|
|
354
|
+
alp-sdk#2076)."""
|
|
355
|
+
if any(c in binary_path for c in "\r\n\0"):
|
|
356
|
+
raise RamRunError("the image path carries a control character")
|
|
357
|
+
return "\n".join(
|
|
358
|
+
[*pre, "halt", f"loadbin {binary_path} 0x{image.base:X}", f"setpc 0x{image.entry:X}", "go", *after_go,
|
|
359
|
+
*([f"Sleep {int(hold_ms)}"] if hold_ms > 0 else []), "exit"]
|
|
360
|
+
) + "\n"
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def mem8_lines(address: int, size: int) -> list[str]:
|
|
364
|
+
out, off = [], 0
|
|
365
|
+
while off < size:
|
|
366
|
+
chunk = min(MEM8_CHUNK, size - off)
|
|
367
|
+
out.append(f"mem8 0x{address + off:X}, 0x{chunk:X}")
|
|
368
|
+
off += chunk
|
|
369
|
+
return out
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def read_script(pre: Sequence[str], address: int, size: int) -> str:
|
|
373
|
+
return "\n".join([*pre, *mem8_lines(address, size), "exit"]) + "\n"
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
_DUMP_LINE = re.compile(r"^([0-9A-Fa-f]+) = ((?:[0-9A-Fa-f]{2}\s*)+)$")
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def parse_dump(transcript: str) -> dict[int, bytes]:
|
|
380
|
+
"""Every `ADDR = xx xx ...` line of a `mem8` transcript, concatenated per
|
|
381
|
+
contiguous run, keyed by the address of the run's first byte."""
|
|
382
|
+
runs: dict[int, bytearray] = {}
|
|
383
|
+
cursor: tuple[int, int] | None = None # (run start, next address)
|
|
384
|
+
for raw in transcript.splitlines():
|
|
385
|
+
match = _DUMP_LINE.match(raw.strip())
|
|
386
|
+
if not match:
|
|
387
|
+
continue
|
|
388
|
+
addr = int(match.group(1), 16)
|
|
389
|
+
data = bytes(int(b, 16) for b in match.group(2).split())
|
|
390
|
+
if cursor is not None and cursor[1] == addr:
|
|
391
|
+
runs[cursor[0]].extend(data)
|
|
392
|
+
cursor = (cursor[0], addr + len(data))
|
|
393
|
+
else:
|
|
394
|
+
runs[addr] = bytearray(data)
|
|
395
|
+
cursor = (addr, addr + len(data))
|
|
396
|
+
return {k: bytes(v) for k, v in runs.items()}
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def read_back(transcript: str, address: int, size: int) -> bytes:
|
|
400
|
+
"""The `size` bytes at `address` out of a `mem8` transcript, or raise
|
|
401
|
+
`RamRunError` when any part is missing (J-Link can drop one chunk's dump
|
|
402
|
+
silently, alp-sdk#2313)."""
|
|
403
|
+
buf = bytearray(size)
|
|
404
|
+
have = bytearray(size)
|
|
405
|
+
for start, data in parse_dump(transcript).items():
|
|
406
|
+
lo = max(start, address)
|
|
407
|
+
hi = min(start + len(data), address + size)
|
|
408
|
+
if lo < hi:
|
|
409
|
+
buf[lo - address : hi - address] = data[lo - start : hi - start]
|
|
410
|
+
have[lo - address : hi - address] = b"\x01" * (hi - lo)
|
|
411
|
+
if not all(have):
|
|
412
|
+
missing = have.index(0)
|
|
413
|
+
raise RamRunError(
|
|
414
|
+
f"the RAM console read is incomplete: no dump for 0x{address + missing:X} "
|
|
415
|
+
f"(of {size} bytes at 0x{address:X})"
|
|
416
|
+
)
|
|
417
|
+
return bytes(buf)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def decode_console(data: bytes) -> str:
|
|
421
|
+
"""The script's awk: stop after more than four consecutive NULs, skip a NUL
|
|
422
|
+
run, newline for CR/LF, printable ASCII kept, everything else dropped."""
|
|
423
|
+
out: list[str] = []
|
|
424
|
+
nul = 0
|
|
425
|
+
for b in data:
|
|
426
|
+
if b == 0:
|
|
427
|
+
nul += 1
|
|
428
|
+
if nul > 4:
|
|
429
|
+
break
|
|
430
|
+
continue
|
|
431
|
+
nul = 0
|
|
432
|
+
if b in (10, 13):
|
|
433
|
+
out.append("\n")
|
|
434
|
+
elif 32 <= b < 127:
|
|
435
|
+
out.append(chr(b))
|
|
436
|
+
return "".join(out)
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
# ── transcript checks ───────────────────────────────────────────────────────
|
|
440
|
+
|
|
441
|
+
_CONNECT_FAILURES = ("Cannot connect", "Could not connect", "Failed to power up DAP")
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _window(transcript: str, echo: str) -> str | None:
|
|
445
|
+
"""What J-Link printed between the echo of `echo` and the next prompt, or
|
|
446
|
+
`None` when the command was never echoed (a stub, or a quiet build)."""
|
|
447
|
+
lines = transcript.splitlines()
|
|
448
|
+
for i, line in enumerate(lines):
|
|
449
|
+
if line.strip() == f"J-Link>{echo}":
|
|
450
|
+
out = []
|
|
451
|
+
for follow in lines[i + 1 :]:
|
|
452
|
+
if follow.startswith("J-Link>"):
|
|
453
|
+
break
|
|
454
|
+
out.append(follow)
|
|
455
|
+
return "\n".join(out)
|
|
456
|
+
return None
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def check_session(transcript: str, *, loadbin: bool) -> str | None:
|
|
460
|
+
"""The refusals `ram-run.sh` makes on a session transcript: a connect that
|
|
461
|
+
did not happen, a session that never reached `Script processing
|
|
462
|
+
completed.` (the J-Link process died mid-way -- NOT a reported command
|
|
463
|
+
failure), a `loadbin` that did not say `O.K.`, or a rejected `setpc`/`go`.
|
|
464
|
+
|
|
465
|
+
For the LOAD session (`loadbin=True`) the transcript must PROVE the load: the
|
|
466
|
+
`loadbin` echo with an `O.K.` after it, a `setpc` echo with nothing but blank
|
|
467
|
+
lines after it, and a `go` echo with at most the memory-map banner after it. A
|
|
468
|
+
transcript that does not echo them cannot confirm the load -- a STALE image
|
|
469
|
+
already in ITCM could otherwise boot and be read back as this run's -- so it is
|
|
470
|
+
refused ("cannot confirm the load"). `None` when the transcript is clean."""
|
|
471
|
+
for marker in _CONNECT_FAILURES:
|
|
472
|
+
if marker in transcript:
|
|
473
|
+
return f"J-Link reported `{marker}`"
|
|
474
|
+
if "Script processing completed." not in transcript:
|
|
475
|
+
return (
|
|
476
|
+
"the J-Link transcript has no 'Script processing completed.' line -- the "
|
|
477
|
+
"process crashed, was killed or was truncated before finishing"
|
|
478
|
+
)
|
|
479
|
+
if not loadbin:
|
|
480
|
+
return None
|
|
481
|
+
echoes = {
|
|
482
|
+
word: next(
|
|
483
|
+
(ln[len("J-Link>") :] for ln in transcript.splitlines() if ln.startswith(f"J-Link>{word} ")
|
|
484
|
+
or ln.strip() == f"J-Link>{word}"),
|
|
485
|
+
None,
|
|
486
|
+
)
|
|
487
|
+
for word in ("loadbin", "setpc", "go")
|
|
488
|
+
}
|
|
489
|
+
missing = [w for w, e in echoes.items() if e is None]
|
|
490
|
+
if missing:
|
|
491
|
+
return (
|
|
492
|
+
"cannot confirm the load: the J-Link transcript has no echo of "
|
|
493
|
+
+ ", ".join(missing) + " (expected `J-Link>loadbin ...`, `J-Link>setpc ...`, `J-Link>go`)"
|
|
494
|
+
)
|
|
495
|
+
load = _window(transcript, echoes["loadbin"])
|
|
496
|
+
if load is None or "O.K." not in load.split():
|
|
497
|
+
return "loadbin did not report 'O.K.' -- refusing to treat this as a fresh load"
|
|
498
|
+
bad = _window(transcript, echoes["setpc"])
|
|
499
|
+
if bad and bad.strip():
|
|
500
|
+
return f"setpc was rejected: {bad.strip()}"
|
|
501
|
+
go = _window(transcript, echoes["go"]) or ""
|
|
502
|
+
if any(
|
|
503
|
+
ln.strip()
|
|
504
|
+
and ln.strip() != "Script processing completed."
|
|
505
|
+
and not re.fullmatch(r"Memory map '.*' is active", ln.strip())
|
|
506
|
+
for ln in go.splitlines()
|
|
507
|
+
):
|
|
508
|
+
return f"go was rejected: {go.strip()}"
|
|
509
|
+
return None
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
_AP_ADDR = re.compile(r"AP\[(\d+)\]\s*\(APAddr\s+(0x[0-9A-Fa-f]+)\)")
|
|
513
|
+
_AP_CORE_FOUND = re.compile(r"AP\[(\d+)\]:\s*Core found")
|
|
514
|
+
_CPUID = re.compile(r"CPUID register:\s*(0x[0-9A-Fa-f]+)")
|
|
515
|
+
_FOUND = re.compile(r"^Found Cortex-M\S*.*$", re.MULTILINE)
|
|
516
|
+
|
|
517
|
+
#: The debug access ports of the two M55s on the Ensemble E8, from the bench: HE AP
|
|
518
|
+
#: 0x00300000, HP AP 0x00200000 (alp-sdk scripts/bench/aen/openocd-ram-run.sh:16-17;
|
|
519
|
+
#: changelog.d/2037-openocd-m55he-bench-core-selection.md:4 "0x00200000 is the M55-HP,
|
|
520
|
+
#: not the HE, and 0x00300000 is the HE"; changelog.d/2025.md:31 `AP[3] (APAddr
|
|
521
|
+
#: 0x00300000)` is the AHB-AP carrying the M55 debug).
|
|
522
|
+
HE_AP_ADDR = 0x00300000
|
|
523
|
+
HP_AP_ADDR = 0x00200000
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def attached_core(transcript: str) -> dict | None:
|
|
527
|
+
"""Which core J-Link attached to, from its own `connect` banner: the AP that reports
|
|
528
|
+
`AP[n]: Core found` (with its `APAddr`), the `CPUID register` and the `Found
|
|
529
|
+
Cortex-M55 ...` line. The `APAddr` of the Core-found AP is what tells HE from HP --
|
|
530
|
+
the `Found Cortex-M55 r1p0` line alone is identical for both. `None` when the
|
|
531
|
+
transcript names no Core-found AP. More than one distinct Core-found AP is
|
|
532
|
+
reported as `multiple`."""
|
|
533
|
+
addrs = {int(m.group(1)): m.group(2) for m in _AP_ADDR.finditer(transcript)}
|
|
534
|
+
found = [int(m.group(1)) for m in _AP_CORE_FOUND.finditer(transcript)]
|
|
535
|
+
cpuid = _CPUID.search(transcript)
|
|
536
|
+
line = _FOUND.search(transcript)
|
|
537
|
+
if not found and not cpuid and not line:
|
|
538
|
+
return None
|
|
539
|
+
ports = sorted(set(found))
|
|
540
|
+
out: dict = {
|
|
541
|
+
"coreFoundAp": ports[0] if len(ports) == 1 else (ports or None),
|
|
542
|
+
"apAddr": addrs.get(ports[0]) if len(ports) == 1 else None,
|
|
543
|
+
"cpuid": cpuid.group(1) if cpuid else None,
|
|
544
|
+
"found": line.group(0).strip() if line else None,
|
|
545
|
+
}
|
|
546
|
+
if len(ports) > 1:
|
|
547
|
+
out["multiple"] = [addrs.get(p) for p in ports]
|
|
548
|
+
return out
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def ap_verdict(attached: dict | None) -> str:
|
|
552
|
+
"""`he` / `hp` from the Core-found AP's address; `multiple` / `unidentified` otherwise."""
|
|
553
|
+
if not attached:
|
|
554
|
+
return "unidentified"
|
|
555
|
+
if "multiple" in attached:
|
|
556
|
+
# More than one Core-found AP. If ANY of them is the HP's, that is HP evidence
|
|
557
|
+
# (non-overridable); otherwise it is merely unplaceable.
|
|
558
|
+
for addr in attached["multiple"]:
|
|
559
|
+
try:
|
|
560
|
+
if addr is not None and int(addr, 16) == HP_AP_ADDR:
|
|
561
|
+
return "hp"
|
|
562
|
+
except ValueError:
|
|
563
|
+
continue
|
|
564
|
+
return "multiple"
|
|
565
|
+
try:
|
|
566
|
+
addr = int(attached["apAddr"], 16)
|
|
567
|
+
except (TypeError, ValueError):
|
|
568
|
+
return "unidentified"
|
|
569
|
+
return {HE_AP_ADDR: "he", HP_AP_ADDR: "hp"}.get(addr, "unidentified")
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
# ── which core did the generic attach land on? (tan-cli#1354) ───────────────
|
|
573
|
+
#
|
|
574
|
+
# J-Link's "Found Cortex-M55 r1p0" is the same line for the HE and the HP core, and a
|
|
575
|
+
# generic `Cortex-M55` attach takes whichever M55 access port it finds. The DECISION
|
|
576
|
+
# rests on the access port: the AP that reports `AP[n]: Core found`, whose APAddr is
|
|
577
|
+
# 0x00300000 for the HE and 0x00200000 for the HP.
|
|
578
|
+
#
|
|
579
|
+
# CORROBORATION is a single read-only `mem32` pair: a core's LOCAL ITCM at 0x0 is its own
|
|
580
|
+
# global window, HE 0x58000000 (alp-sdk metadata/socs/alif/ensemble/e8.json
|
|
581
|
+
# `itcm_global_base` 1476395008 at line 106; docs/aen-bench-bringup.md:20 "loadAddress=
|
|
582
|
+
# 0x50000000 = HP ITCM global, vs HE's 0x58000000"). The HP window 0x50000000 is NEVER
|
|
583
|
+
# read, and this is MEASURED, not cautious. Bench round 8 (2026-10-07, evk-02, AEN803
|
|
584
|
+
# 2026W36-0001, read-only A/B): A2 = `mem32 0x0` + `mem32 0x58000000` ONLY -> A3 `h` OK
|
|
585
|
+
# (DHCSR 00130003). A4 = the full check INCLUDING `mem32 0x50000000` -> A5/A6 `h` FAILS
|
|
586
|
+
# (`WARNING: CPU could not be halted`, DHCSR 00110003) and the core stays unhaltable until a
|
|
587
|
+
# PIN reset (C0-C2 recovered it); the read itself returned words with no error, and the later
|
|
588
|
+
# load only worked through the fallback chain ending in `SYSRESETREQ has confused core` ->
|
|
589
|
+
# VECTRESET. The shipped script is exactly the A2 reads: the local ITCM 0x0 and the HE
|
|
590
|
+
# window, nothing else.
|
|
591
|
+
|
|
592
|
+
HE_ALIAS = _ITCM_GLOBAL["M55_HE"]
|
|
593
|
+
CORE_CHECK_WORDS = 4
|
|
594
|
+
|
|
595
|
+
#: Halt/reset trouble J-Link reports in a load transcript. The Flow D reset markers plus
|
|
596
|
+
#: the ones a RAM-run's halt-then-loadbin hits first: a core that would not halt, a core
|
|
597
|
+
#: J-Link could not find, and the SYSRESETREQ-confused fallback (bench round 8).
|
|
598
|
+
RAM_RUN_TROUBLE_MARKERS = (
|
|
599
|
+
"CPU could not be halted",
|
|
600
|
+
"Could not find core",
|
|
601
|
+
"SYSRESETREQ has confused core",
|
|
602
|
+
"Failed to halt CPU",
|
|
603
|
+
"CPU is not halted",
|
|
604
|
+
"Reset: Failed",
|
|
605
|
+
"CPU may have not been reset",
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def trouble_markers(transcript: str) -> tuple[str, ...]:
|
|
610
|
+
"""Every halt/reset-trouble marker present in `transcript`, in marker order."""
|
|
611
|
+
return tuple(m for m in RAM_RUN_TROUBLE_MARKERS if m in transcript)
|
|
612
|
+
|
|
613
|
+
_MEM32_LINE = re.compile(r"^([0-9A-Fa-f]{8}) = ((?:[0-9A-Fa-f]{8}(?:\s+|$))+)$")
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def core_check_script(pre: Sequence[str]) -> str:
|
|
617
|
+
"""The read-only identification session: `connect` (no halt, no loadbin), then
|
|
618
|
+
`mem32` of the local ITCM and of the HE global alias ONLY. Addresses and the word
|
|
619
|
+
count are ints rendered with `0x%X`."""
|
|
620
|
+
reads = [f"mem32 0x{a:X}, 0x{CORE_CHECK_WORDS:X}" for a in (0x0, HE_ALIAS)]
|
|
621
|
+
return "\n".join([*pre, *reads, "exit"]) + "\n"
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def parse_mem32(transcript: str, address: int, count: int = CORE_CHECK_WORDS) -> tuple[int, ...] | None:
|
|
625
|
+
"""The `count` 32-bit words J-Link dumped at `address`, or `None` when the dump is
|
|
626
|
+
missing, short, or not contiguous from `address` (an unreadable window prints
|
|
627
|
+
`Could not read memory.` and no dump line). Strict: a line that does not match
|
|
628
|
+
`ADDR = WWWWWWWW ...` is ignored, never guessed at."""
|
|
629
|
+
words: dict[int, int] = {}
|
|
630
|
+
for raw in transcript.splitlines():
|
|
631
|
+
match = _MEM32_LINE.match(raw.strip())
|
|
632
|
+
if not match:
|
|
633
|
+
continue
|
|
634
|
+
base = int(match.group(1), 16)
|
|
635
|
+
for i, tok in enumerate(match.group(2).split()):
|
|
636
|
+
words.setdefault(base + 4 * i, int(tok, 16))
|
|
637
|
+
wanted = [address + 4 * i for i in range(count)]
|
|
638
|
+
if not all(a in words for a in wanted):
|
|
639
|
+
return None
|
|
640
|
+
return tuple(words[a] for a in wanted)
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def core_verdict(local: tuple[int, ...] | None, he: tuple[int, ...] | None) -> str:
|
|
644
|
+
"""The ITCM corroboration alone: `match` when the local view equals the HE window,
|
|
645
|
+
`disagree` when both read and differ, `unreadable` when either could not be read."""
|
|
646
|
+
if local is None or he is None:
|
|
647
|
+
return "unreadable"
|
|
648
|
+
return "match" if local == he else "disagree"
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def combine_verdicts(ap: str, itcm: str) -> str:
|
|
652
|
+
"""The decision. ONLY the access port can say HE: `he` needs `ap == he` and an ITCM
|
|
653
|
+
that matches or is silent. The ITCM never confirms on its own.
|
|
654
|
+
|
|
655
|
+
* `ap == he`, ITCM `disagree` -> `conflict` (a local view that is NOT the HE window
|
|
656
|
+
on an HE-looking attach is suspicious, and not overridable);
|
|
657
|
+
* `ap == hp` -> `hp` (an ITCM that matches the HE window against an HP access port is
|
|
658
|
+
reported as `conflict-hp`: still HP evidence, still never overridable);
|
|
659
|
+
* no AP evidence (`unidentified` / `multiple`) -> `unidentified`."""
|
|
660
|
+
if ap == "he":
|
|
661
|
+
return "conflict" if itcm == "disagree" else "he"
|
|
662
|
+
if ap == "hp":
|
|
663
|
+
return "conflict-hp" if itcm == "match" else "hp"
|
|
664
|
+
return "unidentified"
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
#: Verdicts `--assume-he` may override: the evidence is MISSING, not contrary. Anything
|
|
668
|
+
#: that involves HP evidence, or a contradiction on an HE attach, is never overridable.
|
|
669
|
+
OVERRIDABLE_VERDICTS = frozenset({"unidentified", "unreadable"})
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def core_check(transcript: str) -> tuple[str, dict]:
|
|
673
|
+
"""`(verdict, evidence)` for a [`core_check_script`] transcript: the Core-found AP
|
|
674
|
+
(decisive) combined with the ITCM words (corroboration). `evidence` is the envelope's
|
|
675
|
+
`ram.coreCheck` body."""
|
|
676
|
+
local = parse_mem32(transcript, 0x0)
|
|
677
|
+
he = parse_mem32(transcript, HE_ALIAS)
|
|
678
|
+
|
|
679
|
+
def hexed(words):
|
|
680
|
+
return None if words is None else [f"0x{w:08X}" for w in words]
|
|
681
|
+
|
|
682
|
+
attached = attached_core(transcript)
|
|
683
|
+
ap = ap_verdict(attached)
|
|
684
|
+
itcm = core_verdict(local, he)
|
|
685
|
+
evidence = {
|
|
686
|
+
"ap": attached,
|
|
687
|
+
"apVerdict": ap,
|
|
688
|
+
"itcmWords": {
|
|
689
|
+
"local0x00000000": hexed(local),
|
|
690
|
+
f"he0x{HE_ALIAS:08X}": hexed(he),
|
|
691
|
+
},
|
|
692
|
+
"itcmVerdict": itcm,
|
|
693
|
+
}
|
|
694
|
+
return combine_verdicts(ap, itcm), evidence
|