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/dp_id.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Pure SW-DP ID banner parsing for the read-only DPIDR preflight (tan-cli#520
|
|
3
|
+
generalised this from Flow D-only; tan-cli#795 split it out of `tan.core.
|
|
4
|
+
flash_plan` on its own, since these functions are pure string/bool logic that
|
|
5
|
+
nothing in `flash_plan.py` itself calls -- only `tan.commands.flash_cmd.
|
|
6
|
+
_flow_d_preflight`, the one caller that spawns `JLinkExe` and reads its
|
|
7
|
+
banner, does. `flash_plan.py` keeps `_validate_expect_dpidr_width` (it raises
|
|
8
|
+
`FlashPlanError`, defined there); this module has no IO and raises nothing.
|
|
9
|
+
|
|
10
|
+
Extracted verbatim (names unchanged, including the leading underscore --
|
|
11
|
+
matches this codebase's existing cross-module convention for a name that is
|
|
12
|
+
implementation-private to the *feature*, not to the *file*, e.g. `flash_plan.
|
|
13
|
+
_DEV_ROOT` imported straight into `flash_cmd.py`), no behaviour change -- see
|
|
14
|
+
tan-cli#312/#369/#373/#795 in the docstrings below for the history of each
|
|
15
|
+
refusal shape this parsing feeds.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _dp_id_matches(expected: str, banner: str) -> bool:
|
|
23
|
+
"""Whether the banner's reported SW-DP ID equals `expected`, compared as
|
|
24
|
+
PARSED 32-bit values (tan-cli#795) rather than the prior unanchored
|
|
25
|
+
substring match (`_hex_in`, since removed): that match accepted any
|
|
26
|
+
truncation of `expected` that happened to appear in the banner --
|
|
27
|
+
`0x2477` matched `Found SW-DP with ID 0x0BE12477`, and `0x477` (ARM's own
|
|
28
|
+
JEP106 designer field, shared by every ARM SW-DP) matched every ARM
|
|
29
|
+
board's banner. The caller already refuses a short `expected` via
|
|
30
|
+
`flash_plan.validate_flow_d_preflight_args`'s own width check
|
|
31
|
+
(`flash_plan._validate_expect_dpidr_width`, run at plan time, before this
|
|
32
|
+
ever executes -- see `tan.commands.flash_cmd._flow_d_preflight`);
|
|
33
|
+
`_dp_id_value` extracts the banner's own reported ID the same way the
|
|
34
|
+
mismatch-reporting branch below already does."""
|
|
35
|
+
actual = _dp_id_value(banner)
|
|
36
|
+
if actual is None:
|
|
37
|
+
return False
|
|
38
|
+
return int(actual, 16) == int(expected, 16)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
#: SEGGER's own wording for a successful SWD connect that read AN id, whatever
|
|
42
|
+
#: it turned out to be -- "Found SW-DP with ID 0x........" / "DPIDR: 0x........".
|
|
43
|
+
#: Matched loosely on purpose: what this distinguishes is "a real board
|
|
44
|
+
#: answered with a different identity" from "nothing answered", not the exact
|
|
45
|
+
#: firmware/DLL version's phrasing. The hex value is its own capture group
|
|
46
|
+
#: (tan-cli#512) so `_dp_id_value` can report what was ACTUALLY read, not only
|
|
47
|
+
#: whether something was -- `_dp_id_reported` still just asks whether this
|
|
48
|
+
#: matches at all.
|
|
49
|
+
_DP_ID_RE = re.compile(r"(?:with\s+ID|DPIDR)\s*:?\s*(0x[0-9A-Fa-f]+)", re.IGNORECASE)
|
|
50
|
+
|
|
51
|
+
#: SEGGER's own wording for the PROBE itself refusing the connection outright
|
|
52
|
+
#: -- measured verbatim on the rc3 bench run: "Connecting to J-Link ...FAILED:
|
|
53
|
+
#: Cannot connect to the probe/programmer." (tan-cli#312). Deliberately NOT a
|
|
54
|
+
#: bare `FAILED`/`Cannot connect` match (that was the tan-cli#312 review
|
|
55
|
+
#: finding): JLinkExe prints "FAILED" in many contexts, and "Cannot connect"
|
|
56
|
+
#: alone also fires on a TARGET-level refusal -- see `_CONNECT_FAILED_TARGET_RE`
|
|
57
|
+
#: below -- which is a real wiring/probe-selection problem, not a re-enumerating
|
|
58
|
+
#: probe.
|
|
59
|
+
_CONNECT_FAILED_RE = re.compile(r"Cannot connect to the probe/programmer", re.IGNORECASE)
|
|
60
|
+
|
|
61
|
+
#: SEGGER's own wording for a TARGET-level connect refusal -- "Cannot connect
|
|
62
|
+
#: to target." (unplugged SWD ribbon, unpowered board) or "Cannot connect to
|
|
63
|
+
#: J-Link." (a probe that IS reachable via USB but refuses the requested
|
|
64
|
+
#: `jlink_serial`). Both are genuine wiring/probe-selection problems, so their
|
|
65
|
+
#: presence forces `_connect_failed_outright` to False even alongside the
|
|
66
|
+
#: probe-level phrase above -- asserting "wiring is fine" here would be the
|
|
67
|
+
#: false negative tan-cli#312's review flagged (measured against a real
|
|
68
|
+
#: unplugged-ribbon and a real unpowered-target banner).
|
|
69
|
+
_CONNECT_FAILED_TARGET_RE = re.compile(r"Cannot connect to (?:target|J-Link)\b", re.IGNORECASE)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _dp_id_reported(banner: str) -> bool:
|
|
73
|
+
"""Whether the banner names ANY SW-DP ID -- not whether it matches
|
|
74
|
+
`expected` (the caller already ruled that out via `_dp_id_matches`), only
|
|
75
|
+
whether a connect got far enough to read one at all."""
|
|
76
|
+
return _DP_ID_RE.search(banner) is not None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _dp_id_value(banner: str) -> str | None:
|
|
80
|
+
"""The actual SW-DP ID text the banner reported, verbatim (whatever hex
|
|
81
|
+
casing/`0x` spelling SEGGER printed), or `None` if `_DP_ID_RE` does not
|
|
82
|
+
match at all. tan-cli#512: a caller that already knows `_dp_id_reported(
|
|
83
|
+
banner)` is `True` gets a non-`None` value here by construction -- both
|
|
84
|
+
read the same regex against the same banner."""
|
|
85
|
+
match = _DP_ID_RE.search(banner)
|
|
86
|
+
return match.group(1) if match else None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _connect_failed_outright(banner: str) -> bool:
|
|
90
|
+
"""Whether the banner carries SEGGER's own wording for the PROBE itself
|
|
91
|
+
refusing the connection (still re-enumerating, no board reachable at all),
|
|
92
|
+
as opposed to a TARGET-level refusal -- a real wiring/probe-selection
|
|
93
|
+
problem that must keep the original remediation, not the re-enumeration
|
|
94
|
+
one."""
|
|
95
|
+
if _CONNECT_FAILED_TARGET_RE.search(banner) is not None:
|
|
96
|
+
return False
|
|
97
|
+
return _CONNECT_FAILED_RE.search(banner) is not None
|
tan/core/elf_load.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""The lowest load address of an ELF image -- pure, stdlib-only.
|
|
3
|
+
|
|
4
|
+
`tan build --board` (tan-cli#1370) needs one fact about a Zephyr ELF to decide
|
|
5
|
+
how the image may be run: whether it is linked into the TCM (its lowest
|
|
6
|
+
`PT_LOAD` sits below the SoC's flash base) or into flash. This reads the program
|
|
7
|
+
headers and nothing else; it never raises on a file that is not an ELF.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import struct
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
_PT_LOAD = 1
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def lowest_load_address(path: str | Path) -> int | None:
|
|
18
|
+
"""Lowest `p_paddr` among `PT_LOAD` segments that carry bytes (`p_filesz > 0`).
|
|
19
|
+
|
|
20
|
+
A NOBITS `.bss` segment (filesz 0) sits in RAM whatever the image is linked
|
|
21
|
+
to, so counting it would call every flash image a RAM image. `None` means
|
|
22
|
+
UNCLASSIFIABLE -- unreadable, not a little-endian ELF32/ELF64, a malformed
|
|
23
|
+
header table, or no loadable segment with bytes -- and never raises."""
|
|
24
|
+
try:
|
|
25
|
+
data = Path(path).read_bytes()
|
|
26
|
+
return _lowest(data)
|
|
27
|
+
except (OSError, struct.error, ValueError):
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _lowest(data: bytes) -> int | None:
|
|
32
|
+
if len(data) < 52 or data[:4] != b"\x7fELF" or data[5] != 1:
|
|
33
|
+
return None
|
|
34
|
+
if data[4] == 1: # ELF32
|
|
35
|
+
min_ent, word = 32, "<I"
|
|
36
|
+
phoff, = struct.unpack_from("<I", data, 28)
|
|
37
|
+
phentsize, phnum = struct.unpack_from("<HH", data, 42)
|
|
38
|
+
paddr_at, filesz_at = 12, 16
|
|
39
|
+
elif data[4] == 2: # ELF64
|
|
40
|
+
min_ent, word = 56, "<Q"
|
|
41
|
+
if len(data) < 64:
|
|
42
|
+
return None
|
|
43
|
+
phoff, = struct.unpack_from("<Q", data, 32)
|
|
44
|
+
phentsize, phnum = struct.unpack_from("<HH", data, 54)
|
|
45
|
+
paddr_at, filesz_at = 24, 32
|
|
46
|
+
else:
|
|
47
|
+
return None
|
|
48
|
+
if phentsize < min_ent or phnum == 0:
|
|
49
|
+
return None
|
|
50
|
+
lowest: int | None = None
|
|
51
|
+
for i in range(phnum):
|
|
52
|
+
off = phoff + i * phentsize
|
|
53
|
+
if off + phentsize > len(data):
|
|
54
|
+
return None
|
|
55
|
+
p_type, = struct.unpack_from("<I", data, off)
|
|
56
|
+
if p_type != _PT_LOAD:
|
|
57
|
+
continue
|
|
58
|
+
filesz, = struct.unpack_from(word, data, off + filesz_at)
|
|
59
|
+
if filesz == 0:
|
|
60
|
+
continue
|
|
61
|
+
paddr, = struct.unpack_from(word, data, off + paddr_at)
|
|
62
|
+
lowest = paddr if lowest is None else min(lowest, paddr)
|
|
63
|
+
return lowest
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""The alp-sdk error/diagnostic catalogue, as `tan explain --code` reads it.
|
|
3
|
+
|
|
4
|
+
Port of the lookup half of alp-sdk's `scripts/alp_cli/explain.py` (ADR-0020
|
|
5
|
+
end-state B: the verb moves to `tan`, it does not disappear). That command is
|
|
6
|
+
the only reader of `<sdk>/metadata/error-catalog.json` today, and retiring it
|
|
7
|
+
without this port would leave every `ALP-Bxxx` landing page and every
|
|
8
|
+
`ALP_ERR_*` enum comment unreachable from any CLI.
|
|
9
|
+
|
|
10
|
+
**The source is the generated catalogue, not the markdown.** The task this
|
|
11
|
+
was written for described `explain.py` as reading `docs/diagnostics/ALP-B*.md`
|
|
12
|
+
and the `ALP_ERR_*` enum directly. Measured against alp-sdk `origin/dev`, it
|
|
13
|
+
does not: it reads `metadata/error-catalog.json` and nothing else
|
|
14
|
+
(`explain.py:24-25,39-42`). Those two sources are what
|
|
15
|
+
`scripts/gen_error_catalog.py` reads, and that generator stays in alp-sdk --
|
|
16
|
+
it is a `pr-generated-files.yml` drift gate over alp-sdk's own headers and
|
|
17
|
+
docs, not a user command, so ADR-0020 does not move it. tan reading the
|
|
18
|
+
generated JSON is also the only shape compatible with I-26/ADR-0017: tan
|
|
19
|
+
learns no hardware fact and parses no C header, it reads one JSON file out of
|
|
20
|
+
the bound checkout the same way `presets` reads `metadata/e1m_modules/`.
|
|
21
|
+
|
|
22
|
+
Pure apart from the one file read, so the lookup, the suggestion list and the
|
|
23
|
+
rendered field order are unit-testable without a checkout on disk.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import difflib
|
|
29
|
+
import json
|
|
30
|
+
import os
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
#: `<sdk_root>/metadata/error-catalog.json`, as path segments -- the same
|
|
34
|
+
#: shape `tan.core.shapes.SDK_MARKER` uses so a caller may join it either way.
|
|
35
|
+
CATALOG_RELATIVE = ("metadata", "error-catalog.json")
|
|
36
|
+
|
|
37
|
+
#: Rendered field order and label, verbatim from `explain.py`'s `_FIELD_ORDER`.
|
|
38
|
+
#: A field the catalogue omits (an `api-error` carries no `cause`/`fix`) is
|
|
39
|
+
#: SKIPPED, never rendered empty -- the generator's own "never fabricate a
|
|
40
|
+
#: field that has no source" rule, preserved on the reading side.
|
|
41
|
+
FIELD_ORDER: tuple[tuple[str, str], ...] = (
|
|
42
|
+
("summary", "summary"),
|
|
43
|
+
("cause", "cause"),
|
|
44
|
+
("fix", "fix"),
|
|
45
|
+
("doc", "doc"),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
#: `difflib.get_close_matches` tuning, verbatim from `explain.py:87-88`. Kept
|
|
49
|
+
#: as named constants because the SUGGESTION LIST is user-visible output: a
|
|
50
|
+
#: different cutoff silently changes what a typo is answered with.
|
|
51
|
+
SUGGESTION_LIMIT = 5
|
|
52
|
+
SUGGESTION_CUTOFF = 0.4
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class CatalogUnreadable(Exception):
|
|
56
|
+
"""The bound checkout has no readable `metadata/error-catalog.json`.
|
|
57
|
+
|
|
58
|
+
Raised for an absent file, an unreadable one, malformed JSON, and a
|
|
59
|
+
document whose top level is not an object -- one exception for all four
|
|
60
|
+
because the caller's answer is the same in every case: name the path it
|
|
61
|
+
looked at and say the checkout cannot answer, rather than guessing which
|
|
62
|
+
of alp-sdk's several possible states produced it.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(self, path: Path, detail: str) -> None:
|
|
66
|
+
super().__init__(f"{path}: {detail}")
|
|
67
|
+
self.path = path
|
|
68
|
+
self.detail = detail
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def catalog_path(sdk_root: Path | str) -> Path:
|
|
72
|
+
"""`<sdk_root>/metadata/error-catalog.json`."""
|
|
73
|
+
return Path(os.path.join(str(sdk_root), *CATALOG_RELATIVE))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def load_codes(sdk_root: Path | str) -> dict[str, dict]:
|
|
77
|
+
"""The `{code: entry}` map from the bound checkout's catalogue.
|
|
78
|
+
|
|
79
|
+
Raises `CatalogUnreadable`; never returns a partial map. A document that
|
|
80
|
+
parses but carries no `codes` key yields `{}` rather than raising, exactly
|
|
81
|
+
as `explain.py`'s `data.get("codes", {})` does -- an empty catalogue is a
|
|
82
|
+
catalogue in which every code is unknown, which the miss path already
|
|
83
|
+
reports honestly.
|
|
84
|
+
|
|
85
|
+
Every VALUE in `codes` is also checked here, not just `codes` itself: a
|
|
86
|
+
catalogue shaped `{"codes": {"ALP-B003": "not an object"}}` (also a list,
|
|
87
|
+
also `null`) otherwise reaches `lookup`, matches on `ALP-B003`, and
|
|
88
|
+
`resolve_code`'s `entry = codes[key]` then hands a non-dict to
|
|
89
|
+
`summary_line`/`detail_lines`, both of which call `entry.get(...)`.
|
|
90
|
+
Measured with this guard removed: `AttributeError: 'str' object has no
|
|
91
|
+
attribute 'get'`, surfacing as `explain.internal-failure` at exit 5 for a
|
|
92
|
+
checkout problem the reader could fix -- the same exit-1-not-exit-5
|
|
93
|
+
concern `codes` itself is guarded for, just one dereference deeper.
|
|
94
|
+
"""
|
|
95
|
+
path = catalog_path(sdk_root)
|
|
96
|
+
try:
|
|
97
|
+
raw = path.read_text(encoding="utf-8")
|
|
98
|
+
except FileNotFoundError:
|
|
99
|
+
raise CatalogUnreadable(
|
|
100
|
+
path,
|
|
101
|
+
"error catalog not found -- run `python3 scripts/gen_error_catalog.py` "
|
|
102
|
+
"in the alp-sdk checkout",
|
|
103
|
+
) from None
|
|
104
|
+
except (OSError, UnicodeDecodeError) as err:
|
|
105
|
+
raise CatalogUnreadable(path, f"error catalog is unreadable: {err}") from None
|
|
106
|
+
try:
|
|
107
|
+
data = json.loads(raw)
|
|
108
|
+
except ValueError as err:
|
|
109
|
+
raise CatalogUnreadable(path, f"error catalog is not valid JSON: {err}") from None
|
|
110
|
+
if not isinstance(data, dict):
|
|
111
|
+
raise CatalogUnreadable(path, "error catalog is not a JSON object")
|
|
112
|
+
codes = data.get("codes", {})
|
|
113
|
+
if not isinstance(codes, dict):
|
|
114
|
+
raise CatalogUnreadable(path, "error catalog's `codes` is not a JSON object")
|
|
115
|
+
for code, entry in codes.items():
|
|
116
|
+
if not isinstance(entry, dict):
|
|
117
|
+
raise CatalogUnreadable(
|
|
118
|
+
path, f"error catalog entry '{code}' is not a JSON object"
|
|
119
|
+
)
|
|
120
|
+
return codes
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def normalise(code: str) -> str:
|
|
124
|
+
"""Canonicalise user input for matching -- `explain.py`'s `_normalise`.
|
|
125
|
+
|
|
126
|
+
Case-insensitive and whitespace-trimmed, so `alp-b003`, ` ALP-B003 ` and
|
|
127
|
+
`alp_err_no_backend` all resolve.
|
|
128
|
+
"""
|
|
129
|
+
return code.strip().upper()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def lookup(codes: dict[str, dict], query: str) -> tuple[str | None, list[str]]:
|
|
133
|
+
"""`(matched_key, suggestions)` for `query` against `codes`.
|
|
134
|
+
|
|
135
|
+
On a hit the suggestion list is empty; on a miss the key is `None` and the
|
|
136
|
+
suggestions are `explain.py`'s own `difflib` shortlist, mapped back to the
|
|
137
|
+
catalogue's real spelling. Both halves are returned together because the
|
|
138
|
+
caller needs exactly one of them and deciding which is this function's job.
|
|
139
|
+
"""
|
|
140
|
+
by_upper = {normalise(key): key for key in codes}
|
|
141
|
+
wanted = normalise(query)
|
|
142
|
+
key = by_upper.get(wanted)
|
|
143
|
+
if key is not None:
|
|
144
|
+
return key, []
|
|
145
|
+
close = difflib.get_close_matches(
|
|
146
|
+
wanted, list(by_upper), n=SUGGESTION_LIMIT, cutoff=SUGGESTION_CUTOFF
|
|
147
|
+
)
|
|
148
|
+
return None, [by_upper[match] for match in close]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def summary_line(entry: dict) -> str:
|
|
152
|
+
"""`<CODE> (<kind>)` -- the header `explain.py`'s `_render` paints cyan.
|
|
153
|
+
|
|
154
|
+
ONE space, where `explain.py` writes two (`f"{code} ({kind})"`): tan's
|
|
155
|
+
`explain` summary is `"<label> (<id>)"` for every other selector kind and a
|
|
156
|
+
per-mode spacing rule inside one command is a worse surface than a
|
|
157
|
+
one-character divergence. The CODE and the KIND are verbatim.
|
|
158
|
+
"""
|
|
159
|
+
return f"{entry.get('code', '')} ({entry.get('kind', '')})".strip()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def detail_lines(entry: dict) -> list[str]:
|
|
163
|
+
"""`<label>: <value>` per populated field, in `FIELD_ORDER`.
|
|
164
|
+
|
|
165
|
+
The values are verbatim catalogue text -- no re-wrapping, no markdown
|
|
166
|
+
stripping, no truncation. `explain_cmd` prefixes each with `- ` and wraps
|
|
167
|
+
it exactly as it wraps a template's detail lines.
|
|
168
|
+
"""
|
|
169
|
+
return [
|
|
170
|
+
f"{label}: {entry[key]}" for key, label in FIELD_ORDER if entry.get(key)
|
|
171
|
+
]
|