tan-cli 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. tan/__init__.py +0 -0
  2. tan/__main__.py +120 -0
  3. tan/cli.py +939 -0
  4. tan/command_table.py +265 -0
  5. tan/commands/__init__.py +8 -0
  6. tan/commands/bootstrap_cmd.py +4856 -0
  7. tan/commands/bootstrap_patches.py +164 -0
  8. tan/commands/bootstrap_toolchain_fetch.py +155 -0
  9. tan/commands/bootstrap_toolchain_pin.py +36 -0
  10. tan/commands/build/__init__.py +1 -0
  11. tan/commands/build/configure_inputs.py +277 -0
  12. tan/commands/build/execute.py +1852 -0
  13. tan/commands/build/host_python.py +59 -0
  14. tan/commands/build/link_stale.py +78 -0
  15. tan/commands/build/manifest.py +428 -0
  16. tan/commands/build/materialise.py +81 -0
  17. tan/commands/build/token_substitution.py +381 -0
  18. tan/commands/build/toolchain.py +482 -0
  19. tan/commands/build/user_defines_wipe.py +115 -0
  20. tan/commands/build/workspace_patches.py +65 -0
  21. tan/commands/build_cmd.py +2236 -0
  22. tan/commands/build_output.py +331 -0
  23. tan/commands/clean_cmd.py +1183 -0
  24. tan/commands/completion_cmd.py +786 -0
  25. tan/commands/debug_config_cmd.py +2425 -0
  26. tan/commands/diff_cmd.py +940 -0
  27. tan/commands/doctor_cmd.py +4767 -0
  28. tan/commands/examples_cmd.py +644 -0
  29. tan/commands/explain_cmd.py +1319 -0
  30. tan/commands/faultdecode_cmd.py +931 -0
  31. tan/commands/flash_atoc_guard.py +240 -0
  32. tan/commands/flash_cmd.py +5731 -0
  33. tan/commands/flash_mram_guard.py +150 -0
  34. tan/commands/flash_ram.py +578 -0
  35. tan/commands/flash_raw.py +583 -0
  36. tan/commands/generate_cmd.py +1456 -0
  37. tan/commands/image_cmd.py +753 -0
  38. tan/commands/init_cmd.py +1740 -0
  39. tan/commands/inspect_cmd.py +377 -0
  40. tan/commands/kconfig_cmd.py +734 -0
  41. tan/commands/model_cmd.py +1514 -0
  42. tan/commands/model_device_cmd.py +351 -0
  43. tan/commands/model_device_live.py +234 -0
  44. tan/commands/model_host_cmd.py +430 -0
  45. tan/commands/model_zoo_cmd.py +363 -0
  46. tan/commands/monitor_actions.py +125 -0
  47. tan/commands/monitor_cmd.py +626 -0
  48. tan/commands/monitor_session.py +505 -0
  49. tan/commands/new_som_cmd.py +1522 -0
  50. tan/commands/pinmux_cmd.py +568 -0
  51. tan/commands/presets_cmd.py +1123 -0
  52. tan/commands/probe_cmd.py +421 -0
  53. tan/commands/reset_cmd.py +354 -0
  54. tan/commands/reset_confirm.py +168 -0
  55. tan/commands/run_cmd.py +637 -0
  56. tan/commands/scaffold_cmd.py +490 -0
  57. tan/commands/sdk_cmd.py +1723 -0
  58. tan/commands/size_cmd.py +1020 -0
  59. tan/commands/support_bundle_cmd.py +1073 -0
  60. tan/commands/trace_cmd.py +341 -0
  61. tan/commands/validate_cmd.py +2009 -0
  62. tan/commands/west_forward_cmd.py +736 -0
  63. tan/commands/workspace_patch_check.py +296 -0
  64. tan/core/__init__.py +1 -0
  65. tan/core/alp_conf_pregen.py +100 -0
  66. tan/core/artifact_provenance.py +240 -0
  67. tan/core/atoc_guard.py +276 -0
  68. tan/core/atoc_guard_messages.py +232 -0
  69. tan/core/atoc_replacement.py +118 -0
  70. tan/core/atomic_write.py +178 -0
  71. tan/core/board_context.py +351 -0
  72. tan/core/board_diagnostic.py +111 -0
  73. tan/core/board_files.py +221 -0
  74. tan/core/board_validator.py +542 -0
  75. tan/core/board_validator_compat.py +196 -0
  76. tan/core/board_validator_run.py +129 -0
  77. tan/core/board_validator_schema.py +186 -0
  78. tan/core/board_validator_skew.py +46 -0
  79. tan/core/board_yaml_edit.py +295 -0
  80. tan/core/board_yaml_pos.py +118 -0
  81. tan/core/bootstrap.py +2572 -0
  82. tan/core/build_plan.py +625 -0
  83. tan/core/consent.py +104 -0
  84. tan/core/console_filter.py +245 -0
  85. tan/core/debug_launch.py +1803 -0
  86. tan/core/devicetree_lint.py +168 -0
  87. tan/core/dir_removal.py +229 -0
  88. tan/core/doctor_git.py +172 -0
  89. tan/core/doctor_libraries.py +253 -0
  90. tan/core/doctor_render.py +185 -0
  91. tan/core/doctor_scope.py +66 -0
  92. tan/core/doctor_setools.py +237 -0
  93. tan/core/doctor_stale.py +180 -0
  94. tan/core/document_guards.py +723 -0
  95. tan/core/dp_id.py +97 -0
  96. tan/core/elf_load.py +63 -0
  97. tan/core/error_catalog.py +171 -0
  98. tan/core/example_catalog.py +499 -0
  99. tan/core/example_facets.py +151 -0
  100. tan/core/faultdecode.py +521 -0
  101. tan/core/flash_plan.py +2501 -0
  102. tan/core/flow_d_report.py +381 -0
  103. tan/core/flow_d_uptime.py +164 -0
  104. tan/core/fs_confine.py +130 -0
  105. tan/core/global_flags.py +230 -0
  106. tan/core/host_python.py +210 -0
  107. tan/core/image_bundle.py +247 -0
  108. tan/core/inert.py +147 -0
  109. tan/core/jlink_binary.py +128 -0
  110. tan/core/jlink_probe.py +481 -0
  111. tan/core/jsonc_splice.py +404 -0
  112. tan/core/launch_provenance.py +257 -0
  113. tan/core/link_refusal.py +76 -0
  114. tan/core/metadata_schema.py +376 -0
  115. tan/core/model_check.py +227 -0
  116. tan/core/model_device.py +449 -0
  117. tan/core/model_doctor.py +198 -0
  118. tan/core/model_host.py +48 -0
  119. tan/core/model_list.py +112 -0
  120. tan/core/model_zoo.py +353 -0
  121. tan/core/module_template.py +340 -0
  122. tan/core/mram_link.py +87 -0
  123. tan/core/new_som_ospi.py +94 -0
  124. tan/core/os_class.py +159 -0
  125. tan/core/patch_paths.py +87 -0
  126. tan/core/pending.py +40 -0
  127. tan/core/plain_zephyr_plan.py +138 -0
  128. tan/core/plan_exec.py +373 -0
  129. tan/core/plan_tokens.py +600 -0
  130. tan/core/probe.py +155 -0
  131. tan/core/probe_plan.py +237 -0
  132. tan/core/proxy.py +168 -0
  133. tan/core/publish.py +31 -0
  134. tan/core/python_floor.py +178 -0
  135. tan/core/ram_run.py +694 -0
  136. tan/core/ram_watch.py +231 -0
  137. tan/core/raw_write.py +160 -0
  138. tan/core/reset_plan.py +56 -0
  139. tan/core/run.py +161 -0
  140. tan/core/scaffold.py +2203 -0
  141. tan/core/scaffold_selftest_identity.py +225 -0
  142. tan/core/sdk_default_registry.py +565 -0
  143. tan/core/sdk_discovery.py +996 -0
  144. tan/core/sdk_removal.py +465 -0
  145. tan/core/serial_actions.py +175 -0
  146. tan/core/serial_capture.py +185 -0
  147. tan/core/serial_url.py +45 -0
  148. tan/core/setools.py +608 -0
  149. tan/core/setools_scratch.py +243 -0
  150. tan/core/shapes.py +194 -0
  151. tan/core/size.py +610 -0
  152. tan/core/som_buildability.py +204 -0
  153. tan/core/som_schema_version.py +62 -0
  154. tan/core/subprocess_env.py +201 -0
  155. tan/core/sysbuild_image.py +94 -0
  156. tan/core/system_manifest.py +827 -0
  157. tan/core/text_layout.py +73 -0
  158. tan/core/timestamp.py +182 -0
  159. tan/core/tool_lookup.py +383 -0
  160. tan/core/toolchain_pin.py +117 -0
  161. tan/core/toolchain_provision.py +1224 -0
  162. tan/core/trusted_path.py +134 -0
  163. tan/core/uboot_breakin.py +154 -0
  164. tan/core/uri_reference.py +415 -0
  165. tan/core/user_defines.py +152 -0
  166. tan/core/venv.py +332 -0
  167. tan/core/west_patches.py +143 -0
  168. tan/core/west_workspace_refusal.py +159 -0
  169. tan/core/zephyr_env.py +124 -0
  170. tan/env.py +185 -0
  171. tan/envelope.py +647 -0
  172. tan/exit_codes.py +12 -0
  173. tan/lazy_typer.py +185 -0
  174. tan/model/__init__.py +2 -0
  175. tan/model/_gen_fixture.py +132 -0
  176. tan/model/adapters/__init__.py +116 -0
  177. tan/model/adapters/cpu.py +26 -0
  178. tan/model/adapters/deepx.py +94 -0
  179. tan/model/adapters/drpai.py +242 -0
  180. tan/model/adapters/ethos_u.py +951 -0
  181. tan/model/adapters/executorch.py +55 -0
  182. tan/model/analyze.py +694 -0
  183. tan/model/build.py +304 -0
  184. tan/model/check.py +682 -0
  185. tan/model/manifest.py +625 -0
  186. tan/model/measure.py +216 -0
  187. tan/model/npu_ops_validate.py +130 -0
  188. tan/model/package.py +125 -0
  189. tan/model/perf.py +635 -0
  190. tan/model/perf_apply.py +721 -0
  191. tan/model/prep.py +193 -0
  192. tan/model/sram_fit.py +502 -0
  193. tan/model/targets.py +374 -0
  194. tan/model/tensorio.py +287 -0
  195. tan/net.py +73 -0
  196. tan/output_format.py +213 -0
  197. tan/planner/__init__.py +219 -0
  198. tan/planner/aperture.py +202 -0
  199. tan/planner/buildplan.py +1092 -0
  200. tan/planner/camera_owner.py +192 -0
  201. tan/planner/cameras.py +132 -0
  202. tan/planner/carveout.py +755 -0
  203. tan/planner/cli.py +274 -0
  204. tan/planner/headers.py +315 -0
  205. tan/planner/kconfig.py +2435 -0
  206. tan/planner/kconfig_symbols.py +473 -0
  207. tan/planner/libraries.py +677 -0
  208. tan/planner/link_target.py +237 -0
  209. tan/planner/linux_ownership.py +216 -0
  210. tan/planner/loader.py +1525 -0
  211. tan/planner/manifest.py +116 -0
  212. tan/planner/memory.py +161 -0
  213. tan/planner/memregion.py +56 -0
  214. tan/planner/models.py +533 -0
  215. tan/planner/orchestrator.py +874 -0
  216. tan/planner/ownership.py +203 -0
  217. tan/planner/partition.py +1160 -0
  218. tan/planner/paths.py +32 -0
  219. tan/planner/plain_slice.py +139 -0
  220. tan/planner/project_emit/__init__.py +60 -0
  221. tan/planner/project_emit/bom_netlist.py +375 -0
  222. tan/planner/project_emit/dts.py +580 -0
  223. tan/planner/project_emit/hw_info.py +221 -0
  224. tan/planner/project_emit/native_sim.py +136 -0
  225. tan/planner/project_emit/west_libs.py +213 -0
  226. tan/planner/project_loader.py +264 -0
  227. tan/planner/sdk_capability.py +203 -0
  228. tan/planner/sdk_compat.py +395 -0
  229. tan/planner/secure.py +350 -0
  230. tan/planner/slugs.py +394 -0
  231. tan/planner/som_metadata.py +325 -0
  232. tan/planner/strict_loaders.py +131 -0
  233. tan/planner/template.py +1037 -0
  234. tan/planner/template_pins.py +535 -0
  235. tan/planner/template_rewrite.py +714 -0
  236. tan/planner/topology.py +208 -0
  237. tan/planner/validate.py +365 -0
  238. tan/planner/whole_device_alias.py +67 -0
  239. tan/planner/zephyr_board.py +3510 -0
  240. tan/planner_cli.py +97 -0
  241. tan/planner_emit.py +617 -0
  242. tan/planner_root.py +119 -0
  243. tan/soc_ref.py +58 -0
  244. tan/templates/__init__.py +34 -0
  245. tan/templates/vendored/MANIFEST.md +1862 -0
  246. tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
  247. tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
  248. tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
  249. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
  250. tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
  251. tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
  252. tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
  253. tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
  254. tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
  255. tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
  256. tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
  257. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
  258. tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
  259. tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
  260. tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
  261. tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
  262. tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
  263. tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
  264. tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
  265. tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
  266. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
  267. tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
  268. tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
  269. tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
  270. tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
  271. tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
  272. tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
  273. tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
  274. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
  275. tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
  276. tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
  277. tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
  278. tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
  279. tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
  280. tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
  281. tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
  282. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
  283. tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
  284. tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
  285. tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
  286. tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
  287. tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
  288. tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
  289. tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
  290. tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
  291. tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
  292. tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
  293. tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
  294. tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
  295. tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
  296. tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
  297. tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
  298. tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
  299. tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
  300. tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
  301. tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
  302. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
  303. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
  304. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
  305. tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
  306. tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
  307. tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
  308. tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
  309. tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
  310. tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
  311. tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
  312. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
  313. tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
  314. tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
  315. tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
  316. tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
  317. tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
  318. tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
  319. tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
  320. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
  321. tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
  322. tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
  323. tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
  324. tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
  325. tan/version.py +102 -0
  326. tan_cli-0.7.0.dist-info/METADATA +125 -0
  327. tan_cli-0.7.0.dist-info/RECORD +330 -0
  328. tan_cli-0.7.0.dist-info/WHEEL +5 -0
  329. tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
  330. tan_cli-0.7.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,421 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """`tan probe <identify|read>` -- READ-ONLY J-Link probes (tan-cli#1406).
3
+
4
+ The AEN bench dogfood needed raw `JLinkExe` for two read-only questions; this is
5
+ that, going through the machinery `tan flash --ram` already uses: the trusted
6
+ J-Link binary (`resolve_jlink`), the probe-selection guard
7
+ (`--probe-usb-path` / `--probe-serial`, the `ShowEmuList` TOCTOU check that is
8
+ what the board-farm `JLINK_RUN_PLACE` shim hooks), `flash_cmd._execute` ->
9
+ `_spawn_jlink`, and the `--ram` attach check's own scripts. Nothing is halted,
10
+ written or reset: see `tan.core.probe_plan` for the script rules.
11
+
12
+ Flat verb command (like `tan sdk`): an unknown verb gets a real coded envelope,
13
+ not a parser-level usage error.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ import re
19
+ import sys
20
+ import time
21
+ from dataclasses import dataclass, field
22
+ from typing import Any, Callable
23
+
24
+ import typer
25
+
26
+ from tan.commands import flash_cmd as fc
27
+ from tan.core import probe_plan as pp
28
+ from tan.core.dp_id import _dp_id_value
29
+ from tan.core.flash_plan import (
30
+ _DEFAULT_JLINK_SPEED,
31
+ FlashPlan,
32
+ FlashPlanError,
33
+ parse_system_manifest,
34
+ fa_int_checked,
35
+ fa_str,
36
+ fa_str_checked,
37
+ validate_identifier,
38
+ )
39
+ from tan.core.global_flags import accept_global_flags
40
+ from tan.core.jlink_binary import ENV_OVERRIDE as JLINK_ENV, resolve_jlink
41
+ from tan.core.ram_run import (
42
+ RamRunError,
43
+ ap_verdict,
44
+ attached_core,
45
+ check_session,
46
+ core_check,
47
+ parse_mem32,
48
+ )
49
+ from tan.envelope import Envelope, Issue
50
+ from tan.exit_codes import ExitCode
51
+ from tan.output_format import FORMAT_HELP, OutputFormat, resolve_format
52
+
53
+ COMMAND = "probe"
54
+ VERBS = ("identify", "read")
55
+ _SCHEMA_VERSION = fc._DATA_SCHEMA_VERSION # the string "1", like every other command
56
+ _SCRIPT_PREFIX = "tan-probe-"
57
+ _GUARD_SCRIPT = (fc._DISABLE_FW_UPDATE + "ShowEmuList\nexit\n").splitlines()
58
+ _Result = tuple[ExitCode, dict[str, Any], list[Issue], list[str]]
59
+
60
+
61
+ @dataclass
62
+ class _Run:
63
+ """Everything one verb needs, resolved once by `_prepare`."""
64
+
65
+ verb: str
66
+ core: str | None
67
+ build_root: str
68
+ exe: str
69
+ pre: list[str]
70
+ argv: tuple[str, ...]
71
+ guard: Any
72
+ echo: dict[str, Any] | None
73
+ expected: str | None
74
+ selected_id: str | None
75
+ report: dict[str, Any]
76
+ issues: list[Issue] = field(default_factory=list)
77
+ log: list[str] = field(default_factory=list)
78
+
79
+
80
+ def _body(verb: str, report: dict[str, Any] | None = None) -> dict[str, Any]:
81
+ return {"schemaVersion": _SCHEMA_VERSION, "verb": verb, **(report or {})}
82
+
83
+
84
+ def _refuse(verb, issue, data=None, rc=ExitCode.RUNTIME_FAILURE) -> _Result:
85
+ return rc, _body(verb, data), [issue], [f"probe {verb}: {issue.message}"]
86
+
87
+
88
+ def _arg_issue(err: pp.ProbeArgError) -> Issue:
89
+ if err.code == pp.CODE_READ_TOO_LARGE:
90
+ return Issue("probe.read-too-large", "error", str(err))
91
+ return Issue("probe.bad-argument", "error", str(err))
92
+
93
+
94
+ def _load_slices(build_root: str) -> tuple[list[tuple[str, dict]], bool, Issue | None]:
95
+ """`(slices, manifest present, warning)`. An existing but unusable manifest is a
96
+ `probe.manifest-unusable` warning: the comparison it would have armed is skipped."""
97
+ path = fc._abs_join(build_root, "system-manifest.yaml")
98
+ if not fc._is_file(path):
99
+ return [], False, None
100
+ try:
101
+ manifest = parse_system_manifest(fc._read(path))
102
+ except Exception as err: # noqa: BLE001 -- a broken manifest only loses the comparison
103
+ return [], True, Issue(
104
+ "probe.manifest-unusable", "warning",
105
+ f"{path} exists but is not usable ({type(err).__name__}: {err}); the expect_dpidr "
106
+ "comparison and the manifest's probe settings were skipped",
107
+ )
108
+ return [(sl.core_id, sl.flash_args) for sl in manifest.slices], True, None
109
+
110
+
111
+ def _attach_params(flash_args: dict[str, Any], jlink_report: dict[str, Any]):
112
+ """`(preamble, speed, expect_dpidr)` from the selected slice's flash_args; echoes a
113
+ `jlink_device` substitution into the report."""
114
+ serial = fa_str_checked(flash_args, "jlink_serial", False)
115
+ wanted = fa_str(flash_args, "jlink_device") or pp.DEFAULT_DEVICE
116
+ validate_identifier(wanted, "jlink_device", destination="a J-Link Commander script line")
117
+ device = wanted if wanted.startswith("Cortex-") else pp.DEFAULT_DEVICE
118
+ jlink_report["device"] = device
119
+ if device != wanted: # the part profile is a flash loader, not a read attach
120
+ jlink_report["deviceSubstitutedFrom"] = wanted
121
+ speed = fa_int_checked(flash_args, "jlink_speed") or _DEFAULT_JLINK_SPEED
122
+ if serial is not None:
123
+ validate_identifier(serial, "jlink_serial", destination=fc._JLINK_SERIAL_DESTINATION)
124
+ return pp.make_preamble(serial, speed, device), speed, fa_str(flash_args, "expect_dpidr")
125
+
126
+
127
+ def _prepare(
128
+ verb, core, build_root, probe_serial, probe_usb_path, jlink_path, project_dir, enumerate_probes
129
+ ) -> _Run | _Result:
130
+ slices, present, warning = _load_slices(build_root)
131
+ flash_args, selected_id, ambiguity, note = pp.select_slice(slices, core)
132
+ report: dict[str, Any] = {
133
+ "core": core, "buildRoot": build_root, "writes": False, "manifestPresent": present,
134
+ }
135
+ warn = [warning] if warning else []
136
+ if note:
137
+ warn.append(Issue("probe.no-manifest", "info", note))
138
+ if ambiguity:
139
+ return _refuse(verb, Issue("probe.failed", "error", ambiguity), report)
140
+ found = resolve_jlink(jlink_path, project_dir=project_dir)
141
+ exe = found.path if found is not None else None
142
+ report["jlink"] = {"binary": exe, "binarySource": found.source if found else None}
143
+ if exe is None:
144
+ return _refuse(verb, Issue("probe.failed", "error", fc._NO_TRUSTED_JLINK), report)
145
+ ctx = fc._Context(
146
+ sku="", build_root=build_root, sdk_root="", dry_run=False, skip_missing_tools=False,
147
+ force_confirm=False, capture=True, probe_serial=probe_serial,
148
+ probe_usb_path=probe_usb_path, project_dir=project_dir, jlink_path=jlink_path,
149
+ **({"enumerate_probes": enumerate_probes} if enumerate_probes is not None else {}),
150
+ )
151
+ try:
152
+ flash_args, selection, snapshot = fc._flow_d_probe_selection(flash_args, ctx)
153
+ echo = fc._probe_echo(selection, ctx)
154
+ if selection is not None and selection.refusal_code is not None:
155
+ data = {**report, **({"probe": echo} if echo else {})}
156
+ issue = Issue(f"flash.probe-{selection.refusal_code}", "error", selection.refusal or "")
157
+ return _refuse(verb, issue, data)
158
+ guard = None
159
+ if selection is not None and (selection.serial is not None or selection.visible is None):
160
+ guard = fc._ProbeGuard(selection, snapshot, ctx.enumerate_probes, echo,
161
+ script_prefix=_SCRIPT_PREFIX)
162
+ pre, speed, expected = _attach_params(flash_args, report["jlink"])
163
+ except (FlashPlanError, RamRunError) as err:
164
+ return _refuse(verb, Issue("probe.failed", "error", str(err)), report)
165
+ if echo:
166
+ report["probe"] = echo
167
+ report["guard"] = {"script": _GUARD_SCRIPT, "runsBeforeEachSession": guard is not None,
168
+ "note": "the probe-pin verification, spawned before each session below"}
169
+ argv = ("JLinkExe", "-device", report["jlink"]["device"], "-if", "SWD", "-speed", str(speed),
170
+ "-NoGui", "1",
171
+ "-CommanderScript")
172
+ run = _Run(verb, core, build_root, exe, pre, argv, guard, echo, expected, selected_id, report)
173
+ run.issues.extend(warn)
174
+ return run
175
+
176
+
177
+ def _session(run: _Run, label: str, script: str) -> tuple[str, str | None]:
178
+ """Spawn one read-only session. `(transcript, problem)`; the text sent and the
179
+ output go to the run's transcript."""
180
+ sent = fc._DISABLE_FW_UPDATE + script
181
+ out = fc._execute(
182
+ FlashPlan(argv=run.argv, ok_message="", jlink_script=script),
183
+ True, None, None, run.guard, jlink_exe=run.exe, script_prefix=_SCRIPT_PREFIX,
184
+ )
185
+ text = f"{out.stdout}\n{out.stderr}"
186
+ run.log.append(f"## {label} script (exactly as sent)\n{sent}\n## stdout\n{out.stdout}\n"
187
+ f"## stderr\n{out.stderr}\n")
188
+ run.report.setdefault("scripts", {})[label] = sent.splitlines()
189
+ if run.guard is not None and run.guard.tripped:
190
+ return text, run.guard.tripped
191
+ problem = None if out.success else (fc._capture_tail(out) or "J-Link failed")
192
+ return text, problem or check_session(text, loadbin=False)
193
+
194
+
195
+ def _cache_log_dir() -> str:
196
+ """One stable directory for transcripts when there is no build root, so nothing
197
+ accumulates in fresh temp dirs: `$XDG_CACHE_HOME/tan/probe-logs`, else
198
+ `~/.cache/tan/probe-logs` (`_write_transcript` prunes the oldest)."""
199
+ root = os.environ.get("XDG_CACHE_HOME") or os.path.join(os.path.expanduser("~"), ".cache")
200
+ return os.path.join(root, "tan", "probe-logs")
201
+
202
+
203
+ def _save_log(run: _Run) -> None:
204
+ stamp = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
205
+ name = re.sub(r"[^A-Za-z0-9._-]", "_", f"probe-{run.verb}-{stamp}.log")
206
+ base = os.path.join(run.build_root, "flash-logs") if os.path.isdir(run.build_root) else None
207
+ try:
208
+ directory = base or _cache_log_dir()
209
+ run.report["transcriptPath"] = fc._write_transcript(
210
+ os.path.join(directory, name), f"# tan probe {run.verb}\n" + "".join(run.log)
211
+ )
212
+ except OSError as err:
213
+ run.report["transcriptPath"] = None
214
+ run.report["transcriptError"] = str(err)
215
+
216
+
217
+ def _finish(run: _Run, rc: ExitCode, lines: list[str], *more: Issue) -> _Result:
218
+ if run.echo is not None and "identity" in run.report:
219
+ run.report["identity"]["isolation"] = run.echo.get("isolation")
220
+ run.issues.extend(more)
221
+ _save_log(run)
222
+ return rc, _body(run.verb, run.report), run.issues, lines
223
+
224
+
225
+ def _fail(run: _Run, message: str, *, read: bool = False) -> _Result:
226
+ if run.guard is not None and run.guard.tripped:
227
+ issue = Issue(f"flash.probe-{run.guard.tripped_code}", "error", run.guard.tripped)
228
+ else:
229
+ issue = (Issue("probe.read-failed", "error", message) if read
230
+ else Issue("probe.failed", "error", message))
231
+ return _finish(run, ExitCode.RUNTIME_FAILURE, [f"probe {run.verb}: {issue.message}"], issue)
232
+
233
+
234
+ def _dpidr_issue(state: str, expected: str | None, dpidr: str | None) -> Issue:
235
+ if state == "unread":
236
+ return Issue("probe.dpidr-unread", "error", "the connect banner reported no SW-DP ID")
237
+ return Issue(
238
+ "probe.dpidr-mismatch", "error",
239
+ f"expected SW-DP IDR {expected} (manifest expect_dpidr) but the probe reported "
240
+ f"{dpidr}: a different board is attached",
241
+ )
242
+
243
+
244
+ def _core_issue(message: str) -> Issue:
245
+ return Issue("probe.core-mismatch", "error", message)
246
+
247
+
248
+ def _identify(run: _Run) -> _Result:
249
+ text, problem = _session(run, "dpidr", pp.identity_script(run.pre))
250
+ if problem:
251
+ return _fail(run, f"the DPIDR read failed: {problem}")
252
+ dpidr, att = _dp_id_value(text), attached_core(text)
253
+ verdict = ap_verdict(att)
254
+ state = pp.dpidr_state(run.expected, dpidr) or ("unread" if dpidr is None else None)
255
+ ident = {
256
+ "dpidr": dpidr, "expectedDpidr": run.expected,
257
+ "dpidrMatch": None if not run.expected or dpidr is None else state is None,
258
+ "apAddr": (att or {}).get("apAddr"), "cpuid": (att or {}).get("cpuid"),
259
+ "core": verdict, "itcmVerdict": "not-checked", "apVerdict": verdict,
260
+ "attached": att, "isolation": None,
261
+ }
262
+ run.report["identity"] = ident
263
+ if state: # C: nothing else runs against a board that is not the expected one
264
+ issue = _dpidr_issue(state, run.expected, dpidr)
265
+ return _finish(run, ExitCode.RUNTIME_FAILURE, [f"probe identify: {issue.message}"], issue)
266
+ if pp.itcm_check_allowed(pp.is_he_target(run.core, run.selected_id), verdict):
267
+ text2, problem = _session(run, "coreCheck", pp.core_script(run.pre))
268
+ if problem:
269
+ return _fail(run, f"the core check failed: {problem}")
270
+ verdict, evidence = core_check(text2)
271
+ ident.update(core=verdict, itcmVerdict=evidence["itcmVerdict"],
272
+ apVerdict=evidence["apVerdict"], attached=evidence.get("ap") or att)
273
+ ident["apAddr"] = (evidence.get("ap") or att or {}).get("apAddr")
274
+ ident["cpuid"] = (evidence.get("ap") or att or {}).get("cpuid")
275
+ extra: list[Issue] = []
276
+ if ident["itcmVerdict"] == "not-checked":
277
+ extra.append(Issue("probe.itcm-not-checked", "info",
278
+ "ITCM corroboration skipped: "
279
+ + pp.itcm_skip_reason(run.core, run.selected_id, verdict)))
280
+ clash = pp.core_contradiction(pp.claimed_core(run.core, run.selected_id), verdict)
281
+ if clash:
282
+ extra.append(_core_issue(clash))
283
+ if not run.expected and not any(i.code == "probe.no-manifest" for i in run.issues):
284
+ why = "no system-manifest.yaml" if not run.report["manifestPresent"] else "no expect_dpidr armed"
285
+ extra.append(Issue("probe.no-manifest", "info",
286
+ f"{why} for the selected core; the DPIDR match was skipped"))
287
+ rc = ExitCode.RUNTIME_FAILURE if clash else ExitCode.SUCCESS
288
+ line = (f"probe identify: dpidr={dpidr} core={verdict} ap={ident['apAddr']} "
289
+ f"cpuid={ident['cpuid']}")
290
+ return _finish(run, rc, [line], *extra)
291
+
292
+
293
+ def _read(run: _Run, addr: int, words: int) -> _Result:
294
+ text, problem = _session(run, "read", pp.read_script(run.pre, addr, words))
295
+ if problem:
296
+ return _fail(run, f"the read failed: {problem}", read=True)
297
+ dpidr, att = _dp_id_value(text), attached_core(text)
298
+ run.report["attached"] = {"dpidr": dpidr, "ap": att}
299
+ state = pp.dpidr_state(run.expected, dpidr)
300
+ clash = pp.core_contradiction(pp.claimed_core(run.core, run.selected_id), ap_verdict(att))
301
+ bad = _dpidr_issue(state, run.expected, dpidr) if state else (_core_issue(clash) if clash else None)
302
+ if bad: # the words may belong to another board or core: not returned
303
+ return _finish(run, ExitCode.RUNTIME_FAILURE, [f"probe read: {bad.message}"], bad)
304
+ got = parse_mem32(text, addr, words)
305
+ if got is None:
306
+ return _fail(run, f"J-Link returned no complete dump for {words} words at 0x{addr:08X}",
307
+ read=True)
308
+ run.report["read"] = {
309
+ "address": f"0x{addr:08X}", "words": words, "bytes": 4 * words,
310
+ "data": [f"0x{w:08X}" for w in got], "attached": att,
311
+ }
312
+ lines = [f"probe read: 0x{addr + 4 * i:08X} = 0x{w:08X}" for i, w in enumerate(got)]
313
+ return _finish(run, ExitCode.SUCCESS, lines)
314
+
315
+
316
+ def _run(
317
+ verb: str,
318
+ address: str | None,
319
+ count: str | None,
320
+ core: str | None,
321
+ build_root: str,
322
+ probe_serial: str | None,
323
+ probe_usb_path: str | None,
324
+ jlink_path: str | None,
325
+ project_dir: str,
326
+ enumerate_probes: Callable[[], Any] | None = None,
327
+ ) -> _Result:
328
+ bad = ExitCode.VALIDATION_FAILURE
329
+ if verb not in VERBS:
330
+ text = f"unknown verb {verb!r}; use `tan probe identify` or `tan probe read <addr> [words]`"
331
+ return _refuse(verb, Issue("probe.unknown-verb", "error", text), rc=bad)
332
+ addr = words = None
333
+ try:
334
+ core = pp.normalise_core(core)
335
+ if verb == "read":
336
+ if address is None:
337
+ raise pp.ProbeArgError(pp.CODE_BAD_ARGUMENT, "`tan probe read` needs an address")
338
+ addr, words = pp.parse_read(address, count)
339
+ elif address is not None or count is not None:
340
+ raise pp.ProbeArgError(pp.CODE_BAD_ARGUMENT, "`tan probe identify` takes no address")
341
+ except pp.ProbeArgError as err:
342
+ return _refuse(verb, _arg_issue(err), rc=bad)
343
+ message = pp.window_refusal(addr, words) if verb == "read" else None
344
+ if message: # every core, no J-Link spawned
345
+ return _refuse(verb, Issue("probe.read-unsafe-region", "error", message), rc=bad)
346
+ run = _prepare(verb, core, build_root, probe_serial, probe_usb_path, jlink_path,
347
+ project_dir, enumerate_probes)
348
+ if not isinstance(run, _Run):
349
+ return run
350
+ return _identify(run) if verb == "identify" else _read(run, addr, words)
351
+
352
+
353
+ def probe(
354
+ ctx: typer.Context,
355
+ verb: str = typer.Argument(..., metavar="VERB", help="`identify` or `read`."),
356
+ address: str = typer.Argument(
357
+ None, metavar="[ADDR]", help="read: first word address, plain hex (0x...) or decimal, "
358
+ "4-byte aligned."),
359
+ count: str = typer.Argument(
360
+ None, metavar="[WORDS]", help="read: 32-bit words to read (default 4, at most 256; "
361
+ "more is probe.read-too-large)."),
362
+ project: str = typer.Option(None, "--project", metavar="PATH", help="Project root (defaults to '.')."),
363
+ build_root: str = typer.Option(
364
+ None, "--build-root", metavar="PATH",
365
+ help="Build root holding system-manifest.yaml (default <project>/build). When present, its "
366
+ "slice's flash_args supply expect_dpidr (compared by identify), jlink_serial/jlink_speed."),
367
+ core: str = typer.Option(
368
+ None, "--core", metavar="m55_he|m55_hp",
369
+ help="The core you mean to probe; also picks the manifest slice. identify checks the "
370
+ "attach against it (probe.core-mismatch) and reads the ITCM windows only for m55_he. "
371
+ "read refuses 0x50000000-0x5FFFFFFF on EVERY core (probe.read-unsafe-region)."),
372
+ probe_serial: str = typer.Option(None, "--probe-serial", metavar="SN", help="J-Link serial for this run."),
373
+ probe_usb_path: str = typer.Option(
374
+ None, "--probe-usb-path", metavar="BUS-PORT",
375
+ help="Select the J-Link at this USB port path (e.g. 3-4.2); verified before every JLinkExe "
376
+ "spawn exactly as `tan flash` does (TAN_PROBE_USB_PATH is exported for a masking wrapper)."),
377
+ jlink: str = typer.Option(
378
+ None, "--jlink", metavar="PATH",
379
+ help=f"The J-Link Commander binary (otherwise {JLINK_ENV}, PATH, then a SEGGER install root; "
380
+ "never the project .venv)."),
381
+ output_format: OutputFormat = typer.Option(None, "--format", help=FORMAT_HELP),
382
+ ) -> None:
383
+ """READ-ONLY J-Link probe: `identify` (SW-DP ID, access port, CPUID, core verdict) and
384
+ `read ADDR [WORDS]` (a bounded `mem32`). Never writes, erases, halts, resets or runs.
385
+
386
+ \b
387
+ tan probe identify --probe-usb-path 3-4.2 --core m55_he --format json
388
+ tan probe read 0x80010000 4 --probe-usb-path 3-4.2
389
+ """
390
+ json_mode = resolve_format(output_format, ctx.obj, choices=OutputFormat) == "json"
391
+ probe_serial = probe_serial if isinstance(probe_serial, str) else None
392
+ probe_usb_path = probe_usb_path if isinstance(probe_usb_path, str) else None
393
+ if probe_usb_path is not None and not fc.is_valid_usb_path(probe_usb_path):
394
+ raise typer.BadParameter(
395
+ f"{probe_usb_path!r} is not a USB port path like 3-4.2 (<bus>-<port>[.<port>...])",
396
+ param_hint="--probe-usb-path",
397
+ )
398
+ cwd = fc.workspace_root(project)
399
+ project_obj = fc._resolve_project(cwd, None)
400
+ root = build_root if isinstance(build_root, str) else fc._abs_join(cwd, "build")
401
+ if isinstance(build_root, str) and not root.startswith("/"):
402
+ root = fc._abs_join(cwd, root)
403
+ try:
404
+ exit_code, data, issues, lines = _run(
405
+ verb, address, count, core if isinstance(core, str) else None, root,
406
+ probe_serial, probe_usb_path, jlink if isinstance(jlink, str) else None, cwd,
407
+ )
408
+ except Exception as err: # noqa: BLE001 -- a tan bug is reported as one, with an envelope
409
+ exit_code = ExitCode.INTERNAL_FAILURE
410
+ data = {"schemaVersion": _SCHEMA_VERSION, "verb": verb}
411
+ issues = [Issue("probe.internal-failure", "error", f"{type(err).__name__}: {err}")]
412
+ lines = ["probe: internal failure"]
413
+ if json_mode:
414
+ fc.emit(Envelope(COMMAND, project_obj, data, issues, exit_code))
415
+ else:
416
+ for line in lines:
417
+ print(line, file=sys.stderr if exit_code else sys.stdout)
418
+ raise typer.Exit(int(exit_code))
419
+
420
+
421
+ probe = accept_global_flags(probe)