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,192 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Which core owns a board.yaml `cameras:` entry -- the ONE ownership rule.
3
+
4
+ A dependency-free leaf (like `slugs.py`): plain dicts in, plain data out, so
5
+ the CLI validator (`alp_cli/validator.py`, lazy import) and the build planner
6
+ (`cameras.py`) cannot disagree.
7
+
8
+ A camera is owned by exactly one core, and ONLY that core's build gets the
9
+ camera (`-DSHIELD` for Zephyr, `ALP_CAMERA_CAM<n>` for Yocto).
10
+
11
+ * `cameras[].core` names the owner explicitly; it must be a candidate.
12
+ * Otherwise the candidates are the cores whose OS the camera's CONNECTOR
13
+ supports: a Zephyr core running a customer app (not `alp-stock-shim`)
14
+ needs the connector's `zephyr_shields` and the module's `zephyr_shield`; a
15
+ Yocto core needs the connector's `linux: true`. Exactly one -> it owns
16
+ the camera; none or several is an error (several: set `cameras[].core`).
17
+
18
+ `cores` maps core id -> {"os": ..., "app": ..., "board": ...}, already merged
19
+ from the board.yaml `cores:` over the SoM preset's `topology:`.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from dataclasses import dataclass, field
25
+ from pathlib import Path
26
+ from typing import Any, Optional
27
+
28
+ #: The SDK's own M-core shim (firmware/alp-stock-shim): never a camera owner.
29
+ STOCK_SHIM_APP = "alp-stock-shim"
30
+
31
+
32
+ @dataclass
33
+ class CameraPlan:
34
+ connector: str
35
+ module: str
36
+ owner: Optional[str]
37
+ errors: list[str] = field(default_factory=list)
38
+ #: With no owner: the cores whose build this camera's errors block -- the
39
+ #: ones that could own it (usable, or of an OS the connector supports),
40
+ #: never an unrelated core (e.g. the AEN A32 for a Zephyr-only connector).
41
+ blocks: list[str] = field(default_factory=list)
42
+
43
+
44
+ def resolve_cores(project_cores: Any, topology: Any) -> dict[str, dict[str, Any]]:
45
+ """Merge board.yaml `cores:` over the SoM `topology:` into
46
+ {id: {os, app, board}}. `os` falls back the way the loader's class rule
47
+ does for the shipped SoMs: a Zephyr `board:` -> zephyr, a Yocto
48
+ `machine:` -> yocto, else off."""
49
+ topo = topology if isinstance(topology, dict) else {}
50
+ proj = project_cores if isinstance(project_cores, dict) else {}
51
+ out: dict[str, dict[str, Any]] = {}
52
+ for cid, base in topo.items():
53
+ entry = dict(base) if isinstance(base, dict) else {}
54
+ if isinstance(proj.get(cid), dict):
55
+ entry.update(proj[cid])
56
+ os_ = entry.get("os") or (
57
+ "zephyr" if entry.get("board")
58
+ else "yocto" if entry.get("machine") else "off")
59
+ out[cid] = {"os": os_, "app": entry.get("app"), "board": entry.get("board")}
60
+ return out
61
+
62
+
63
+ def candidates(cores: dict[str, dict[str, Any]]) -> list[str]:
64
+ return [cid for cid, c in cores.items()
65
+ if c["os"] == "yocto"
66
+ or (c["os"] == "zephyr" and c.get("app")
67
+ and c["app"] != STOCK_SHIM_APP)]
68
+
69
+
70
+ def _usable(core: dict[str, Any], connector: dict[str, Any],
71
+ module_doc: Optional[dict[str, Any]]) -> bool:
72
+ """Can a core of this OS drive a camera of this module on this connector?"""
73
+ if core["os"] == "yocto":
74
+ return bool(connector.get("linux"))
75
+ return bool(connector.get("zephyr_shields")) and (
76
+ module_doc is None or bool(module_doc.get("zephyr_shield")))
77
+
78
+
79
+ def camera_owner(cores: dict[str, dict[str, Any]], explicit: Optional[str],
80
+ conn: str, connector: dict[str, Any], module: str,
81
+ module_doc: Optional[dict[str, Any]]
82
+ ) -> tuple[Optional[str], Optional[str]]:
83
+ """(owner core id, None) or (None, error message).
84
+
85
+ An explicit `core:` must be a structural candidate (the OS-specific
86
+ problems are reported by `plan_cameras`). Otherwise the candidates are
87
+ the structural ones whose OS the connector (and, for Zephyr, the module)
88
+ supports: one -> owner; none or several -> error."""
89
+ cands = candidates(cores)
90
+ if explicit is not None:
91
+ if explicit in cands:
92
+ return explicit, None
93
+ return None, (f"cameras: core '{explicit}' is not a Zephyr core running a "
94
+ f"customer app or a Yocto core of this project "
95
+ f"(candidates: {', '.join(cands) or 'none'})")
96
+ usable = [c for c in cands if _usable(cores[c], connector, module_doc)]
97
+ if len(usable) == 1:
98
+ return usable[0], None
99
+ if usable:
100
+ return None, (f"cameras: ambiguous camera owner for {conn} "
101
+ f"({', '.join(usable)}): set `cameras[].core`")
102
+ supports = [n for n, ok in (("Zephyr", connector.get("zephyr_shields")),
103
+ ("Linux (Yocto)", connector.get("linux"))) if ok]
104
+ if not supports:
105
+ return None, (f"cameras: connector {conn} declares neither "
106
+ f"`zephyr_shields:` nor `linux: true`, so no core can own "
107
+ f"its camera")
108
+ why = ""
109
+ if module_doc is not None and not module_doc.get("zephyr_shield"):
110
+ why = (f"; module '{module}' has no `zephyr_shield:` in "
111
+ f"metadata/camera_modules/{module}.yaml, so Zephyr cannot use it")
112
+ return None, (f"cameras: no core can own the camera on {conn}: the connector "
113
+ f"supports {' and '.join(supports)} only, and the project has "
114
+ f"no matching core (Zephyr customer app / Yocto){why}")
115
+
116
+
117
+ def has_overlay(shield: str, board: str, repo: Path) -> bool:
118
+ """Zephyr applies `boards/<board>_<qualifiers>.overlay`, else `<board>.overlay`."""
119
+ name, *quals = board.split()[0].split("/")
120
+ d = repo / "zephyr" / "boards" / "shields" / shield / "boards"
121
+ return any((d / f"{n}.overlay").is_file()
122
+ for n in ("_".join([name, *quals]), name))
123
+
124
+
125
+ def plan_cameras(cameras: Any, connectors: Any, cores: dict[str, dict[str, Any]],
126
+ modules: dict[str, Any], repo: Path) -> list[CameraPlan]:
127
+ """One CameraPlan per `cameras:` entry: its owner and every static
128
+ problem (empty `errors` = buildable). `modules` maps module id -> its
129
+ camera-module YAML (absent id: reported elsewhere, not here)."""
130
+ connectors = connectors if isinstance(connectors, dict) else {}
131
+ plans: list[CameraPlan] = []
132
+ seen: dict[tuple[str, str], str] = {} # (owner, module shield) -> connector
133
+ for entry in cameras if isinstance(cameras, list) else []:
134
+ if not isinstance(entry, dict):
135
+ continue
136
+ conn, mod = entry.get("connector"), entry.get("module")
137
+ if not isinstance(conn, str) or not isinstance(mod, str):
138
+ continue
139
+ if conn not in connectors: # reported by the validator / planner
140
+ plans.append(CameraPlan(conn, mod, None))
141
+ continue
142
+ connector = connectors[conn] if isinstance(connectors[conn], dict) else {}
143
+ owner, err = camera_owner(cores, entry.get("core"), conn, connector,
144
+ mod, modules.get(mod))
145
+ plan = CameraPlan(conn, mod, owner, [err] if err else [])
146
+ plans.append(plan)
147
+ if owner is None:
148
+ cands = candidates(cores)
149
+ usable = [c for c in cands
150
+ if _usable(cores[c], connector, modules.get(mod))]
151
+ by_os = [c for c in cands
152
+ if connector.get("linux" if cores[c]["os"] == "yocto"
153
+ else "zephyr_shields")]
154
+ plan.blocks = usable if len(usable) > 1 else (by_os or cands)
155
+ if owner is not None and cores[owner]["os"] == "yocto" and not connector.get("linux"):
156
+ plan.errors.append(
157
+ f"cameras: connector {conn} does not declare Linux support "
158
+ f"(`linux: true`), so Yocto core '{owner}' cannot own its camera")
159
+ if owner is None or cores[owner]["os"] != "zephyr":
160
+ continue
161
+ shield = (modules.get(mod) or {}).get("zephyr_shield")
162
+ carrier = connector.get("zephyr_shields") or []
163
+ if mod in modules and not shield:
164
+ plan.errors.append(
165
+ f"cameras: module '{mod}' on {conn} has no `zephyr_shield:` in "
166
+ f"metadata/camera_modules/{mod}.yaml -- it cannot be selected on "
167
+ f"Zephyr core '{owner}'; use a module with one, or give the "
168
+ f"camera to a Yocto core with `cameras[].core`")
169
+ if not carrier:
170
+ plan.errors.append(
171
+ f"cameras: connector {conn} declares no `zephyr_shields:` -- no "
172
+ f"Zephyr carrier shield exists for it, so Zephyr core '{owner}' "
173
+ f"cannot own its camera")
174
+ board = cores[owner].get("board")
175
+ if board:
176
+ for sh in carrier:
177
+ if not has_overlay(sh, board, repo):
178
+ plan.errors.append(
179
+ f"cameras: no Zephyr shield overlay for board target "
180
+ f"'{board}' (core '{owner}'): shield '{sh}' has no "
181
+ f"zephyr/boards/shields/{sh}/boards/<board>.overlay for "
182
+ f"it -- add one for that SoM target")
183
+ if shield:
184
+ # A module shield instantiates ONE camera node; two connectors
185
+ # naming the same module can't both be expressed in one -DSHIELD.
186
+ if (owner, shield) in seen:
187
+ plan.errors.append(
188
+ f"cameras: module shield '{shield}' is used by both "
189
+ f"{seen[(owner, shield)]} and {conn} on core '{owner}'; a "
190
+ f"Zephyr shield is a single instance")
191
+ seen.setdefault((owner, shield), conn)
192
+ return plans
tan/planner/cameras.py ADDED
@@ -0,0 +1,132 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Resolve a project's `cameras: [{connector, module, core?}]` into build inputs.
3
+
4
+ One resolver for both OS lanes, so the same board.yaml line picks the camera
5
+ on every SoM. Ownership (which core gets the camera) is `camera_owner.py`,
6
+ shared with the CLI validator; ONLY the owner core's build is touched (the owner is the core whose OS the
7
+ connector supports, see there):
8
+
9
+ * Zephyr owner -> ONE `-DSHIELD=<carrier shields> <module shield>` define
10
+ (carrier half from the board's `camera_connectors.<CAMn>.zephyr_shields`,
11
+ camera half from `metadata/camera_modules/<module>.yaml: zephyr_shield`).
12
+ On a sysbuild it is `-D<image>_SHIELD=...` so MCUboot does not get it.
13
+ The space-joined value is ONE argv element (a shell would need it quoted:
14
+ `-DSHIELD="a b"`).
15
+ * Yocto owner -> `ALP_CAMERA_CAM<n> = "<module_id>"` in local.conf. The
16
+ kernel bbappend keys the sensor DT include generated by
17
+ scripts/gen_camera_dt.py (#2736) on that variable; it reads CAM0 only.
18
+
19
+ Connector `CAMn` is camera index `n` (`alp-camera<n>` in Zephyr DT, the `n`
20
+ in `ALP_CAMERA_CAM<n>`).
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from pathlib import Path
26
+ from typing import Optional
27
+
28
+ import yaml
29
+
30
+ from .camera_owner import CameraPlan, plan_cameras
31
+ from .models import BoardProject, OrchestratorError, Slice
32
+ from .paths import REPO
33
+
34
+
35
+ class CameraSelectError(OrchestratorError):
36
+ """A `cameras:` entry cannot be turned into a build input for a slice."""
37
+
38
+
39
+ def _connectors(project: BoardProject) -> dict:
40
+ # A `preset:` board carries them; an inline board's live in board.yaml.
41
+ return ((project.board_preset or {}).get("camera_connectors")
42
+ or project.raw.get("camera_connectors") or {})
43
+
44
+
45
+ def _module(project: BoardProject, module_id: str) -> Optional[dict]:
46
+ p = project.effective_metadata_root() / "camera_modules" / f"{module_id}.yaml"
47
+ try:
48
+ return yaml.safe_load(p.read_text(encoding="utf-8")) or {}
49
+ except OSError:
50
+ return None
51
+
52
+
53
+ def _cores(project: BoardProject) -> dict:
54
+ return {cid: {"os": s.os, "app": s.app, "board": s.board}
55
+ for cid, s in project.cores.items()}
56
+
57
+
58
+ def _plans(project: BoardProject, repo: Path = REPO) -> list[CameraPlan]:
59
+ cams = sorted(project.raw.get("cameras") or [],
60
+ key=lambda c: int(c["connector"][3:]))
61
+ if not cams:
62
+ return []
63
+ modules = {c["module"]: _module(project, c["module"]) for c in cams}
64
+ plans = plan_cameras(cams, _connectors(project), _cores(project),
65
+ {m: d for m, d in modules.items() if d is not None}, repo)
66
+ connectors = _connectors(project)
67
+ for p in plans:
68
+ if modules.get(p.module) is None:
69
+ p.errors.append(f"cameras: unknown module '{p.module}' "
70
+ f"(no metadata/camera_modules/{p.module}.yaml)")
71
+ if p.connector not in connectors:
72
+ p.errors.append(f"cameras: connector '{p.connector}' is not a camera "
73
+ f"connector of this board")
74
+ return plans
75
+
76
+
77
+ def check(project: BoardProject, slice_: Slice,
78
+ repo: Path = REPO) -> list[CameraPlan]:
79
+ """The cameras this slice owns. Raises CameraSelectError when they are
80
+ unbuildable for it (the backstop for what `tan validate` reports as
81
+ ALP-B003). An ownerless error (none / ambiguous) blocks only the cores
82
+ that could have owned the camera (`CameraPlan.blocks`)."""
83
+ plans = _plans(project, repo)
84
+ mine = [p for p in plans if p.owner == slice_.core_id]
85
+ errs = [e for p in plans
86
+ if p.owner == slice_.core_id
87
+ or (p.owner is None and slice_.core_id in p.blocks)
88
+ for e in p.errors]
89
+ if errs:
90
+ raise CameraSelectError("; ".join(dict.fromkeys(errs)))
91
+ return mine
92
+
93
+
94
+ def zephyr_shield_define(project: BoardProject, slice_: Slice,
95
+ image: Optional[str] = None,
96
+ repo: Path = REPO) -> Optional[str]:
97
+ """`SHIELD=<...>` (`<image>_SHIELD=<...>` on a sysbuild) for the Zephyr
98
+ slice that owns cameras, else None."""
99
+ mine = check(project, slice_, repo)
100
+ if not mine or slice_.os != "zephyr":
101
+ return None
102
+ connectors = _connectors(project)
103
+ shields: list[str] = []
104
+ for p in mine:
105
+ shields += connectors[p.connector]["zephyr_shields"]
106
+ shields.append(_module(project, p.module)["zephyr_shield"])
107
+ shields = list(dict.fromkeys(shields)) # a carrier shield is shared
108
+ return f"{image + '_' if image else ''}SHIELD=" + " ".join(shields)
109
+
110
+
111
+ def shield_define_for_build(project: BoardProject, slice_: Slice,
112
+ base_dir: Optional[Path] = None) -> Optional[str]:
113
+ """`zephyr_shield_define` with the sysbuild image prefix resolved the way
114
+ `_slice_command` names `<image>_EXTRA_CONF_FILE` (#866)."""
115
+ if not check(project, slice_) or slice_.os != "zephyr":
116
+ return None
117
+ from .orchestrator import _zephyr_app_dir
118
+ from .secure import emit_sysbuild_conf, emit_tfm_sysbuild_conf
119
+ image = None
120
+ if emit_sysbuild_conf(project) or emit_tfm_sysbuild_conf(project):
121
+ image = _zephyr_app_dir(
122
+ slice_.app, base_dir or project.source_dir or Path.cwd()).name
123
+ return zephyr_shield_define(project, slice_, image)
124
+
125
+
126
+ def yocto_camera_lines(project: BoardProject, slice_: Slice) -> list[str]:
127
+ """local.conf lines for the cameras this Yocto slice owns. Never raises:
128
+ `tan validate` and the command path report the problems."""
129
+ if slice_.os != "yocto":
130
+ return []
131
+ return [f'ALP_CAMERA_CAM{p.connector[3:]} = "{p.module}"'
132
+ for p in _plans(project) if p.owner == slice_.core_id]