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/setools.py
ADDED
|
@@ -0,0 +1,608 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""SETOOLS integration for the Flow D (`alif_mram_jlink`) slot0 sign step --
|
|
3
|
+
tan-cli#353's remaining half.
|
|
4
|
+
|
|
5
|
+
Both host paths that put a signed image into an Alif Ensemble part's MRAM
|
|
6
|
+
(`tan.core.flash_plan`'s Flow A/Flow D) need Alif's SETOOLS `app-gen-toc` step
|
|
7
|
+
to sign the ATOC first; alp-sdk's own manifest never carries a signed blob --
|
|
8
|
+
measured on a fresh AEN801 emit, `flash_args` holds only
|
|
9
|
+
`jlink_flash_device`. Before this module, that meant a customer signed
|
|
10
|
+
OUTSIDE tan (alp-sdk's `docs/aen-provisioning.md` §3-4 -- that path is not in
|
|
11
|
+
THIS repo; alp-sdk is where it lives) and hand-edited the manifest
|
|
12
|
+
with the resulting `atoc`/`atoc_address` before `tan flash` would do anything
|
|
13
|
+
-- `flash_plan.plan_alif_mram_jlink`'s "both required" refusal names the
|
|
14
|
+
missing fields, not the vendor tool that produces them.
|
|
15
|
+
|
|
16
|
+
**SETOOLS is license-gated and Alp Lab does not redistribute it** -- the same
|
|
17
|
+
stance `tan doctor`'s own `setools` check already takes. What this module
|
|
18
|
+
adds is: given a SETOOLS install the customer already has on disk, drive its
|
|
19
|
+
`app-gen-toc` step for them -- copy the build's raw `.bin`, write the JSON
|
|
20
|
+
config it wants, run it, and read back the ATOC placement it prints -- so
|
|
21
|
+
`tan flash` can complete end to end. RESOLVING that install is also this
|
|
22
|
+
module's job ([`resolve_setools_dir`]): the `--setools-dir` flag, then
|
|
23
|
+
`SETOOLS_DIR`, then `flash_args.setools_dir`, in that order, and NOTHING
|
|
24
|
+
ELSE -- no filesystem search -- because a WRONG SETOOLS silently signing
|
|
25
|
+
against the wrong part is worse than tan refusing outright.
|
|
26
|
+
|
|
27
|
+
That ranking is deliberate and is the tan-cli#368 re-ranking; read
|
|
28
|
+
[`resolve_setools_dir`]'s own docstring before changing it. `flash_args` is
|
|
29
|
+
GENERATED -- every `tan build` overwrites it -- so a stale hand-edited
|
|
30
|
+
`setools_dir` must never outrank the `SETOOLS_DIR` an operator exported or a
|
|
31
|
+
flag they passed for this one invocation. This header previously stated the
|
|
32
|
+
reverse order (tan-cli#572), leaving two docstrings in ONE file disagreeing
|
|
33
|
+
about which SETOOLS signs the image.
|
|
34
|
+
|
|
35
|
+
**Not `tan.core.flash_plan`.** That module is pure/no-IO by its own
|
|
36
|
+
docstring; this one is not -- it copies a file, writes a config, and spawns
|
|
37
|
+
`app-gen-toc`, the same real-filesystem-work exception
|
|
38
|
+
`tan.core.venv`/`tan.core.bootstrap` already carry. Every DECISION about
|
|
39
|
+
*when* to call this module (never off the `alif_mram_jlink` path, never once
|
|
40
|
+
`atoc`/`atoc_address` are already resolved) stays in `tan.commands.flash_cmd`,
|
|
41
|
+
which is also the only caller.
|
|
42
|
+
|
|
43
|
+
**The sign never writes the customer's install (tan-cli#1325).** It runs in a
|
|
44
|
+
private scratch overlay of it (`tan.core.setools_scratch`), so it is
|
|
45
|
+
side-effect-free and `--dry-run` runs it too. The caller owns the scratch tree
|
|
46
|
+
and removes it when the entry ends.
|
|
47
|
+
|
|
48
|
+
**No new hardware fact (ADR-0017 / I-26).** `mramAddress` is
|
|
49
|
+
`flash_args.slot0_load_address` verbatim -- already a documented Flow D key
|
|
50
|
+
(`flash_plan.plan_alif_mram_jlink`) -- and `cpu_id` is the manifest's own
|
|
51
|
+
`core_id` upper-cased (`m55_he` -> `M55_HE`); neither is invented here.
|
|
52
|
+
|
|
53
|
+
ponytail: `cpu_id = core_id.upper()` is a naming-convention bet, not a
|
|
54
|
+
metadata fact -- correct for every AEN `core_id` measured so far (`m55_he` /
|
|
55
|
+
`m55_hp`). Upgrade path if a future `core_id` spelling ever diverges from
|
|
56
|
+
SETOOLS' own `cpu_id` vocabulary: a `flash_args.setools_cpu_id` override,
|
|
57
|
+
added once a real manifest needs one -- not added speculatively here.
|
|
58
|
+
"""
|
|
59
|
+
from __future__ import annotations
|
|
60
|
+
|
|
61
|
+
import dataclasses
|
|
62
|
+
import json
|
|
63
|
+
import os
|
|
64
|
+
import shutil
|
|
65
|
+
import subprocess
|
|
66
|
+
import time
|
|
67
|
+
from collections.abc import Callable, Sequence
|
|
68
|
+
from dataclasses import dataclass
|
|
69
|
+
from typing import Any
|
|
70
|
+
|
|
71
|
+
from tan.core.flash_plan import (
|
|
72
|
+
FLOW_D_METHOD,
|
|
73
|
+
FlashPlanError,
|
|
74
|
+
fa_str,
|
|
75
|
+
parse_atoc_start_address,
|
|
76
|
+
validate_identifier,
|
|
77
|
+
)
|
|
78
|
+
from tan.core.setools_scratch import (
|
|
79
|
+
DEVICE_ENTRY_VERSION,
|
|
80
|
+
AtocReport,
|
|
81
|
+
DeviceConfig,
|
|
82
|
+
cleanup_scratch,
|
|
83
|
+
make_scratch,
|
|
84
|
+
parse_atoc_report,
|
|
85
|
+
)
|
|
86
|
+
from tan.core.subprocess_env import spawn_env
|
|
87
|
+
|
|
88
|
+
#: The one SETOOLS executable this module drives. Bare name, no extension --
|
|
89
|
+
#: the Alif Security Toolkit bundle (`app-release-exec-linux-SE_FW_x.y.z`) is
|
|
90
|
+
#: a Linux tool; nothing here guesses a `.exe`/`.bat` variant, matching `tan
|
|
91
|
+
#: doctor`'s own `setools_check` (`tan/commands/doctor_cmd.py`).
|
|
92
|
+
APP_GEN_TOC = "app-gen-toc"
|
|
93
|
+
|
|
94
|
+
#: Seconds `app-gen-toc` may run before it is killed -- a local sign step over
|
|
95
|
+
#: one small binary, generous mainly against a hung/misconfigured SETOOLS
|
|
96
|
+
#: install (e.g. waiting on an interactive prompt an app-only ATOC should
|
|
97
|
+
#: never need).
|
|
98
|
+
APP_GEN_TOC_TIMEOUT_S = 120.0
|
|
99
|
+
|
|
100
|
+
#: SETOOLS' own fixed output locations, always relative to `$SETOOLS_DIR` and
|
|
101
|
+
#: never configurable -- reading these back is not "searching the
|
|
102
|
+
#: filesystem": they are the ONE place `app-gen-toc` itself writes, per
|
|
103
|
+
#: alp-sdk's `docs/aen-provisioning.md` and every bench script under alp-sdk's
|
|
104
|
+
#: `scripts/bench/aen/` -- NEITHER path exists in this repo (tan-cli); both
|
|
105
|
+
#: are alp-sdk paths, cited here only as the authority for the fixed shape.
|
|
106
|
+
_ATOC_BLOB_REL = os.path.join("build", "AppTocPackage.bin")
|
|
107
|
+
_ATOC_MAP_REL = os.path.join("build", "app-package-map.txt")
|
|
108
|
+
|
|
109
|
+
@dataclass(frozen=True)
|
|
110
|
+
class SetoolsSource:
|
|
111
|
+
"""A resolved `$SETOOLS_DIR`, plus WHERE it came from -- every refusal
|
|
112
|
+
downstream names `source`, so a customer juggling both an explicit
|
|
113
|
+
manifest value and a shell export knows which one tan actually read."""
|
|
114
|
+
|
|
115
|
+
path: str
|
|
116
|
+
source: str
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def operator_supplied(self) -> bool:
|
|
120
|
+
"""The path came from the operator -- `--setools-dir` or `$SETOOLS_DIR` --
|
|
121
|
+
not from `flash_args.setools_dir`, which the PROJECT controls (a checkout's
|
|
122
|
+
manifest). Only an operator-supplied install is one tan may EXECUTE for a
|
|
123
|
+
preview (tan-cli#1343 review)."""
|
|
124
|
+
return self.source != _MANIFEST_SOURCE
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
_MANIFEST_SOURCE = "flash_args.setools_dir"
|
|
128
|
+
|
|
129
|
+
#: Registered issue code (`contract/issue-codes.json`) for a SETOOLS install or device
|
|
130
|
+
#: config that only the PROJECT named.
|
|
131
|
+
UNTRUSTED_SOURCE_CODE = "flash.setools-untrusted-source"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class SetoolsUntrustedSourceError(FlashPlanError):
|
|
135
|
+
"""The SETOOLS install (or the device config signed into MRAM) was named only
|
|
136
|
+
by the project's manifest. `code` is the registered issue code."""
|
|
137
|
+
|
|
138
|
+
code = UNTRUSTED_SOURCE_CODE
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def untrusted_install_message(setools: "SetoolsSource") -> str:
|
|
142
|
+
return (
|
|
143
|
+
f"{FLOW_D_METHOD}: the SETOOLS install '{setools.path}' is named only by "
|
|
144
|
+
"flash_args.setools_dir in the manifest, which the project controls -- tan will not "
|
|
145
|
+
"EXECUTE an app-gen-toc a checkout picked for a write to a board. Name the install "
|
|
146
|
+
"yourself: --setools-dir <path> on the command line or SETOOLS_DIR=<path> in the "
|
|
147
|
+
"environment."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _inside(path: str, root: str) -> bool:
|
|
152
|
+
try:
|
|
153
|
+
real_path, real_root = os.path.realpath(path), os.path.realpath(root)
|
|
154
|
+
return os.path.commonpath([real_path, real_root]) == real_root
|
|
155
|
+
except (ValueError, OSError):
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def check_device_config_location(path: str, setools_dir: str, project_dir: str | None) -> None:
|
|
160
|
+
"""A manifest-named device configuration is accepted only when it resolves (symlinks
|
|
161
|
+
followed) inside the operator-named SETOOLS install or the project directory: the
|
|
162
|
+
file is signed into MRAM, so an arbitrary path (`/etc/...`, another user's home) is
|
|
163
|
+
refused. Raises [`SetoolsUntrustedSourceError`]."""
|
|
164
|
+
roots = [setools_dir] + ([project_dir] if project_dir else [])
|
|
165
|
+
if any(_inside(path, root) for root in roots):
|
|
166
|
+
return
|
|
167
|
+
raise SetoolsUntrustedSourceError(
|
|
168
|
+
f"{FLOW_D_METHOD}: flash_args.setools_device_config resolves to '{path}', outside the "
|
|
169
|
+
f"SETOOLS install ('{setools_dir}') and the project directory"
|
|
170
|
+
+ (f" ('{project_dir}')" if project_dir else "")
|
|
171
|
+
+ " -- it would be signed into MRAM, so tan only accepts a device config from "
|
|
172
|
+
"those places. Move it into the project, or drop the key to use the stock config."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def resolve_setools_dir(
|
|
177
|
+
flash_args: Any, env: dict[str, str], flag: str | None = None
|
|
178
|
+
) -> SetoolsSource | None:
|
|
179
|
+
"""Most-explicit-first (tan-cli#368): the `--setools-dir` CLI flag, then
|
|
180
|
+
`$SETOOLS_DIR`, then `flash_args.setools_dir`. `None` when none of the
|
|
181
|
+
three is set. Never a filesystem search and never a guess -- a wrong
|
|
182
|
+
SETOOLS signing against the wrong part is worse than refusing.
|
|
183
|
+
|
|
184
|
+
The flag outranks the environment, which outranks the manifest --
|
|
185
|
+
DELIBERATELY the opposite of most `flash_args` accessors in this codebase
|
|
186
|
+
(which read the manifest as authoritative). `build/system-manifest.yaml`
|
|
187
|
+
is regenerated by every `tan build` and alp-sdk's own emit carries no
|
|
188
|
+
`setools_dir` key at all, so a hand-edit there is silently destroyed by
|
|
189
|
+
the customer's next build (#368) -- it is the LEAST durable of the three,
|
|
190
|
+
not the most, and is ranked accordingly. `SETOOLS_DIR` survives a build
|
|
191
|
+
but is shell/session-scoped; `--setools-dir` is the one source pinnable
|
|
192
|
+
per invocation regardless of either, so it wins outright.
|
|
193
|
+
"""
|
|
194
|
+
if flag:
|
|
195
|
+
return SetoolsSource(flag, "the --setools-dir flag")
|
|
196
|
+
from_env = env.get("SETOOLS_DIR")
|
|
197
|
+
if from_env:
|
|
198
|
+
return SetoolsSource(from_env, "the SETOOLS_DIR environment variable")
|
|
199
|
+
explicit = fa_str(flash_args, "setools_dir")
|
|
200
|
+
if explicit:
|
|
201
|
+
return SetoolsSource(explicit, _MANIFEST_SOURCE)
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _app_gen_toc_candidates(setools_dir: str) -> list[str]:
|
|
206
|
+
"""Every filename [`find_app_gen_toc`] tries, in order -- shared with
|
|
207
|
+
[`missing_tool_message`] (tan-cli#369) so the diagnosis names EXACTLY
|
|
208
|
+
what was checked, never a conclusion beyond it. Bare `APP_GEN_TOC`
|
|
209
|
+
everywhere; also `APP_GEN_TOC + ".exe"` on Windows, since a genuine
|
|
210
|
+
Windows SETOOLS install ships the executable with an extension and the
|
|
211
|
+
bare name alone is never found there."""
|
|
212
|
+
candidates = [os.path.join(setools_dir, APP_GEN_TOC)]
|
|
213
|
+
if os.name == "nt":
|
|
214
|
+
candidates.append(os.path.join(setools_dir, APP_GEN_TOC + ".exe"))
|
|
215
|
+
return candidates
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def find_app_gen_toc(setools_dir: str) -> str | None:
|
|
219
|
+
"""The first of [`_app_gen_toc_candidates`] that exists inside
|
|
220
|
+
`setools_dir`, or `None`. Incapable of raising -- `setools_dir` is a
|
|
221
|
+
customer-supplied path (`--setools-dir`, an env var, or
|
|
222
|
+
`flash_args.setools_dir`) that may hold anything."""
|
|
223
|
+
try:
|
|
224
|
+
return next(
|
|
225
|
+
(c for c in _app_gen_toc_candidates(setools_dir) if os.path.isfile(c)), None
|
|
226
|
+
)
|
|
227
|
+
except (OSError, ValueError):
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def unresolved_message(sku: str | None = None, flash_device: str | None = None) -> str:
|
|
232
|
+
"""The guidance for `resolve_setools_dir` answering `None` -- names EVERY
|
|
233
|
+
accepted source, in PRECEDENCE ORDER, flag first (tan-cli#368): the flag
|
|
234
|
+
is the one source visible in `tan flash --help` and pinnable per
|
|
235
|
+
invocation, so it leads; the manifest field is named last and flagged as
|
|
236
|
+
build-owned, since `tan build` silently overwrites a hand-edit there on
|
|
237
|
+
the customer's next build. Modeled on `sdk_cmd.NO_SDK_NEXT_STEPS`/
|
|
238
|
+
`doctor_cmd.setools_check`'s own tone: remedy first, blame never.
|
|
239
|
+
|
|
240
|
+
tan-cli#1319: the subject is NAMED from the manifest -- `sku` (its
|
|
241
|
+
`hw_info.sku`) and `flash_device` (`flash_args.jlink_flash_device`, the
|
|
242
|
+
SoC variant's J-Link part profile) -- never a hardcoded SKU. Both absent
|
|
243
|
+
(a hand-written manifest) falls back to a SKU-free noun."""
|
|
244
|
+
subject = _slot0_subject(sku, flash_device)
|
|
245
|
+
return (
|
|
246
|
+
f"{FLOW_D_METHOD}: {subject} needs a SIGNED ATOC, which only "
|
|
247
|
+
f"Alif's SETOOLS `{APP_GEN_TOC}` step can produce. SETOOLS is license-gated "
|
|
248
|
+
"and alp-sdk does not redistribute it -- install it from Alif, then point "
|
|
249
|
+
"tan at it, most-specific first: --setools-dir <path> on the command line, "
|
|
250
|
+
"SETOOLS_DIR=<path> in the environment, or flash_args.setools_dir in the "
|
|
251
|
+
"manifest (lowest precedence, and OVERWRITTEN by the next `tan build` -- "
|
|
252
|
+
"prefer the flag or the environment variable for a durable setting)."
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _slot0_subject(sku: str | None, flash_device: str | None) -> str:
|
|
257
|
+
"""`the E1M-AEN803 slot0 image (AE822FA0E5597LS0_M55_HE)` -- whatever of
|
|
258
|
+
the manifest's SKU / J-Link device profile is known, never invented."""
|
|
259
|
+
parts = [p.strip() for p in (sku, flash_device) if isinstance(p, str) and p.strip()]
|
|
260
|
+
if not parts:
|
|
261
|
+
return "an Alif Ensemble MRAM slot0 image"
|
|
262
|
+
if len(parts) == 1:
|
|
263
|
+
return f"the {parts[0]} slot0 image"
|
|
264
|
+
return f"the {parts[0]} slot0 image ({parts[1]})"
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def missing_tool_message(setools: SetoolsSource) -> str:
|
|
268
|
+
"""The guidance when `setools.path` resolved (from `setools.source`) but
|
|
269
|
+
[`find_app_gen_toc`] found nothing there -- distinct from
|
|
270
|
+
[`unresolved_message`] because the customer already told tan where to
|
|
271
|
+
look; the problem is what tan found there, not that nothing was named.
|
|
272
|
+
|
|
273
|
+
**tan-cli#369.** Used to assert a CONCLUSION ("this does not look like an
|
|
274
|
+
Alif Security Toolkit install") identically for a directory that does not
|
|
275
|
+
exist at all, a path pointed at the `app-gen-toc` BINARY itself instead
|
|
276
|
+
of its parent directory, and a genuine Windows install
|
|
277
|
+
(`find_app_gen_toc` did not try `app-gen-toc.exe` -- fixed alongside
|
|
278
|
+
this). Names only what was actually checked: every candidate filename
|
|
279
|
+
[`_app_gen_toc_candidates`] tries, and whether `setools.path` is even a
|
|
280
|
+
real directory -- never a verdict the check did not make.
|
|
281
|
+
"""
|
|
282
|
+
candidates = _app_gen_toc_candidates(setools.path)
|
|
283
|
+
tried = " or ".join(f"'{c}'" for c in candidates)
|
|
284
|
+
try:
|
|
285
|
+
is_dir = os.path.isdir(setools.path)
|
|
286
|
+
except (OSError, ValueError):
|
|
287
|
+
is_dir = False
|
|
288
|
+
if is_dir:
|
|
289
|
+
where = f"SETOOLS not found at {tried} -- the directory exists but holds none of them."
|
|
290
|
+
else:
|
|
291
|
+
where = (
|
|
292
|
+
f"SETOOLS not found at {tried} -- '{setools.path}' is not a directory at "
|
|
293
|
+
f"all. If it names the {APP_GEN_TOC} binary itself, point tan at its "
|
|
294
|
+
"PARENT directory instead."
|
|
295
|
+
)
|
|
296
|
+
return (
|
|
297
|
+
f"{FLOW_D_METHOD}: SETOOLS_DIR resolved to '{setools.path}' (via "
|
|
298
|
+
f"{setools.source}), but {where} Check the path, or re-download SETOOLS from "
|
|
299
|
+
"Alif."
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def slot0_config(
|
|
304
|
+
name: str,
|
|
305
|
+
binary: str,
|
|
306
|
+
mram_address: str,
|
|
307
|
+
cpu_id: str,
|
|
308
|
+
device_binary: str | None = None,
|
|
309
|
+
) -> dict[str, Any]:
|
|
310
|
+
"""The `app-gen-toc` JSON config for one slot0 ATOC. With `device_binary`
|
|
311
|
+
(the file name of the device configuration, tan-cli#1322) the table leads
|
|
312
|
+
with a `DEVICE` entry in the exact shape alp-sdk's bench recipe signs
|
|
313
|
+
(`scripts/bench/aen/flash-run.sh`: `binary`, version `0.5.00`, `signed`,
|
|
314
|
+
`disabled: false`); without it the config is the app-only shape tan-cli#353
|
|
315
|
+
first measured. The app entry itself is identical either way."""
|
|
316
|
+
config: dict[str, Any] = {}
|
|
317
|
+
if device_binary is not None:
|
|
318
|
+
config["DEVICE"] = {
|
|
319
|
+
"disabled": False,
|
|
320
|
+
"binary": device_binary,
|
|
321
|
+
"version": DEVICE_ENTRY_VERSION,
|
|
322
|
+
"signed": True,
|
|
323
|
+
}
|
|
324
|
+
config[name] = {
|
|
325
|
+
"binary": binary,
|
|
326
|
+
"version": "1.0.0",
|
|
327
|
+
"mramAddress": mram_address,
|
|
328
|
+
"cpu_id": cpu_id,
|
|
329
|
+
"flags": ["boot"],
|
|
330
|
+
"signed": True,
|
|
331
|
+
}
|
|
332
|
+
return config
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def combined_slot0_config(
|
|
336
|
+
slices: Sequence[tuple[str, str, str]],
|
|
337
|
+
device_binary: str | None = None,
|
|
338
|
+
) -> dict[str, Any]:
|
|
339
|
+
"""The `app-gen-toc` config for ONE ATOC naming every slice (tan-cli#1509):
|
|
340
|
+
the optional `DEVICE` entry, then one entry per `(name, binary, mram_address)`
|
|
341
|
+
-- each exactly what [`slot0_config`] writes for a lone slice (`cpu_id` is the
|
|
342
|
+
upper-cased name). One slice gives `slot0_config`'s output verbatim."""
|
|
343
|
+
config: dict[str, Any] = {}
|
|
344
|
+
for n, (name, binary, address) in enumerate(slices):
|
|
345
|
+
config.update(
|
|
346
|
+
slot0_config(
|
|
347
|
+
name, binary, address, name.upper(),
|
|
348
|
+
device_binary=device_binary if n == 0 else None,
|
|
349
|
+
)
|
|
350
|
+
)
|
|
351
|
+
return config
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def read_atoc_address(setools_dir: str) -> str | None:
|
|
355
|
+
"""The ATOC placement `app-gen-toc` just wrote, out of its own
|
|
356
|
+
`build/app-package-map.txt` report. Reuses `flash_plan
|
|
357
|
+
.parse_atoc_start_address`'s parse (byte-identical to every bench
|
|
358
|
+
script's own `awk .../app-package-map.txt | tail -1`) -- the read half
|
|
359
|
+
lives here, not there, since `flash_plan` stays no-IO. `None` when the
|
|
360
|
+
report is not there; a caller decides what that means."""
|
|
361
|
+
map_path = os.path.join(setools_dir, _ATOC_MAP_REL)
|
|
362
|
+
try:
|
|
363
|
+
with open(map_path, encoding="utf-8", errors="replace", newline="") as fh:
|
|
364
|
+
text = fh.read()
|
|
365
|
+
except OSError:
|
|
366
|
+
return None
|
|
367
|
+
return parse_atoc_start_address(text)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def _tail(stdout: str, stderr: str) -> str:
|
|
371
|
+
"""The last 4 non-empty lines of whichever stream carries something --
|
|
372
|
+
mirrors `flash_cmd._capture_tail`'s shape (not imported: that helper
|
|
373
|
+
reads a `flash_cmd._Outcome`, a shape this module has no reason to
|
|
374
|
+
depend on)."""
|
|
375
|
+
text = stderr if stderr.strip() else stdout
|
|
376
|
+
lines = [line for line in text.splitlines() if line.strip()][-4:]
|
|
377
|
+
return " | ".join(lines) if lines else "no output"
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
@dataclass(frozen=True)
|
|
381
|
+
class SignedSlot0:
|
|
382
|
+
"""One completed scratch sign (tan-cli#1325). The ATOC blob lives in
|
|
383
|
+
`scratch_dir`, which the CALLER removes ([`cleanup_scratch`]) once J-Link has
|
|
384
|
+
read it; the shared SETOOLS install was never written."""
|
|
385
|
+
|
|
386
|
+
atoc_path: str
|
|
387
|
+
atoc_address: str
|
|
388
|
+
atoc_size: int
|
|
389
|
+
scratch_dir: str
|
|
390
|
+
report: AtocReport
|
|
391
|
+
device_config: DeviceConfig | None
|
|
392
|
+
#: Paths in the SHARED install newer than the start of this sign (at most 5).
|
|
393
|
+
#: Expected empty; non-empty means another process wrote there meanwhile -- or
|
|
394
|
+
#: that the overlay leaked. Reported, never a refusal (a concurrent raw bench
|
|
395
|
+
#: recipe is legitimate).
|
|
396
|
+
shared_touched: tuple[str, ...] = ()
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def sign_slot0(
|
|
400
|
+
setools_dir: str,
|
|
401
|
+
app_gen_toc: str,
|
|
402
|
+
artefact_bin: str,
|
|
403
|
+
entry_id: str,
|
|
404
|
+
mram_address: str,
|
|
405
|
+
*,
|
|
406
|
+
device_config: DeviceConfig | None = None,
|
|
407
|
+
scratch_parent: str | None = None,
|
|
408
|
+
on_scratch: Callable[[str], None] | None = None,
|
|
409
|
+
extra_slices: Sequence[tuple[str, str, str]] = (),
|
|
410
|
+
) -> SignedSlot0:
|
|
411
|
+
"""Run one `app-gen-toc` sign step in a PRIVATE scratch overlay of
|
|
412
|
+
`setools_dir` ([`make_scratch`]): copy `artefact_bin` into the scratch
|
|
413
|
+
`build/images/`, write `build/config/<entry_id>-slot0.json` (with a leading
|
|
414
|
+
`DEVICE` entry when `device_config` is given, its file copied into the
|
|
415
|
+
scratch `build/config/`), spawn the scratch copy of `app_gen_toc` with
|
|
416
|
+
`cwd=<scratch>` (its config path is relative to it, matching the bench's own
|
|
417
|
+
`cd $SETOOLS_DIR && ./app-gen-toc -f build/config/...`), then read back the
|
|
418
|
+
ATOC placement and entry list from the scratch report.
|
|
419
|
+
|
|
420
|
+
The shared install is READ ONLY here (tan-cli#1325): the previous design
|
|
421
|
+
wrote `build/AppTocPackage.bin`, `build/images/`, `build/config/`, appended
|
|
422
|
+
to `build/app-package-map.txt` and created `build/tan-atoc/` plus a lock file
|
|
423
|
+
in it, so one `tan flash` changed the package every other user of that
|
|
424
|
+
install found there. Because nothing shared is written, the cross-process
|
|
425
|
+
lock tan-cli#380 needed for that shared output is gone too: two runs get two
|
|
426
|
+
scratch trees and cannot cross-pair. A stale package or map cannot leak in
|
|
427
|
+
either -- the scratch `build/` starts empty, so a tool that exits 0 without
|
|
428
|
+
writing is caught by the plain "no report / no blob" checks below.
|
|
429
|
+
|
|
430
|
+
`extra_slices` (`(entry_id, artefact_bin, mram_address)` each, tan-cli#1509)
|
|
431
|
+
adds further entries to the SAME config, so one ATOC names every slice; the
|
|
432
|
+
first slice is the one named by the positional arguments.
|
|
433
|
+
|
|
434
|
+
Returns [`SignedSlot0`]; the caller owns `scratch_dir`. Raises
|
|
435
|
+
`FlashPlanError` -- naming `app-gen-toc`'s own captured output where there is
|
|
436
|
+
any -- on: a filesystem failure preparing the scratch tree, a spawn failure
|
|
437
|
+
or timeout, a non-zero exit, a report with no `'APP Package Start Address:'`
|
|
438
|
+
line, or a successful exit that did not produce the ATOC blob. SETOOLS' own
|
|
439
|
+
diagnostic is the authoritative one; this only surfaces it. On any raise the
|
|
440
|
+
scratch tree is already removed.
|
|
441
|
+
"""
|
|
442
|
+
validate_identifier(entry_id, "the flash target id")
|
|
443
|
+
for extra_id, _bin, _addr in extra_slices:
|
|
444
|
+
validate_identifier(extra_id, "the flash target id")
|
|
445
|
+
setools_dir = os.path.abspath(setools_dir)
|
|
446
|
+
app_gen_toc = os.path.abspath(app_gen_toc)
|
|
447
|
+
try:
|
|
448
|
+
scratch = make_scratch(setools_dir, scratch_parent)
|
|
449
|
+
except OSError as err:
|
|
450
|
+
raise FlashPlanError(
|
|
451
|
+
f"{FLOW_D_METHOD}: could not prepare a scratch copy of the SETOOLS install "
|
|
452
|
+
f"'{setools_dir}' for the sign step: {err}"
|
|
453
|
+
) from err
|
|
454
|
+
started, before = time.time_ns(), _snapshot(setools_dir)
|
|
455
|
+
try:
|
|
456
|
+
# BEFORE anything can be interrupted: the caller registers the scratch's
|
|
457
|
+
# removal here, so a KeyboardInterrupt mid-sign cannot leak the tree.
|
|
458
|
+
if on_scratch is not None:
|
|
459
|
+
on_scratch(scratch)
|
|
460
|
+
signed = _sign_in_scratch(
|
|
461
|
+
scratch, setools_dir, app_gen_toc, artefact_bin, entry_id, mram_address,
|
|
462
|
+
device_config, extra_slices,
|
|
463
|
+
)
|
|
464
|
+
return dataclasses.replace(signed, shared_touched=_newer_than(setools_dir, started, before))
|
|
465
|
+
except BaseException:
|
|
466
|
+
cleanup_scratch(scratch)
|
|
467
|
+
raise
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _snapshot(root: str) -> dict[str, tuple[int, int]]:
|
|
471
|
+
"""`{path: (mtime_ns, size)}` for every file under `root`. Never raises."""
|
|
472
|
+
snap: dict[str, tuple[int, int]] = {}
|
|
473
|
+
try:
|
|
474
|
+
for base, _dirs, files in os.walk(root):
|
|
475
|
+
for name in files:
|
|
476
|
+
path = os.path.join(base, name)
|
|
477
|
+
try:
|
|
478
|
+
st = os.lstat(path)
|
|
479
|
+
except OSError:
|
|
480
|
+
continue
|
|
481
|
+
snap[path] = (st.st_mtime_ns, st.st_size)
|
|
482
|
+
except OSError:
|
|
483
|
+
pass
|
|
484
|
+
return snap
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def _newer_than(
|
|
488
|
+
root: str,
|
|
489
|
+
since_ns: int,
|
|
490
|
+
before: dict[str, tuple[int, int]] | None = None,
|
|
491
|
+
limit: int = 5,
|
|
492
|
+
) -> tuple[str, ...]:
|
|
493
|
+
"""Files under `root` changed since the sign began (cheap post-check that
|
|
494
|
+
the overlay kept the shared install untouched). A file counts when it is
|
|
495
|
+
absent from the `before` snapshot, its (mtime, size) differs from it, or its
|
|
496
|
+
mtime is newer than `since_ns`. The snapshot is what makes this robust: on
|
|
497
|
+
Windows file mtimes come from a coarse (~15 ms) clock that can lag
|
|
498
|
+
`time.time_ns()`, so a write right after `since_ns` was taken can carry an
|
|
499
|
+
mtime <= it and a bare timestamp compare misses it. Never raises."""
|
|
500
|
+
found: list[str] = []
|
|
501
|
+
for path, (mtime_ns, size) in _snapshot(root).items():
|
|
502
|
+
prior = None if before is None else before.get(path)
|
|
503
|
+
if mtime_ns > since_ns or (before is not None and prior != (mtime_ns, size)):
|
|
504
|
+
found.append(path)
|
|
505
|
+
if len(found) >= limit:
|
|
506
|
+
break
|
|
507
|
+
return tuple(found)
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _sign_in_scratch(
|
|
511
|
+
scratch: str,
|
|
512
|
+
setools_dir: str,
|
|
513
|
+
app_gen_toc: str,
|
|
514
|
+
artefact_bin: str,
|
|
515
|
+
entry_id: str,
|
|
516
|
+
mram_address: str,
|
|
517
|
+
device_config: DeviceConfig | None,
|
|
518
|
+
extra_slices: Sequence[tuple[str, str, str]] = (),
|
|
519
|
+
) -> SignedSlot0:
|
|
520
|
+
binary_name = f"{entry_id}.bin"
|
|
521
|
+
config_rel = os.path.join("build", "config", f"{entry_id}-slot0.json")
|
|
522
|
+
atoc_map_path = os.path.join(scratch, _ATOC_MAP_REL)
|
|
523
|
+
atoc_blob_path = os.path.join(scratch, _ATOC_BLOB_REL)
|
|
524
|
+
try:
|
|
525
|
+
shutil.copyfile(artefact_bin, os.path.join(scratch, "build", "images", binary_name))
|
|
526
|
+
for extra_id, extra_bin, _addr in extra_slices:
|
|
527
|
+
shutil.copyfile(extra_bin, os.path.join(scratch, "build", "images", f"{extra_id}.bin"))
|
|
528
|
+
if device_config is not None:
|
|
529
|
+
shutil.copyfile(
|
|
530
|
+
device_config.path, os.path.join(scratch, "build", "config", device_config.name)
|
|
531
|
+
)
|
|
532
|
+
with open(os.path.join(scratch, config_rel), "w", encoding="utf-8", newline="\n") as fh:
|
|
533
|
+
json.dump(
|
|
534
|
+
combined_slot0_config(
|
|
535
|
+
[(entry_id, binary_name, mram_address)]
|
|
536
|
+
+ [(i, f"{i}.bin", a) for i, _b, a in extra_slices],
|
|
537
|
+
device_binary=device_config.name if device_config is not None else None,
|
|
538
|
+
),
|
|
539
|
+
fh,
|
|
540
|
+
indent=2,
|
|
541
|
+
)
|
|
542
|
+
fh.write("\n")
|
|
543
|
+
except OSError as err:
|
|
544
|
+
raise FlashPlanError(
|
|
545
|
+
f"{FLOW_D_METHOD}: could not prepare the SETOOLS sign step under the scratch "
|
|
546
|
+
f"copy '{scratch}' of '{setools_dir}': {err}"
|
|
547
|
+
) from err
|
|
548
|
+
|
|
549
|
+
# The scratch copy of the tool, not the shared one: its own directory (and the
|
|
550
|
+
# `../build` it addresses) is then the scratch root.
|
|
551
|
+
inside = os.path.relpath(app_gen_toc, setools_dir)
|
|
552
|
+
scratch_tool = os.path.join(scratch, inside) if not inside.startswith("..") else app_gen_toc
|
|
553
|
+
try:
|
|
554
|
+
# tan-cli#992: this signs the ATOC that gets written to real silicon --
|
|
555
|
+
# the same reasoning `flash_cmd._child_env` documents applies here
|
|
556
|
+
# verbatim, so `env=` is never left to inherit this process's (possibly
|
|
557
|
+
# bundle-poisoned) environment.
|
|
558
|
+
proc = subprocess.run(
|
|
559
|
+
[scratch_tool, "-f", config_rel],
|
|
560
|
+
cwd=scratch,
|
|
561
|
+
capture_output=True,
|
|
562
|
+
text=True,
|
|
563
|
+
encoding="utf-8",
|
|
564
|
+
errors="replace",
|
|
565
|
+
env=spawn_env(),
|
|
566
|
+
timeout=APP_GEN_TOC_TIMEOUT_S,
|
|
567
|
+
)
|
|
568
|
+
except subprocess.TimeoutExpired as err:
|
|
569
|
+
raise FlashPlanError(
|
|
570
|
+
f"{FLOW_D_METHOD}: {APP_GEN_TOC} timed out after "
|
|
571
|
+
f"{APP_GEN_TOC_TIMEOUT_S:.0f}s signing {config_rel}"
|
|
572
|
+
) from err
|
|
573
|
+
except OSError as err:
|
|
574
|
+
raise FlashPlanError(f"{FLOW_D_METHOD}: could not run {app_gen_toc}: {err}") from err
|
|
575
|
+
if proc.returncode != 0:
|
|
576
|
+
raise FlashPlanError(
|
|
577
|
+
f"{FLOW_D_METHOD}: {APP_GEN_TOC} -f {config_rel} exited {proc.returncode}: "
|
|
578
|
+
f"{_tail(proc.stdout, proc.stderr)}"
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
try:
|
|
582
|
+
with open(atoc_map_path, encoding="utf-8", errors="replace", newline="") as fh:
|
|
583
|
+
report_text = fh.read()
|
|
584
|
+
except OSError:
|
|
585
|
+
report_text = ""
|
|
586
|
+
address = parse_atoc_start_address(report_text)
|
|
587
|
+
if address is None:
|
|
588
|
+
raise FlashPlanError(
|
|
589
|
+
f"{FLOW_D_METHOD}: {APP_GEN_TOC} exited 0 but its report "
|
|
590
|
+
f"({_ATOC_MAP_REL}, in the scratch copy) carries no 'APP Package Start "
|
|
591
|
+
"Address:' line -- check the SETOOLS config, or sign by hand."
|
|
592
|
+
)
|
|
593
|
+
try:
|
|
594
|
+
atoc_size = os.path.getsize(atoc_blob_path)
|
|
595
|
+
except OSError:
|
|
596
|
+
raise FlashPlanError(
|
|
597
|
+
f"{FLOW_D_METHOD}: {APP_GEN_TOC} exited 0 and reported an address, but "
|
|
598
|
+
f"{_ATOC_BLOB_REL} was not produced in the scratch copy -- check the SETOOLS "
|
|
599
|
+
"output."
|
|
600
|
+
) from None
|
|
601
|
+
return SignedSlot0(
|
|
602
|
+
atoc_path=atoc_blob_path,
|
|
603
|
+
atoc_address=address,
|
|
604
|
+
atoc_size=atoc_size,
|
|
605
|
+
scratch_dir=scratch,
|
|
606
|
+
report=parse_atoc_report(report_text),
|
|
607
|
+
device_config=device_config,
|
|
608
|
+
)
|