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/planner/carveout.py
ADDED
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Carve-out resolver -- board.yaml `ipc:` entries -> resolved shared-memory regions.
|
|
4
|
+
|
|
5
|
+
Each IPC entry maps to a `ResolvedCarveOut`: the shared-memory base/size come
|
|
6
|
+
from the SoM's resolved memory_map, the region id is a deterministic FNV-1a
|
|
7
|
+
hash, plus the default mailbox channel and page alignment. An entry that can't
|
|
8
|
+
resolve (missing mailbox metadata / no memory_map) becomes a blocked
|
|
9
|
+
ResolvedCarveOut carrying the reason. Extracted from alp_orchestrate as the #285
|
|
10
|
+
carve-out seam.
|
|
11
|
+
|
|
12
|
+
Depends only downward -- models (dataclasses; the metadata root now travels
|
|
13
|
+
on the BoardProject, tan-cli#573) and
|
|
14
|
+
som_metadata.resolve_memory_map; nothing calls back into the package __init__.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Any, Optional
|
|
20
|
+
|
|
21
|
+
from .aperture import classify_region, region_extent, resolve_aperture
|
|
22
|
+
from .models import BoardProject, IpcEntry, ResolvedCarveOut
|
|
23
|
+
from .memregion import _PAGE, _region_size_bytes
|
|
24
|
+
from .som_metadata import resolve_memory_map
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Cap on how many per-region details join into one ineligibility reason
|
|
28
|
+
# (alp-sdk#1365 split B review, Nit). The joined string is emitted VERBATIM
|
|
29
|
+
# as a SINGLE-LINE C comment in `alp_system_ipc.h` and a single-line DTS
|
|
30
|
+
# comment (`headers.py`); on the real E1M-AEN801 preset (5 excluded
|
|
31
|
+
# regions -- today's ceiling: every AEN SKU's memory_map has exactly 7
|
|
32
|
+
# rows, and the widest `ipc:` endpoint set that isn't a32_cluster-only ever
|
|
33
|
+
# matches 5 of them) the uncapped string reached ~1.5 kB, and a preset with
|
|
34
|
+
# more `memory_map:` rows grows it unboundedly. Set one above today's
|
|
35
|
+
# proven ceiling so no current preset ever truncates (excluded regions can
|
|
36
|
+
# carry DIFFERENT reasons -- flash-class containment vs. an unresolved
|
|
37
|
+
# base -- so silently dropping one is a real loss of diagnostic precision,
|
|
38
|
+
# not just length). This still bounds genuinely pathological growth (a
|
|
39
|
+
# preset with many more rows) without touching any output produced by
|
|
40
|
+
# metadata that exists today.
|
|
41
|
+
_MAX_EXCLUDED_DETAIL = 6
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _fnv1a_32(data: bytes) -> int:
|
|
45
|
+
"""FNV-1a 32-bit hash. 10 lines, no deps."""
|
|
46
|
+
h = 0x811c9dc5
|
|
47
|
+
for b in data:
|
|
48
|
+
h ^= b
|
|
49
|
+
h = (h * 0x01000193) & 0xFFFFFFFF
|
|
50
|
+
return h
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _align_down(value: int, alignment: int) -> int:
|
|
55
|
+
return value - (value % alignment)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _resolve_mailbox_channel(
|
|
59
|
+
mailbox: dict[str, Any],
|
|
60
|
+
entry_name: str,
|
|
61
|
+
claimed: set[int],
|
|
62
|
+
) -> tuple[Optional[int], Optional[str]]:
|
|
63
|
+
"""Pick the mailbox channel for an IPC entry, or explain why none.
|
|
64
|
+
|
|
65
|
+
1. A channel whose `reserved_for:` equals the entry name wins.
|
|
66
|
+
2. Otherwise the lowest-id still-unclaimed `reserved_for: app`
|
|
67
|
+
channel is handed out (each channel carries one carve-out).
|
|
68
|
+
3. Otherwise no channel: the caller blocks the entry. Channel 0 is
|
|
69
|
+
`alp_default_rpmsg`'s and is never handed to an unrelated entry.
|
|
70
|
+
"""
|
|
71
|
+
channels = list(mailbox.get("channels") or [])
|
|
72
|
+
for ch in channels:
|
|
73
|
+
if ch.get("reserved_for") == entry_name:
|
|
74
|
+
return int(ch["id"]), None
|
|
75
|
+
free = sorted(int(ch["id"]) for ch in channels
|
|
76
|
+
if ch.get("reserved_for") == "app"
|
|
77
|
+
and int(ch["id"]) not in claimed)
|
|
78
|
+
if free:
|
|
79
|
+
return free[0], None
|
|
80
|
+
named = sorted({str(ch.get("reserved_for")) for ch in channels})
|
|
81
|
+
return None, (
|
|
82
|
+
f"ipc entry '{entry_name}' has no mailbox channel: none is "
|
|
83
|
+
f"`reserved_for: {entry_name}` and no unclaimed `reserved_for: "
|
|
84
|
+
f"app` channel is left (reservations: {', '.join(named) or 'none'}). "
|
|
85
|
+
f"Rename the entry to a reserved name or add a channel to the "
|
|
86
|
+
f"SoM preset's mailbox.channels.")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _region_ipc_eligibility(
|
|
90
|
+
region: dict[str, Any],
|
|
91
|
+
aperture: tuple[int, int],
|
|
92
|
+
is_preset_authored: bool,
|
|
93
|
+
) -> tuple[bool, str]:
|
|
94
|
+
"""alp-sdk#1365 split B (P1): is `region` a legal IPC carve-out target?
|
|
95
|
+
|
|
96
|
+
Called only once `aperture` has already resolved non-`None` -- the
|
|
97
|
+
caller (`_candidate_regions` below) special-cases `aperture is None`
|
|
98
|
+
itself, honouring the legacy `carveout:` flag verbatim so this whole
|
|
99
|
+
function is a no-op on every non-Alif SoM (V2N/V2M).
|
|
100
|
+
|
|
101
|
+
Eligible iff the region's derived class (`aperture.classify_region`)
|
|
102
|
+
is not `flash`, AND -- for a region the SoM preset authored itself --
|
|
103
|
+
its `write_authority` is `customer_runtime`. A DERIVED region (SoC-level
|
|
104
|
+
`memory_regions`, or the silicon-variant fallback) needs no authority:
|
|
105
|
+
it is RAM by construction.
|
|
106
|
+
|
|
107
|
+
`mram_main` (metadata/e1m_modules/E1M-AEN{301,...,803}, all seven
|
|
108
|
+
presets) is the reason this function exists: it lists an
|
|
109
|
+
`a32_cluster`/`m55_*` endpoint, carries NO `carveout` key at all, and
|
|
110
|
+
-- back when its `base` stayed `"TBD"` -- was the only thing
|
|
111
|
+
standing between an `ipc:` entry and a carve-out inside the live
|
|
112
|
+
ATOC band (0x80578000..0x80580000). #2053 resolved `base` to
|
|
113
|
+
`0x80000000` on every AEN preset, so its extent now equals the
|
|
114
|
+
aperture exactly (the whole-device alias) and `classify_region`
|
|
115
|
+
calls it `"flash"` outright, which disqualifies it unconditionally a
|
|
116
|
+
few lines below (independent of `write_authority` entirely) -- the
|
|
117
|
+
hazard is closed on both sides of that resolution: before #2053 its
|
|
118
|
+
class was `"unresolved"` and its authored `write_authority:
|
|
119
|
+
composite` (never `customer_runtime`) already disqualified it via
|
|
120
|
+
the `cls == "unresolved"` tail further down (still reachable for any
|
|
121
|
+
future preset that authors an unresolved `base`; pinned directly by
|
|
122
|
+
`python/tests/planner/test_carveout_aperture_ordering.py`'s
|
|
123
|
+
`TestUnresolvedLegOrdering` now that no shipped preset reaches it
|
|
124
|
+
end-to-end).
|
|
125
|
+
|
|
126
|
+
AGREE contract (alp-sdk#1365 split B review, BLOCKER): `carveout:` is
|
|
127
|
+
a LEGACY OVERRIDE that must AGREE with a resolvable derived class. It
|
|
128
|
+
is NOT a second vote that can silently win against `flash`/`ram`, nor
|
|
129
|
+
against the `write_authority`-derived answer on an `unclassified`
|
|
130
|
+
row -- a disagreement is a metadata bug, refused loudly, naming BOTH
|
|
131
|
+
the derived class (with the addresses that produced it) and the
|
|
132
|
+
authored flag. Proven regression this closes: a preset-authored row
|
|
133
|
+
OUTSIDE the aperture with `write_authority: customer_runtime` AND
|
|
134
|
+
`carveout: false` used to resolve `status: ok` on the write_authority
|
|
135
|
+
answer alone, silently dropping the author's explicit `carveout:
|
|
136
|
+
false` -- exactly the OSPI-XIP-row shape `classify_region`'s
|
|
137
|
+
`"unclassified"` docstring warns must never become an IPC candidate
|
|
138
|
+
just because it resolves outside the aperture.
|
|
139
|
+
"""
|
|
140
|
+
name = region.get("name")
|
|
141
|
+
cls = classify_region(region, aperture, is_preset_authored)
|
|
142
|
+
carveout = region.get("carveout")
|
|
143
|
+
|
|
144
|
+
def _agree_or_refuse(
|
|
145
|
+
class_desc: str,
|
|
146
|
+
derived_eligible: bool,
|
|
147
|
+
) -> Optional[tuple[bool, str]]:
|
|
148
|
+
"""None when `carveout:` is absent or agrees; else the refusal."""
|
|
149
|
+
if carveout is None or bool(carveout) == derived_eligible:
|
|
150
|
+
return None
|
|
151
|
+
ext = region_extent(region)
|
|
152
|
+
where = f" [0x{ext[0]:x}, 0x{ext[1]:x})" if ext else ""
|
|
153
|
+
verdict = "eligible" if derived_eligible else "not eligible"
|
|
154
|
+
return False, (
|
|
155
|
+
f"region {name!r}{where} derives {class_desc} against the "
|
|
156
|
+
f"SoC's declared on-die MRAM aperture [0x{aperture[0]:x}, "
|
|
157
|
+
f"0x{aperture[1]:x}) -- {verdict} for an IPC carve-out -- but "
|
|
158
|
+
f"its authored `carveout: {carveout!r}` disagrees; fix the "
|
|
159
|
+
f"metadata, the derived class is authoritative, not a "
|
|
160
|
+
f"silently-honoured override")
|
|
161
|
+
|
|
162
|
+
if cls == "flash":
|
|
163
|
+
disagreement = _agree_or_refuse("flash-class", False)
|
|
164
|
+
if disagreement is not None:
|
|
165
|
+
return disagreement
|
|
166
|
+
ext = region_extent(region)
|
|
167
|
+
where = f" [0x{ext[0]:x}, 0x{ext[1]:x})" if ext else ""
|
|
168
|
+
return False, (
|
|
169
|
+
f"region {name!r}{where} is flash-class (contained in the "
|
|
170
|
+
f"SoC's declared on-die MRAM aperture) -- not safe as an IPC "
|
|
171
|
+
f"carve-out target")
|
|
172
|
+
|
|
173
|
+
if cls == "ram":
|
|
174
|
+
# #2010: likely UNREACHABLE on every SoM shipped today. `cls ==
|
|
175
|
+
# "ram"` requires `not is_preset_authored`, i.e. a row derived
|
|
176
|
+
# from the SoC's own `memory_regions:` rather than authored by
|
|
177
|
+
# the SoM preset -- but every Alif preset that resolves an
|
|
178
|
+
# aperture at all also authors an explicit `memory_map:` block
|
|
179
|
+
# (`is_preset_authored` is True for all of them), and no
|
|
180
|
+
# non-Alif SoM ever resolves an aperture in the first place
|
|
181
|
+
# (`aperture is None` short-circuits before this function is
|
|
182
|
+
# even called -- see `_candidate_regions()`). `git grep -n
|
|
183
|
+
# "carveout:" metadata/socs/` returns zero hits, confirming no
|
|
184
|
+
# SoC-derived row carries the flag either. Kept, not deleted: a
|
|
185
|
+
# future minimal Alif SoM port that relies entirely on SoC-level
|
|
186
|
+
# `memory_regions:` (no preset `memory_map:` override) would
|
|
187
|
+
# make `is_preset_authored` False while an aperture still
|
|
188
|
+
# resolves, reaching this leg for real. Pinned by direct call
|
|
189
|
+
# (`TestRamLegReachability`,
|
|
190
|
+
# tests/scripts/test_orchestrate_carveout_aperture_ordering.py)
|
|
191
|
+
# since no board.yaml in this tree can reach it end-to-end.
|
|
192
|
+
disagreement = _agree_or_refuse(
|
|
193
|
+
"ram-class (outside the aperture, not preset-authored)", True)
|
|
194
|
+
if disagreement is not None:
|
|
195
|
+
return disagreement
|
|
196
|
+
return True, ""
|
|
197
|
+
|
|
198
|
+
if cls == "unclassified":
|
|
199
|
+
wa = region.get("write_authority")
|
|
200
|
+
derived_eligible = wa == "customer_runtime"
|
|
201
|
+
disagreement = _agree_or_refuse(
|
|
202
|
+
f"an unclassified class outside the aperture "
|
|
203
|
+
f"(write_authority={wa!r})", derived_eligible)
|
|
204
|
+
if disagreement is not None:
|
|
205
|
+
return disagreement
|
|
206
|
+
if derived_eligible:
|
|
207
|
+
return True, ""
|
|
208
|
+
return False, (
|
|
209
|
+
f"region {name!r} resolves outside the declared MRAM "
|
|
210
|
+
f"aperture (containment doesn't classify it) and its "
|
|
211
|
+
f"authored write_authority is {wa!r}, not "
|
|
212
|
+
f"'customer_runtime'")
|
|
213
|
+
|
|
214
|
+
# cls == "unresolved": this region's OWN base doesn't resolve. (The
|
|
215
|
+
# caller never reaches here when the SoC declares no aperture at all
|
|
216
|
+
# -- that case is handled before this function is called.) Nothing
|
|
217
|
+
# to compare against, so honour whichever authored flag is present
|
|
218
|
+
# rather than guess: the legacy `carveout:` FIRST -- it is the
|
|
219
|
+
# conservative, pre-split-B signal, honoured VERBATIM as the fallback
|
|
220
|
+
# answer here -- then `write_authority` when no `carveout:` is
|
|
221
|
+
# authored, then refuse. (alp-sdk#1365 split B review, MAJOR 2: this
|
|
222
|
+
# used to check `write_authority` first, silently dropping an
|
|
223
|
+
# authored `carveout: false` whenever `write_authority:
|
|
224
|
+
# customer_runtime` was also present.)
|
|
225
|
+
if carveout is not None:
|
|
226
|
+
if carveout is False:
|
|
227
|
+
return False, (
|
|
228
|
+
f"region {name!r} has an unresolved base "
|
|
229
|
+
f"({region.get('base')!r}); honouring its legacy "
|
|
230
|
+
f"`carveout: false`")
|
|
231
|
+
return True, ""
|
|
232
|
+
wa = region.get("write_authority")
|
|
233
|
+
if wa is not None:
|
|
234
|
+
if wa == "customer_runtime":
|
|
235
|
+
return True, ""
|
|
236
|
+
return False, (
|
|
237
|
+
f"region {name!r} has an unresolved base "
|
|
238
|
+
f"({region.get('base')!r}) so containment against the "
|
|
239
|
+
f"declared MRAM aperture can't be verified, and its "
|
|
240
|
+
f"authored write_authority is {wa!r}, not "
|
|
241
|
+
f"'customer_runtime'")
|
|
242
|
+
return False, (
|
|
243
|
+
f"region {name!r} has an unresolved base "
|
|
244
|
+
f"({region.get('base')!r}) and carries neither "
|
|
245
|
+
f"`write_authority` nor a legacy `carveout` flag -- class "
|
|
246
|
+
f"unresolved, never guessed (ADR-0034 clause 4)")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _blocked_carve_out(entry: IpcEntry, reason: str) -> ResolvedCarveOut:
|
|
250
|
+
"""Project an IpcEntry into a blocked ResolvedCarveOut.
|
|
251
|
+
|
|
252
|
+
Used when SoM metadata isn't ready yet (TBD addresses, missing
|
|
253
|
+
mailbox controller) or the board.yaml entry can't be satisfied
|
|
254
|
+
(no region, collision, etc.). The manifest records the entry as
|
|
255
|
+
`status: blocked` + `reason: ...` so reviewers see the gap; the
|
|
256
|
+
actual slice-build step is what trips on it.
|
|
257
|
+
"""
|
|
258
|
+
return ResolvedCarveOut(
|
|
259
|
+
name=entry.name,
|
|
260
|
+
kind=entry.kind,
|
|
261
|
+
endpoints=list(entry.endpoints),
|
|
262
|
+
base=0, size=0, region="",
|
|
263
|
+
cacheable=bool(entry.cacheable) if entry.cacheable is not None else False,
|
|
264
|
+
src_ept=0, dst_ept=0, mailbox_channel=0,
|
|
265
|
+
status="blocked", reason=reason,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def resolve_carve_outs(
|
|
270
|
+
project: BoardProject,
|
|
271
|
+
) -> list[ResolvedCarveOut]:
|
|
272
|
+
"""Spec §6.1 algorithm.
|
|
273
|
+
|
|
274
|
+
1. Sort ipc entries alphabetically by name.
|
|
275
|
+
2. For each entry, pick the first memory region whose
|
|
276
|
+
accessible_from: covers every endpoint and whose `cacheable:`
|
|
277
|
+
attribute matches the entry's preference (non-cacheable by
|
|
278
|
+
default; explicit `cacheable: true` flips the preference).
|
|
279
|
+
3. Emit a `status: blocked` entry when the matching region has a
|
|
280
|
+
TBD base / size (the SoM isn't HW-mapped yet); the manifest
|
|
281
|
+
records the block reason and the actual slice-build step is
|
|
282
|
+
what fails.
|
|
283
|
+
4. Allocate top-down within the region, page-aligned (4 KiB), and
|
|
284
|
+
only inside the sub-extent every endpoint can actually address
|
|
285
|
+
(`access_windows:`, #553). An explicit `ipc[].address:` is
|
|
286
|
+
placed FIRST, bounds-checked against that same window and
|
|
287
|
+
overlap-checked against every carve-out already placed, so two
|
|
288
|
+
channels can never resolve onto one physical address and an
|
|
289
|
+
address in no region is refused instead of being labelled with
|
|
290
|
+
one (#552).
|
|
291
|
+
5. Endpoint IDs: FNV-1a of the entry name, low byte ORed with
|
|
292
|
+
0x400 for src, +1 for dst. Collision check at emit time.
|
|
293
|
+
|
|
294
|
+
Phase 3 strict-channel-reservation enforcement (spec §6.4):
|
|
295
|
+
- If the SoM preset's `mailbox.controller` is `TBD`, every
|
|
296
|
+
rpmsg entry lands blocked with a hint pointing at the preset
|
|
297
|
+
that owes the value.
|
|
298
|
+
- If the controller is set but no channel is `reserved_for:
|
|
299
|
+
alp_default_rpmsg` (and any `ipc[].kind == rpmsg` entry is
|
|
300
|
+
present), the rpmsg entries land blocked so the customer
|
|
301
|
+
adds an explicit reservation rather than silently dropping
|
|
302
|
+
the channel to 0.
|
|
303
|
+
"""
|
|
304
|
+
if not project.ipc:
|
|
305
|
+
return []
|
|
306
|
+
|
|
307
|
+
# Derive the effective memory-region table. An explicit `memory_map:`
|
|
308
|
+
# block in the SoM preset wins verbatim (non-stock partitioning); when
|
|
309
|
+
# absent the helper derives the table from the SoC variant JSON so the
|
|
310
|
+
# orchestrator doesn't need to duplicate that logic.
|
|
311
|
+
memory_map = resolve_memory_map(project.som_preset,
|
|
312
|
+
project.effective_metadata_root())
|
|
313
|
+
mailbox = dict(project.som_preset.get("mailbox") or {})
|
|
314
|
+
|
|
315
|
+
# alp-sdk#1365 split B (P1): the SoC's declared on-die flash aperture
|
|
316
|
+
# (None on every non-Alif SoM, or an Alif SoC/variant that hasn't
|
|
317
|
+
# declared one), and whether THIS SoM preset authored `memory_map:`
|
|
318
|
+
# itself -- both feed `_region_ipc_eligibility()` below. `memory_map`
|
|
319
|
+
# above is an all-or-nothing derivation (`resolve_memory_map`'s own
|
|
320
|
+
# precedence rule): every row in it is preset-authored, or none is.
|
|
321
|
+
aperture = resolve_aperture(
|
|
322
|
+
project.som_preset, project.effective_metadata_root())
|
|
323
|
+
is_preset_authored = bool(project.som_preset.get("memory_map"))
|
|
324
|
+
|
|
325
|
+
# Phase 3 strict mailbox checks (spec §6.4). Surfaces preset
|
|
326
|
+
# bugs before the user spends time on a build that would silently
|
|
327
|
+
# collide on mailbox channel 0. When metadata is incomplete, the
|
|
328
|
+
# rpmsg entries land blocked rather than aborting resolution
|
|
329
|
+
# (Phase 4 acceptance §6.1: emit a manifest, fail the build).
|
|
330
|
+
has_rpmsg_entry = any(e.kind == "rpmsg" for e in project.ipc)
|
|
331
|
+
rpmsg_block_reason: Optional[str] = None
|
|
332
|
+
if has_rpmsg_entry:
|
|
333
|
+
controller = mailbox.get("controller")
|
|
334
|
+
# Case/whitespace-insensitive: "tbd", "Tbd", " TBD " are all the
|
|
335
|
+
# same hand-typed placeholder as a bare "TBD" (alp-sdk #1048).
|
|
336
|
+
controller_is_tbd = (
|
|
337
|
+
isinstance(controller, str) and controller.strip().upper() == "TBD"
|
|
338
|
+
)
|
|
339
|
+
if controller is None or controller_is_tbd:
|
|
340
|
+
rpmsg_block_reason = (
|
|
341
|
+
f"SoM {project.sku} mailbox controller is "
|
|
342
|
+
f"{'unset' if controller is None else 'TBD'}; "
|
|
343
|
+
f"carve-out resolution requires authoritative mailbox "
|
|
344
|
+
f"metadata. Fill `mailbox.controller:` in "
|
|
345
|
+
f"metadata/e1m_modules/{project.sku}.yaml with the "
|
|
346
|
+
f"vendor mailbox node name (e.g. `renesas_mhu`, "
|
|
347
|
+
f"`alif_mhuv2`) or remove the rpmsg "
|
|
348
|
+
f"entries from board.yaml.")
|
|
349
|
+
else:
|
|
350
|
+
reserved_tags = {
|
|
351
|
+
ch.get("reserved_for")
|
|
352
|
+
for ch in (mailbox.get("channels") or [])
|
|
353
|
+
}
|
|
354
|
+
if "alp_default_rpmsg" not in reserved_tags:
|
|
355
|
+
rpmsg_block_reason = (
|
|
356
|
+
f"no mailbox channel reserved for alp_default_rpmsg "
|
|
357
|
+
f"in {project.sku}; add one with `reserved_for: "
|
|
358
|
+
f"alp_default_rpmsg` to metadata/e1m_modules/"
|
|
359
|
+
f"{project.sku}.yaml mailbox.channels (e.g. "
|
|
360
|
+
f"`- {{ id: 0, reserved_for: alp_default_rpmsg }}`).")
|
|
361
|
+
|
|
362
|
+
# Per-region high-water-mark allocator state. Initialised lazily
|
|
363
|
+
# the first time we touch a region; returns None when the region
|
|
364
|
+
# carries a TBD base or unresolvable size.
|
|
365
|
+
region_top: dict[str, int] = {}
|
|
366
|
+
# Every span already handed out in a region, INCLUDING the ones
|
|
367
|
+
# pinned by an explicit `ipc[].address:`. Without this two carve-outs
|
|
368
|
+
# resolve onto one physical window and both report `ok` (#552).
|
|
369
|
+
placed_spans: dict[str, list[tuple[int, int, str]]] = {}
|
|
370
|
+
|
|
371
|
+
def _region_top_init(region: dict[str, Any]) -> tuple[Optional[int], Optional[str]]:
|
|
372
|
+
name = region["name"]
|
|
373
|
+
if name in region_top:
|
|
374
|
+
return region_top[name], None
|
|
375
|
+
# A region derived from the SoC variant JSON (no explicit
|
|
376
|
+
# `memory_map:` in the preset) carries name/size but NO `base`
|
|
377
|
+
# until the SoM is HW-mapped. Treat a missing base the same as
|
|
378
|
+
# an explicit `TBD` so an un-mapped SoM (e.g. AEN801, whose E8
|
|
379
|
+
# SoC JSON has no per-region base yet) lands a clean *blocked*
|
|
380
|
+
# carve-out instead of crashing with KeyError: 'base'.
|
|
381
|
+
base = region.get("base")
|
|
382
|
+
size_bytes = _region_size_bytes(region)
|
|
383
|
+
base_is_unmapped = (
|
|
384
|
+
base is None
|
|
385
|
+
or (isinstance(base, str) and base.strip().upper() == "TBD")
|
|
386
|
+
)
|
|
387
|
+
if base_is_unmapped:
|
|
388
|
+
return None, (
|
|
389
|
+
f"memory_map.base is {'unset' if base is None else 'TBD'} "
|
|
390
|
+
f"for region '{name}' in SoM {project.sku}; this SoM "
|
|
391
|
+
f"hasn't been HW-mapped yet so IPC carve-outs cannot be "
|
|
392
|
+
f"allocated. Add a `memory_map:` block to "
|
|
393
|
+
f"metadata/e1m_modules/{project.sku}.yaml (or per-region "
|
|
394
|
+
f"`base`) or remove the matching ipc entry from board.yaml.")
|
|
395
|
+
if size_bytes is None:
|
|
396
|
+
return None, (
|
|
397
|
+
f"memory_map.size is unresolvable for region '{name}' "
|
|
398
|
+
f"in SoM {project.sku} (size_mib / size_kib unset or "
|
|
399
|
+
f"TBD). Cannot allocate carve-outs.")
|
|
400
|
+
# Top-down allocator: top = base + size, page-aligned.
|
|
401
|
+
top = base + size_bytes
|
|
402
|
+
top = _align_down(top, _PAGE)
|
|
403
|
+
region_top[name] = top
|
|
404
|
+
return top, None
|
|
405
|
+
|
|
406
|
+
def _endpoint_window(
|
|
407
|
+
region: dict[str, Any],
|
|
408
|
+
endpoint_set: set[str],
|
|
409
|
+
) -> tuple[int, int]:
|
|
410
|
+
"""Sub-extent of `region` that EVERY endpoint can actually address.
|
|
411
|
+
|
|
412
|
+
`accessible_from:` is a yes/no per core; it cannot say "this core
|
|
413
|
+
reaches only part of the region". The RZ/V2N Cortex-M33 does
|
|
414
|
+
exactly that: per Renesas FSP `bsp_slave_address.h` its DDR window
|
|
415
|
+
is CM33-secure `0x80000000` / CM33-non-secure `0x90000000` -> A55
|
|
416
|
+
`0x40000000`, **256 MiB**, while `ddr_main` spans 4 GiB from
|
|
417
|
+
`0x48000000`. A top-down allocation therefore handed the M33
|
|
418
|
+
`0x147f80000` -- a 33-bit address that truncates to `0x47f80000`,
|
|
419
|
+
below the DDR base, when cast to a pointer on the M33 (#553).
|
|
420
|
+
|
|
421
|
+
`access_windows:` (optional, per region, keyed by core id) declares
|
|
422
|
+
the reachable slice as an absolute `base` + `size_mib`/`size_kib` in
|
|
423
|
+
the same address space as the region's own `base`. A region that
|
|
424
|
+
declares none is reachable in full, so this returns the whole
|
|
425
|
+
extent and nothing changes for it.
|
|
426
|
+
"""
|
|
427
|
+
lo = int(region["base"])
|
|
428
|
+
hi = lo + (_region_size_bytes(region) or 0)
|
|
429
|
+
windows = region.get("access_windows") or {}
|
|
430
|
+
if not isinstance(windows, dict):
|
|
431
|
+
return lo, hi
|
|
432
|
+
for core in sorted(endpoint_set):
|
|
433
|
+
win = windows.get(core)
|
|
434
|
+
if not isinstance(win, dict):
|
|
435
|
+
continue
|
|
436
|
+
win_lo = win.get("base")
|
|
437
|
+
win_size = _region_size_bytes(win)
|
|
438
|
+
if not isinstance(win_lo, int) or win_size is None:
|
|
439
|
+
continue
|
|
440
|
+
lo = max(lo, win_lo)
|
|
441
|
+
hi = min(hi, win_lo + win_size)
|
|
442
|
+
return lo, hi
|
|
443
|
+
|
|
444
|
+
def _first_free_down(
|
|
445
|
+
top: int,
|
|
446
|
+
size_aligned: int,
|
|
447
|
+
spans: list[tuple[int, int, str]],
|
|
448
|
+
floor: int,
|
|
449
|
+
) -> int:
|
|
450
|
+
"""First page-aligned base at or below `top` colliding with nothing.
|
|
451
|
+
|
|
452
|
+
The top-down allocator must allocate AROUND spans already pinned by
|
|
453
|
+
an explicit `ipc[].address:`, not merely notice the collision --
|
|
454
|
+
refusing instead of skipping would break the ordinary case (a
|
|
455
|
+
project that pins one channel and lets the rest float). Returns a
|
|
456
|
+
base below `floor` when the region has no room left; the caller
|
|
457
|
+
reports that as a blocked entry.
|
|
458
|
+
"""
|
|
459
|
+
base = _align_down(top - size_aligned, _PAGE)
|
|
460
|
+
moved = True
|
|
461
|
+
while moved and base >= floor:
|
|
462
|
+
moved = False
|
|
463
|
+
for lo, hi, _ in sorted(spans, reverse=True):
|
|
464
|
+
if base < hi and lo < base + size_aligned:
|
|
465
|
+
base = _align_down(lo - size_aligned, _PAGE)
|
|
466
|
+
moved = True
|
|
467
|
+
return base
|
|
468
|
+
|
|
469
|
+
def _candidate_regions(
|
|
470
|
+
entry: IpcEntry,
|
|
471
|
+
) -> tuple[list[dict[str, Any]], Optional[str]]:
|
|
472
|
+
"""Regions this entry may land in, ranked; or a block reason."""
|
|
473
|
+
prefers_cacheable = (bool(entry.cacheable)
|
|
474
|
+
if entry.cacheable is not None else False)
|
|
475
|
+
endpoint_set = set(entry.endpoints)
|
|
476
|
+
|
|
477
|
+
# Filter candidates: accessibility covers every endpoint, and the
|
|
478
|
+
# region is eligible for IPC carve-out allocation.
|
|
479
|
+
#
|
|
480
|
+
# alp-sdk#1365 split B (P1): eligibility is now DERIVED against the
|
|
481
|
+
# SoC's declared on-die MRAM aperture (`_region_ipc_eligibility`)
|
|
482
|
+
# instead of trusting the legacy `carveout:` flag outright -- that
|
|
483
|
+
# flag is what previously left `mram_main` (no `carveout` key at
|
|
484
|
+
# all, base `TBD`) eligible by default, one `base:` fill away from
|
|
485
|
+
# resolving an `a32_cluster` carve-out inside the live ATOC band.
|
|
486
|
+
# When the SoC declares NO aperture (`aperture is None` -- every
|
|
487
|
+
# non-Alif SoM, or an Alif SoC/variant that omits `soc_flash_base`),
|
|
488
|
+
# this keeps the exact pre-split-B `carveout: false` filter so
|
|
489
|
+
# those SoMs resolve byte-identically.
|
|
490
|
+
candidates: list[dict[str, Any]] = []
|
|
491
|
+
excluded_names: list[str] = [] # aperture is None: legacy path
|
|
492
|
+
excluded_detail: list[str] = [] # aperture resolved: derived path
|
|
493
|
+
for region in memory_map:
|
|
494
|
+
af = set(region.get("accessible_from") or [])
|
|
495
|
+
if not endpoint_set.issubset(af):
|
|
496
|
+
continue
|
|
497
|
+
if aperture is None:
|
|
498
|
+
if region.get("carveout") is False:
|
|
499
|
+
excluded_names.append(region["name"])
|
|
500
|
+
continue
|
|
501
|
+
candidates.append(region)
|
|
502
|
+
continue
|
|
503
|
+
eligible, reason = _region_ipc_eligibility(
|
|
504
|
+
region, aperture, is_preset_authored)
|
|
505
|
+
if not eligible:
|
|
506
|
+
excluded_detail.append(f"{region['name']!r} ({reason})")
|
|
507
|
+
continue
|
|
508
|
+
candidates.append(region)
|
|
509
|
+
if not candidates:
|
|
510
|
+
if excluded_names:
|
|
511
|
+
return [], (
|
|
512
|
+
f"ipc entry '{entry.name}' endpoints {entry.endpoints} "
|
|
513
|
+
f"only match memory_map region(s) {excluded_names} in "
|
|
514
|
+
f"SoM {project.sku}, and all of them are marked "
|
|
515
|
+
f"`carveout: false` (flash-class, not safe as shared "
|
|
516
|
+
f"memory); add an allocatable region (`carveout: true` "
|
|
517
|
+
f"or omitted) to metadata/e1m_modules/{project.sku}.yaml "
|
|
518
|
+
f"or remove the matching ipc entry from board.yaml.")
|
|
519
|
+
if excluded_detail:
|
|
520
|
+
shown = excluded_detail[:_MAX_EXCLUDED_DETAIL]
|
|
521
|
+
omitted = len(excluded_detail) - len(shown)
|
|
522
|
+
more = (
|
|
523
|
+
f"; and {omitted} more region(s) also excluded (reasons "
|
|
524
|
+
f"omitted, not necessarily the same one -- list capped "
|
|
525
|
+
f"at {_MAX_EXCLUDED_DETAIL})"
|
|
526
|
+
if omitted > 0 else "")
|
|
527
|
+
return [], (
|
|
528
|
+
f"ipc entry '{entry.name}' endpoints {entry.endpoints} "
|
|
529
|
+
f"only match memory_map region(s) in SoM {project.sku} "
|
|
530
|
+
f"that are ineligible for an IPC carve-out: "
|
|
531
|
+
f"{'; '.join(shown)}{more}. Add a region whose "
|
|
532
|
+
f"resolved base sits outside the SoC's declared MRAM "
|
|
533
|
+
f"aperture (and, if the SoM preset authors the row "
|
|
534
|
+
f"itself, carries `write_authority: "
|
|
535
|
+
f"customer_runtime`) to "
|
|
536
|
+
f"metadata/e1m_modules/{project.sku}.yaml, or remove "
|
|
537
|
+
f"the matching ipc entry from board.yaml.")
|
|
538
|
+
return [], (
|
|
539
|
+
f"ipc entry '{entry.name}' endpoints {entry.endpoints} "
|
|
540
|
+
f"have no matching memory_map region in SoM "
|
|
541
|
+
f"{project.sku}")
|
|
542
|
+
|
|
543
|
+
# Prefer the region whose `cacheable:` flag matches the entry's
|
|
544
|
+
# preference. Default carve-out is non-cacheable.
|
|
545
|
+
def _rank(r: dict[str, Any]) -> tuple[int, int]:
|
|
546
|
+
cacheable_match = (bool(r.get("cacheable")) == prefers_cacheable)
|
|
547
|
+
# Smaller region size first (avoid eating the giant DDR
|
|
548
|
+
# region with tiny carve-outs when ocram fits).
|
|
549
|
+
size_b = _region_size_bytes(r) or 1 << 62
|
|
550
|
+
return (0 if cacheable_match else 1, size_b)
|
|
551
|
+
|
|
552
|
+
candidates.sort(key=_rank)
|
|
553
|
+
return candidates, None
|
|
554
|
+
|
|
555
|
+
def _carve_size_aligned(entry: IpcEntry) -> int:
|
|
556
|
+
carve_size = entry.carve_out_kb * 1024
|
|
557
|
+
return ((carve_size + _PAGE - 1) // _PAGE) * _PAGE
|
|
558
|
+
|
|
559
|
+
def _overlap(
|
|
560
|
+
region_name: str,
|
|
561
|
+
base: int,
|
|
562
|
+
size_aligned: int,
|
|
563
|
+
) -> Optional[tuple[int, int, str]]:
|
|
564
|
+
top = base + size_aligned
|
|
565
|
+
for lo, hi, peer in placed_spans.get(region_name, []):
|
|
566
|
+
if not (top <= lo or base >= hi):
|
|
567
|
+
return (lo, hi, peer)
|
|
568
|
+
return None
|
|
569
|
+
|
|
570
|
+
def _place_pinned(
|
|
571
|
+
entry: IpcEntry,
|
|
572
|
+
) -> tuple[Optional[dict[str, Any]], int, Optional[str]]:
|
|
573
|
+
"""Honour an explicit `ipc[].address:` -- bounds- and overlap-checked."""
|
|
574
|
+
candidates, block_reason = _candidate_regions(entry)
|
|
575
|
+
if block_reason is not None:
|
|
576
|
+
return None, 0, block_reason
|
|
577
|
+
|
|
578
|
+
base = entry.address or 0
|
|
579
|
+
if base % _PAGE != 0:
|
|
580
|
+
return None, 0, (
|
|
581
|
+
f"ipc entry '{entry.name}' explicit address "
|
|
582
|
+
f"0x{base:x} is not page-aligned (4 KiB)")
|
|
583
|
+
|
|
584
|
+
size_aligned = _carve_size_aligned(entry)
|
|
585
|
+
endpoint_set = set(entry.endpoints)
|
|
586
|
+
|
|
587
|
+
# The pinned range decides the region -- the ranked-first region is
|
|
588
|
+
# only a PREFERENCE, and labelling a pin with it was how
|
|
589
|
+
# `0xdeadb000` came back tagged `region: ocram_low` (#552).
|
|
590
|
+
windows: list[str] = []
|
|
591
|
+
chosen: Optional[dict[str, Any]] = None
|
|
592
|
+
tbd_reason: Optional[str] = None
|
|
593
|
+
for region in candidates:
|
|
594
|
+
_top, region_block_reason = _region_top_init(region)
|
|
595
|
+
if region_block_reason is not None:
|
|
596
|
+
tbd_reason = tbd_reason or region_block_reason
|
|
597
|
+
continue
|
|
598
|
+
lo, hi = _endpoint_window(region, endpoint_set)
|
|
599
|
+
windows.append(f"{region['name']} 0x{lo:x}..0x{hi:x}")
|
|
600
|
+
if lo <= base and base + size_aligned <= hi:
|
|
601
|
+
chosen = region
|
|
602
|
+
break
|
|
603
|
+
if chosen is None:
|
|
604
|
+
if not windows:
|
|
605
|
+
return None, 0, (tbd_reason or (
|
|
606
|
+
f"ipc entry '{entry.name}' explicit address 0x{base:x} "
|
|
607
|
+
f"cannot be checked: no candidate region in SoM "
|
|
608
|
+
f"{project.sku} carries a resolvable base + size"))
|
|
609
|
+
return None, 0, (
|
|
610
|
+
f"ipc entry '{entry.name}' explicit address range "
|
|
611
|
+
f"0x{base:x}..0x{base + size_aligned:x} lies outside every "
|
|
612
|
+
f"memory_map region endpoints {entry.endpoints} can reach "
|
|
613
|
+
f"on SoM {project.sku} (reachable: {', '.join(windows)}); "
|
|
614
|
+
f"drop the `address:` override to let the allocator place "
|
|
615
|
+
f"it, or move it inside one of those windows.")
|
|
616
|
+
|
|
617
|
+
region_name = chosen["name"]
|
|
618
|
+
clash = _overlap(region_name, base, size_aligned)
|
|
619
|
+
if clash is not None:
|
|
620
|
+
return None, 0, (
|
|
621
|
+
f"ipc entry '{entry.name}' explicit address 0x{base:x} "
|
|
622
|
+
f"({entry.carve_out_kb} KiB) overlaps carve-out "
|
|
623
|
+
f"'{clash[2]}' already placed at 0x{clash[0]:x}..0x"
|
|
624
|
+
f"{clash[1]:x} in region '{region_name}'")
|
|
625
|
+
|
|
626
|
+
placed_spans.setdefault(region_name, []).append(
|
|
627
|
+
(base, base + size_aligned, entry.name))
|
|
628
|
+
# A pin does NOT move the bump allocator's high-water mark -- it may
|
|
629
|
+
# sit deliberately low -- but `placed_spans` now makes the allocator
|
|
630
|
+
# step over it.
|
|
631
|
+
return chosen, base, None
|
|
632
|
+
|
|
633
|
+
def _place_auto(
|
|
634
|
+
entry: IpcEntry,
|
|
635
|
+
) -> tuple[Optional[dict[str, Any]], int, Optional[str]]:
|
|
636
|
+
"""Allocate top-down inside the endpoint-reachable window."""
|
|
637
|
+
candidates, block_reason = _candidate_regions(entry)
|
|
638
|
+
if block_reason is not None:
|
|
639
|
+
return None, 0, block_reason
|
|
640
|
+
|
|
641
|
+
chosen = candidates[0]
|
|
642
|
+
region_name = chosen["name"]
|
|
643
|
+
|
|
644
|
+
# Initialise the region (and surface any TBD field). Blocked
|
|
645
|
+
# regions emit a blocked entry rather than aborting.
|
|
646
|
+
_top, region_block_reason = _region_top_init(chosen)
|
|
647
|
+
if region_block_reason is not None:
|
|
648
|
+
return None, 0, region_block_reason
|
|
649
|
+
|
|
650
|
+
size_aligned = _carve_size_aligned(entry)
|
|
651
|
+
window_lo, window_hi = _endpoint_window(chosen, set(entry.endpoints))
|
|
652
|
+
# Descend from whichever is lower: the region's remaining top, or
|
|
653
|
+
# the ceiling of the reachable window (#553).
|
|
654
|
+
start = _align_down(min(region_top[region_name], window_hi), _PAGE)
|
|
655
|
+
base = _first_free_down(
|
|
656
|
+
start, size_aligned, placed_spans.get(region_name, []), window_lo)
|
|
657
|
+
if base < window_lo:
|
|
658
|
+
region_lo = int(chosen["base"])
|
|
659
|
+
region_hi = region_lo + (_region_size_bytes(chosen) or 0)
|
|
660
|
+
window_note = ""
|
|
661
|
+
if (window_lo, window_hi) != (region_lo, region_hi):
|
|
662
|
+
window_note = (
|
|
663
|
+
f"; endpoints {entry.endpoints} reach only "
|
|
664
|
+
f"0x{window_lo:x}..0x{window_hi:x} of it "
|
|
665
|
+
f"(0x{region_lo:x}..0x{region_hi:x})")
|
|
666
|
+
return None, 0, (
|
|
667
|
+
f"ipc entry '{entry.name}' ({entry.carve_out_kb} "
|
|
668
|
+
f"KiB) doesn't fit in region '{region_name}' "
|
|
669
|
+
f"after prior allocations{window_note}")
|
|
670
|
+
|
|
671
|
+
region_top[region_name] = base
|
|
672
|
+
placed_spans.setdefault(region_name, []).append(
|
|
673
|
+
(base, base + size_aligned, entry.name))
|
|
674
|
+
return chosen, base, None
|
|
675
|
+
|
|
676
|
+
# Sort entries alphabetically by name for determinism.
|
|
677
|
+
sorted_entries = sorted(project.ipc, key=lambda e: e.name)
|
|
678
|
+
|
|
679
|
+
# Placement runs in TWO passes: explicit `address:` pins first, then
|
|
680
|
+
# the bump allocator around them. A single name-sorted pass let an
|
|
681
|
+
# auto-allocated `a_chan` take `0x80000` before the pinned `b_chan`
|
|
682
|
+
# asked for it, and both came back `ok` on the same address (#552).
|
|
683
|
+
# Emission below still walks `sorted_entries`, so ordering -- and the
|
|
684
|
+
# endpoint-id collision report -- is unchanged. Keyed by INDEX, not
|
|
685
|
+
# name: board.schema.json does not require `ipc[].name` to be unique
|
|
686
|
+
# (a duplicate is caught by the endpoint-id check further down), and a
|
|
687
|
+
# name-keyed map would silently give the twins one placement.
|
|
688
|
+
placeable = [(i, e) for i, e in enumerate(sorted_entries)
|
|
689
|
+
if not (e.kind == "rpmsg" and rpmsg_block_reason is not None)]
|
|
690
|
+
placement: dict[int, tuple[Optional[dict[str, Any]], int, Optional[str]]] = {}
|
|
691
|
+
for index, entry in placeable:
|
|
692
|
+
if entry.address is not None:
|
|
693
|
+
placement[index] = _place_pinned(entry)
|
|
694
|
+
for index, entry in placeable:
|
|
695
|
+
if entry.address is None:
|
|
696
|
+
placement[index] = _place_auto(entry)
|
|
697
|
+
|
|
698
|
+
resolved: list[ResolvedCarveOut] = []
|
|
699
|
+
# Channels reserved by exact entry name are claimed up front so an
|
|
700
|
+
# earlier-sorted unnamed entry can never take one of them.
|
|
701
|
+
claimed_channels: set[int] = {
|
|
702
|
+
int(ch["id"]) for ch in (mailbox.get("channels") or [])
|
|
703
|
+
if ch.get("reserved_for") in {e.name for e in sorted_entries}}
|
|
704
|
+
seen_low_bytes: dict[int, str] = {}
|
|
705
|
+
|
|
706
|
+
for index, entry in enumerate(sorted_entries):
|
|
707
|
+
# Mailbox metadata blocked? rpmsg entries can't proceed.
|
|
708
|
+
if entry.kind == "rpmsg" and rpmsg_block_reason is not None:
|
|
709
|
+
resolved.append(_blocked_carve_out(entry, rpmsg_block_reason))
|
|
710
|
+
continue
|
|
711
|
+
|
|
712
|
+
chosen, base, place_reason = placement[index]
|
|
713
|
+
if place_reason is not None or chosen is None:
|
|
714
|
+
resolved.append(_blocked_carve_out(
|
|
715
|
+
entry, place_reason or "carve-out could not be placed"))
|
|
716
|
+
continue
|
|
717
|
+
region_name = chosen["name"]
|
|
718
|
+
carve_size_aligned = _carve_size_aligned(entry)
|
|
719
|
+
|
|
720
|
+
# Endpoint ID derivation.
|
|
721
|
+
h = _fnv1a_32(entry.name.encode("utf-8"))
|
|
722
|
+
low = h & 0x0FF
|
|
723
|
+
if low in seen_low_bytes:
|
|
724
|
+
resolved.append(_blocked_carve_out(entry, (
|
|
725
|
+
f"ipc entry '{entry.name}' endpoint-id low byte "
|
|
726
|
+
f"0x{low:02x} collides with prior entry "
|
|
727
|
+
f"'{seen_low_bytes[low]}'. Rename one of the channels.")))
|
|
728
|
+
continue
|
|
729
|
+
seen_low_bytes[low] = entry.name
|
|
730
|
+
src_ept = 0x400 | low
|
|
731
|
+
dst_ept = src_ept + 1
|
|
732
|
+
|
|
733
|
+
mbox, mbox_reason = _resolve_mailbox_channel(
|
|
734
|
+
mailbox, entry.name, claimed_channels)
|
|
735
|
+
if mbox is None:
|
|
736
|
+
resolved.append(_blocked_carve_out(entry, mbox_reason or ""))
|
|
737
|
+
continue
|
|
738
|
+
claimed_channels.add(mbox)
|
|
739
|
+
|
|
740
|
+
resolved.append(ResolvedCarveOut(
|
|
741
|
+
name=entry.name,
|
|
742
|
+
kind=entry.kind,
|
|
743
|
+
endpoints=list(entry.endpoints),
|
|
744
|
+
base=base,
|
|
745
|
+
size=carve_size_aligned,
|
|
746
|
+
region=region_name,
|
|
747
|
+
cacheable=bool(chosen.get("cacheable", False))
|
|
748
|
+
if entry.cacheable is None
|
|
749
|
+
else bool(entry.cacheable),
|
|
750
|
+
src_ept=src_ept,
|
|
751
|
+
dst_ept=dst_ept,
|
|
752
|
+
mailbox_channel=mbox,
|
|
753
|
+
))
|
|
754
|
+
|
|
755
|
+
return resolved
|