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/partition.py
ADDED
|
@@ -0,0 +1,1160 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Storage-partition resolver -- board.yaml `storage:` entries -> resolved partitions.
|
|
4
|
+
|
|
5
|
+
Resolves each StorageEntry to a ResolvedPartition: picks the flash device (the
|
|
6
|
+
SoM memory_map region names + on_module.ospi_memories keys), computes base/size
|
|
7
|
+
with page alignment, and flags overlaps. An entry that can't resolve (unknown
|
|
8
|
+
device / no size) becomes a blocked ResolvedPartition. Extracted from
|
|
9
|
+
alp_orchestrate as the #285 partition seam.
|
|
10
|
+
|
|
11
|
+
Depends only downward -- models (which carries the metadata root the project
|
|
12
|
+
was resolved against, tan-cli#573), memregion (_PAGE /
|
|
13
|
+
_region_size_bytes), and som_metadata.resolve_memory_map; nothing calls back into
|
|
14
|
+
the package __init__.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import sys
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Any, Optional
|
|
22
|
+
|
|
23
|
+
from .aperture import is_partition_inside_aperture, resolve_aperture
|
|
24
|
+
from .memregion import _PAGE, _region_size_bytes
|
|
25
|
+
from .models import BoardProject, ResolvedPartition, StorageEntry
|
|
26
|
+
from .som_metadata import resolve_memory_map
|
|
27
|
+
|
|
28
|
+
# Sentinel distinguishing "caller has no hoisted aperture yet, resolve it"
|
|
29
|
+
# from a genuinely-resolved `aperture is None` ("this SoC declares none").
|
|
30
|
+
# `resolve_storage_partitions()` resolves the aperture ONCE and threads it
|
|
31
|
+
# through every helper below (the way `carveout.py` hoists it once per
|
|
32
|
+
# `resolve_carve_outs()` call) instead of each helper -- and each region a
|
|
33
|
+
# helper loops over -- re-parsing the SoC JSON and re-resolving the silicon
|
|
34
|
+
# variant on its own. Callers outside this module (`loader.py`'s eager
|
|
35
|
+
# cross-check, and the direct-call pytest suite) don't pass `aperture` at
|
|
36
|
+
# all, so they keep resolving it themselves via this sentinel's default --
|
|
37
|
+
# unchanged behaviour, just no longer redundant inside one resolve.
|
|
38
|
+
_APERTURE_UNSET: Any = object()
|
|
39
|
+
|
|
40
|
+
# `write_authority` value that means "writable by the application at
|
|
41
|
+
# runtime" -- the ONLY value a `memory_map` row may itself claim for a
|
|
42
|
+
# runtime mount (#2088). Same value, same rationale, as
|
|
43
|
+
# `check_atoc_reservation._RUNTIME_WRITABLE_AUTHORITY` (#2086); duplicated
|
|
44
|
+
# rather than imported since the two live in separate top-level scripts.
|
|
45
|
+
_RUNTIME_WRITABLE_AUTHORITY = "customer_runtime"
|
|
46
|
+
|
|
47
|
+
# `write_authority` value meaning "whole-device alias spanning contained
|
|
48
|
+
# rows of DIFFERENT authority -- consult the contained rows instead" (the
|
|
49
|
+
# schema's own words for `memory_region.write_authority`, `mram_main`'s tag
|
|
50
|
+
# on every AEN preset today). A composite row does NOT claim
|
|
51
|
+
# customer-runtime-writable for itself, but it is also not an unconditional
|
|
52
|
+
# pass: `_composite_alias_coverage_gap()` below is what actually "consults
|
|
53
|
+
# the contained rows" this value promises -- refusing the alias outright
|
|
54
|
+
# whenever a contained row can't be verified (unresolved base/size, absent
|
|
55
|
+
# write_authority, or a gap none of them cover). Treating `composite` the
|
|
56
|
+
# same as `customer_image`/`vendor_image`/`secure_enclave`/`none` --
|
|
57
|
+
# refusing it unconditionally -- would refuse `mram_main` on every AEN
|
|
58
|
+
# preset, the only whole-device alias any of them declares, even though it
|
|
59
|
+
# is the intended `flash_device:` target
|
|
60
|
+
# (docs/adr/0027-storage-regions-are-declared-by-role.md); this value
|
|
61
|
+
# exists so that shape can still be verified rather than rubber-stamped.
|
|
62
|
+
_DEFERRING_ALIAS_AUTHORITY = "composite"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _is_ospi_key_unassembled(som_preset: dict[str, Any], key: str) -> bool:
|
|
66
|
+
"""Does `som_preset.on_module.ospi_memories[key]` carry `assembled:
|
|
67
|
+
false` -- the schema's tag for an on-module OSPI part this SKU's PCB
|
|
68
|
+
doesn't actually populate (e.g. E1M-AEN801's `ospi0`/`ospi1`, #2311)?
|
|
69
|
+
|
|
70
|
+
Shared by every caller that must refuse an unfitted OSPI part rather
|
|
71
|
+
than treat its `capacity_mbit` (a property of the shared PCB footprint,
|
|
72
|
+
not evidence of assembly) as usable: `_known_flash_devices()` and
|
|
73
|
+
`_resolve_flash_device()` below, and `loader.py`'s
|
|
74
|
+
`storage[].flash_device` cross-field check and its
|
|
75
|
+
`security.psa.{its,ps}_storage` cross-check. Line-for-line port of
|
|
76
|
+
alp-sdk's `scripts/alp_orchestrate/partition.py::_is_ospi_key_unassembled`;
|
|
77
|
+
both sides move together (#2311).
|
|
78
|
+
"""
|
|
79
|
+
om = som_preset.get("on_module") or {}
|
|
80
|
+
ospi = om.get("ospi_memories") or {}
|
|
81
|
+
if not isinstance(ospi, dict) or key not in ospi:
|
|
82
|
+
return False
|
|
83
|
+
entry = ospi[key] or {}
|
|
84
|
+
return isinstance(entry, dict) and entry.get("assembled") is False
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _resolve_aperture_arg(
|
|
88
|
+
som_preset: dict[str, Any],
|
|
89
|
+
metadata_root: Path,
|
|
90
|
+
aperture: Any,
|
|
91
|
+
) -> Optional[tuple[int, int]]:
|
|
92
|
+
"""Return `aperture` verbatim when a caller already hoisted it;
|
|
93
|
+
resolve it fresh only when `aperture` is the `_APERTURE_UNSET`
|
|
94
|
+
sentinel (no caller has resolved it yet for this call)."""
|
|
95
|
+
if aperture is _APERTURE_UNSET:
|
|
96
|
+
return resolve_aperture(som_preset, metadata_root)
|
|
97
|
+
return aperture
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _is_flash_sub_partition(
|
|
101
|
+
region: dict[str, Any],
|
|
102
|
+
som_preset: dict[str, Any],
|
|
103
|
+
metadata_root: Path,
|
|
104
|
+
aperture: Any = _APERTURE_UNSET,
|
|
105
|
+
) -> bool:
|
|
106
|
+
"""alp-sdk#1365 split B (P2): is `region` a partition INSIDE a flash
|
|
107
|
+
device, not a device of its own?
|
|
108
|
+
|
|
109
|
+
Derives the verdict against the SoC's declared on-die MRAM aperture
|
|
110
|
+
(`aperture.is_partition_inside_aperture`) instead of trusting the
|
|
111
|
+
legacy `carveout:` flag outright. Falls back to the legacy flag
|
|
112
|
+
verbatim whenever the derivation can't resolve a definite verdict --
|
|
113
|
+
no aperture declared for this SoC (every non-Alif SoM), this region's
|
|
114
|
+
own `base` still unresolved (e.g. AEN's `mram_main`), OR this region's
|
|
115
|
+
resolved extent lying OUTSIDE the declared aperture (containment is
|
|
116
|
+
one-sided: outside proves nothing, so it does not by itself mean
|
|
117
|
+
"this is a device", alp-sdk#1365 split B review MAJOR 3): this is what
|
|
118
|
+
keeps split B a no-op everywhere it can't prove a better answer.
|
|
119
|
+
|
|
120
|
+
`aperture` defaults to the `_APERTURE_UNSET` sentinel, resolving it
|
|
121
|
+
fresh per call; pass an already-resolved aperture to avoid re-parsing
|
|
122
|
+
the SoC JSON when a caller is looping over many regions/devices in one
|
|
123
|
+
resolve.
|
|
124
|
+
"""
|
|
125
|
+
aperture = _resolve_aperture_arg(som_preset, metadata_root, aperture)
|
|
126
|
+
inside = is_partition_inside_aperture(region, aperture)
|
|
127
|
+
if inside is None:
|
|
128
|
+
return region.get("carveout") is False
|
|
129
|
+
return inside
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _known_flash_devices(
|
|
133
|
+
som_preset: dict[str, Any],
|
|
134
|
+
metadata_root: Path,
|
|
135
|
+
aperture: Any = _APERTURE_UNSET,
|
|
136
|
+
) -> list[str]:
|
|
137
|
+
"""Enumerate every flash-device name a `storage[].flash_device:` may
|
|
138
|
+
reference for the given SoM preset.
|
|
139
|
+
|
|
140
|
+
Today this is the union of:
|
|
141
|
+
- `memory_map:` region names (explicit override or derived from
|
|
142
|
+
the SoC variant), excluding any region `_is_flash_sub_partition()`
|
|
143
|
+
calls a partition INSIDE a flash-class node rather than a device
|
|
144
|
+
of its own (#1484) -- derived against the SoC's declared on-die
|
|
145
|
+
MRAM aperture, falling back to the legacy `carveout: false` flag
|
|
146
|
+
wherever containment can't prove a verdict (alp-sdk#1365 split B),
|
|
147
|
+
AND
|
|
148
|
+
- `on_module.ospi_memories:` keys (when declared on the SoM).
|
|
149
|
+
|
|
150
|
+
Kept as a list so the loader's "did you mean..." message can sort
|
|
151
|
+
it deterministically.
|
|
152
|
+
|
|
153
|
+
`aperture` defaults to `_APERTURE_UNSET`, resolving it once for this
|
|
154
|
+
call (rather than once per region, as a call embedded in the loop
|
|
155
|
+
used to); pass an already-resolved aperture to share it across many
|
|
156
|
+
calls in one resolve (see `_APERTURE_UNSET`'s module comment).
|
|
157
|
+
"""
|
|
158
|
+
aperture = _resolve_aperture_arg(som_preset, metadata_root, aperture)
|
|
159
|
+
names: set[str] = set()
|
|
160
|
+
for region in resolve_memory_map(som_preset, metadata_root):
|
|
161
|
+
n = region.get("name")
|
|
162
|
+
if not isinstance(n, str):
|
|
163
|
+
continue
|
|
164
|
+
if _is_flash_sub_partition(region, som_preset, metadata_root, aperture):
|
|
165
|
+
# A flash-class SUB-region -- an MRAM partition living inside a
|
|
166
|
+
# `mram_storage`-class flash node, not a flash device of its own
|
|
167
|
+
# (see the schema's `memory_region.carveout` description). On
|
|
168
|
+
# AEN this is `mcuboot` / `he_slot0` / `hp_slot0` / `reserved` /
|
|
169
|
+
# `storage` / `atoc`: none of them carries a Devicetree label,
|
|
170
|
+
# and decorating one with a `partitions { }` child targets a
|
|
171
|
+
# node the board tree never defines (#1484). Keep it out of the
|
|
172
|
+
# advertised set and out of `_resolve_flash_device()` below.
|
|
173
|
+
# alp-sdk#1365 split B: derived against the SoC's declared
|
|
174
|
+
# on-die MRAM aperture (containment), not the legacy
|
|
175
|
+
# `carveout:` flag -- see `_is_flash_sub_partition()`.
|
|
176
|
+
continue
|
|
177
|
+
names.add(n)
|
|
178
|
+
om = som_preset.get("on_module") or {}
|
|
179
|
+
ospi = om.get("ospi_memories") or {}
|
|
180
|
+
if isinstance(ospi, dict):
|
|
181
|
+
for k in ospi:
|
|
182
|
+
if not isinstance(k, str):
|
|
183
|
+
continue
|
|
184
|
+
if _is_ospi_key_unassembled(som_preset, k):
|
|
185
|
+
# Not fitted on this SKU (e.g. E1M-AEN801's ospi0/ospi1) --
|
|
186
|
+
# don't advertise a device `_resolve_flash_device()` below
|
|
187
|
+
# will refuse anyway (alp-sdk#2311).
|
|
188
|
+
continue
|
|
189
|
+
names.add(k)
|
|
190
|
+
return sorted(names)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _reserved_spans(
|
|
194
|
+
device_name: str,
|
|
195
|
+
capacity_bytes: int,
|
|
196
|
+
som_preset: dict[str, Any],
|
|
197
|
+
metadata_root: Path,
|
|
198
|
+
) -> "tuple[list[tuple[int, int, str]], Optional[str]]":
|
|
199
|
+
"""Return the SoM's own regions as DEVICE-RELATIVE [lo, hi) spans.
|
|
200
|
+
|
|
201
|
+
Returns `(spans, None)` on success, `([], reason)` when the spans can't
|
|
202
|
+
be computed safely -- the caller then falls back to sibling-only overlap
|
|
203
|
+
checking rather than trusting a guessed conversion (alp-sdk#1331).
|
|
204
|
+
|
|
205
|
+
Why this is needed at all: `storage[].offset_kib:` (and the bump
|
|
206
|
+
allocator) work in offsets WITHIN a flash device, while `memory_map:`
|
|
207
|
+
regions declare ABSOLUTE bases. Nothing previously bridged the two, so
|
|
208
|
+
every check was blind to the SoM's own layout -- on E1M-AEN801 that let a
|
|
209
|
+
littlefs mount resolve to offset 0 of `mram_main`, which is MCUboot, with
|
|
210
|
+
no `offset_kib:` involved and no `status: blocked`.
|
|
211
|
+
|
|
212
|
+
The device base is DERIVED, then VERIFIED, never assumed:
|
|
213
|
+
|
|
214
|
+
- If the device is itself a `memory_map:` region with an integer
|
|
215
|
+
`base`, that base is the origin -- authoritative, no further check
|
|
216
|
+
needed. On every AEN preset today this is the branch `mram_main`
|
|
217
|
+
takes: #2053 resolved its `base` to `0x80000000`, an ordinary
|
|
218
|
+
integer like any other region's.
|
|
219
|
+
- Otherwise (a device with no `base` of its own, e.g. a whole-window
|
|
220
|
+
alias whose `base:` is still an unresolved `"TBD"` placeholder --
|
|
221
|
+
no shipped AEN preset has this shape today; `mram_main` was the
|
|
222
|
+
standing example before #2053) take the lowest integer base among
|
|
223
|
+
the sibling regions and require `lowest + capacity == highest
|
|
224
|
+
region top`. That identity is what proves the alias really spans
|
|
225
|
+
the same window the fine-grained regions tile; if it does not
|
|
226
|
+
hold, refuse to convert. This is the ONLY leg that ever ran the
|
|
227
|
+
identity check -- it exists to substitute for a missing origin,
|
|
228
|
+
so it is unreachable (and its absence is safe, not a gap) once
|
|
229
|
+
the device carries a real `base` and takes the branch above
|
|
230
|
+
instead.
|
|
231
|
+
|
|
232
|
+
`_resolve_flash_device`'s descriptor deliberately carries no physical
|
|
233
|
+
base (Zephyr's flash-mapping layer derives it from the DT controller
|
|
234
|
+
node), which is why the origin is reconstructed here instead of being
|
|
235
|
+
read off the descriptor.
|
|
236
|
+
"""
|
|
237
|
+
regions = [r for r in resolve_memory_map(som_preset, metadata_root)
|
|
238
|
+
if isinstance(r, dict)]
|
|
239
|
+
sized = [(r["base"], r["base"] + (_region_size_bytes(r) or 0), str(r.get("name")))
|
|
240
|
+
for r in regions
|
|
241
|
+
if isinstance(r.get("base"), int) and _region_size_bytes(r)]
|
|
242
|
+
if not sized:
|
|
243
|
+
return [], (f"SoM declares no addressed memory_map region, so "
|
|
244
|
+
f"'{device_name}' offsets cannot be checked against it")
|
|
245
|
+
|
|
246
|
+
self_region = next((r for r in regions if r.get("name") == device_name), None)
|
|
247
|
+
if self_region is not None and isinstance(self_region.get("base"), int):
|
|
248
|
+
origin = self_region["base"]
|
|
249
|
+
else:
|
|
250
|
+
origin = min(lo for lo, _, _ in sized)
|
|
251
|
+
window_top = max(hi for _, hi, _ in sized)
|
|
252
|
+
if origin + capacity_bytes != window_top:
|
|
253
|
+
return [], (
|
|
254
|
+
f"flash device '{device_name}' has no declared base and its "
|
|
255
|
+
f"capacity ({capacity_bytes} B) does not span the addressed "
|
|
256
|
+
f"memory_map window (0x{origin:x}..0x{window_top:x}), so its "
|
|
257
|
+
f"offsets cannot be mapped onto the SoM's regions")
|
|
258
|
+
|
|
259
|
+
spans: "list[tuple[int, int, str]]" = []
|
|
260
|
+
for lo, hi, name in sized:
|
|
261
|
+
# The device itself is not an obstacle inside itself; nor is a region
|
|
262
|
+
# that spans the whole window (a coarse alias) -- otherwise every
|
|
263
|
+
# partition would "overlap" it and nothing could ever be placed.
|
|
264
|
+
if name == device_name or (lo <= origin and hi - lo >= capacity_bytes):
|
|
265
|
+
continue
|
|
266
|
+
rel_lo, rel_hi = lo - origin, hi - origin
|
|
267
|
+
# Clip to the device; a region outside it is simply not our problem.
|
|
268
|
+
rel_lo, rel_hi = max(rel_lo, 0), min(rel_hi, capacity_bytes)
|
|
269
|
+
if rel_hi > rel_lo:
|
|
270
|
+
spans.append((rel_lo, rel_hi, name))
|
|
271
|
+
return sorted(spans), None
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _first_free(
|
|
275
|
+
base_bytes: int,
|
|
276
|
+
size_aligned: int,
|
|
277
|
+
allocated: "list[tuple[int, int, str]]",
|
|
278
|
+
capacity_bytes: int,
|
|
279
|
+
) -> int:
|
|
280
|
+
"""Advance `base_bytes` past any span it collides with, page-aligned.
|
|
281
|
+
|
|
282
|
+
The bump allocator must allocate AROUND the SoM's reserved regions, not
|
|
283
|
+
merely notice a collision: refusing instead of skipping would break the
|
|
284
|
+
ordinary case (a project that names no addresses at all) rather than fix
|
|
285
|
+
it. Returns the first non-colliding page-aligned base, or a value past
|
|
286
|
+
`capacity_bytes` when the device has no room left -- the caller reports
|
|
287
|
+
that as a blocked entry.
|
|
288
|
+
"""
|
|
289
|
+
moved = True
|
|
290
|
+
while moved:
|
|
291
|
+
moved = False
|
|
292
|
+
for lo, hi, _ in sorted(allocated):
|
|
293
|
+
if base_bytes < hi and lo < base_bytes + size_aligned:
|
|
294
|
+
base_bytes = ((hi + _PAGE - 1) // _PAGE) * _PAGE
|
|
295
|
+
moved = True
|
|
296
|
+
if base_bytes >= capacity_bytes:
|
|
297
|
+
return base_bytes
|
|
298
|
+
return base_bytes
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _composite_alias_coverage_gap(
|
|
302
|
+
alias_name: str,
|
|
303
|
+
alias_base: Any,
|
|
304
|
+
capacity_bytes: int,
|
|
305
|
+
som_preset: dict[str, Any],
|
|
306
|
+
metadata_root: Path,
|
|
307
|
+
aperture: Optional[tuple[int, int]],
|
|
308
|
+
) -> Optional[str]:
|
|
309
|
+
"""#2088: what "`write_authority: composite` means consult the
|
|
310
|
+
contained rows" actually has to DO, not just claim.
|
|
311
|
+
|
|
312
|
+
Returns `None` when the `memory_map:` rows CONTAINED in
|
|
313
|
+
`alias_name`'s own `[origin, origin+capacity_bytes)` window --
|
|
314
|
+
resolved, `write_authority`-bearing, non-overlapping -- CONTIGUOUSLY
|
|
315
|
+
tile that window with no gap (review round 2, Major 1: an earlier
|
|
316
|
+
version of this function summed sizes rather than walking spans, so
|
|
317
|
+
an overlap of N bytes plus a hole of N bytes passed the sum check
|
|
318
|
+
while the hole itself stayed unprotected -- reproduced by deleting
|
|
319
|
+
`atoc` and widening `he_slot0` by exactly `atoc`'s size). Returns a
|
|
320
|
+
refusal reason otherwise, naming the gap or the overlapping pair.
|
|
321
|
+
|
|
322
|
+
A row whose resolved extent lies OUTSIDE the window (a genuinely
|
|
323
|
+
unrelated device that merely happens to share this SoM's
|
|
324
|
+
`memory_map:` list) is ignored -- containment, not mere co-listing,
|
|
325
|
+
is what makes a row this alias's business.
|
|
326
|
+
|
|
327
|
+
A row whose base or size DOESN'T resolve is not immediately fatal on
|
|
328
|
+
its own (review round 2, Major 3: an earlier version refused for
|
|
329
|
+
ANY such row unconditionally, which also refused a genuinely
|
|
330
|
+
unrelated device parked at a TBD-but-clearly-different address
|
|
331
|
+
purely because its address wasn't resolved yet -- indistinguishable
|
|
332
|
+
from `atoc`'s real pre-HW-mapped shape without more information).
|
|
333
|
+
Instead: walk only the rows that DO resolve; if they alone already
|
|
334
|
+
tile the window with no gap, an unresolved-base sibling can only be
|
|
335
|
+
something else entirely (resolving it into an overlap would be a
|
|
336
|
+
SEPARATE metadata bug for a different gate to catch, not evidence
|
|
337
|
+
this alias is unsafe today) -- but if a gap remains, an unresolved
|
|
338
|
+
sibling is named as a candidate that might explain it (never ruled
|
|
339
|
+
out, per ADR-0034 clause 4), while an alias with NO unresolved
|
|
340
|
+
sibling AND a gap has nothing left to blame but a genuinely missing
|
|
341
|
+
row.
|
|
342
|
+
|
|
343
|
+
`origin` is `alias_base` when the alias's own base has resolved;
|
|
344
|
+
else `aperture[0]` when an aperture resolves for this SoM (`mram_main`
|
|
345
|
+
itself carries `base: "TBD"` on every AEN preset today, so this is
|
|
346
|
+
the common case, not the exception -- using the SoC's own declared
|
|
347
|
+
aperture start here, rather than the lowest resolved sibling base,
|
|
348
|
+
avoids anchoring the window at a fabricated address when a resolved
|
|
349
|
+
but genuinely-unrelated sibling happens to sit below every real
|
|
350
|
+
contained row, review round 2 Minor); else the lowest resolved base
|
|
351
|
+
among sibling rows, for a non-Alif SoM with no aperture to anchor to.
|
|
352
|
+
|
|
353
|
+
Without this whole function, a sibling with an unresolved `base` or
|
|
354
|
+
a sibling missing from `memory_map:` altogether contributes NO span
|
|
355
|
+
to `_reserved_spans()`, which reserves only what it can see -- so
|
|
356
|
+
the bump allocator treats the unaccounted band as free. On
|
|
357
|
+
`mram_main`, that band can be the Secure-Enclave-owned `atoc` window
|
|
358
|
+
(bench-observed live at 0x80578000..0x80580000, `carveout.py:100-104`).
|
|
359
|
+
`aperture` gates the same absent-write_authority leniency
|
|
360
|
+
`_resolve_flash_device()`'s own guard applies just above this call --
|
|
361
|
+
a non-Alif sibling with no `write_authority` at all is not itself
|
|
362
|
+
the hazard.
|
|
363
|
+
"""
|
|
364
|
+
siblings = [r for r in resolve_memory_map(som_preset, metadata_root)
|
|
365
|
+
if isinstance(r, dict) and r.get("name") != alias_name]
|
|
366
|
+
|
|
367
|
+
def _resolved_int(value: Any) -> bool:
|
|
368
|
+
return isinstance(value, int) and not isinstance(value, bool)
|
|
369
|
+
|
|
370
|
+
if _resolved_int(alias_base):
|
|
371
|
+
origin = alias_base
|
|
372
|
+
elif aperture is not None:
|
|
373
|
+
origin = aperture[0]
|
|
374
|
+
else:
|
|
375
|
+
resolved_bases = [r["base"] for r in siblings if _resolved_int(r.get("base"))]
|
|
376
|
+
if not resolved_bases:
|
|
377
|
+
return (
|
|
378
|
+
f"flash device '{alias_name}' declares "
|
|
379
|
+
f"write_authority: {_DEFERRING_ALIAS_AUTHORITY!r} on SoM "
|
|
380
|
+
f"{som_preset.get('sku', '<unknown>')}, which defers "
|
|
381
|
+
f"eligibility to its contained memory_map rows -- but "
|
|
382
|
+
f"neither '{alias_name}' itself nor any sibling row has a "
|
|
383
|
+
f"resolved base, and no SoC aperture resolves either, so "
|
|
384
|
+
f"its window cannot be anchored to verify anything inside "
|
|
385
|
+
f"it. Resolve at least one memory_map row's base before "
|
|
386
|
+
f"targeting '{alias_name}' with a storage[].flash_device: "
|
|
387
|
+
f"(#2088)")
|
|
388
|
+
origin = min(resolved_bases)
|
|
389
|
+
top = origin + capacity_bytes
|
|
390
|
+
|
|
391
|
+
contained: "list[tuple[int, int, str]]" = []
|
|
392
|
+
ambiguous_names: "list[str]" = []
|
|
393
|
+
for region in siblings:
|
|
394
|
+
name = region.get("name")
|
|
395
|
+
base = region.get("base")
|
|
396
|
+
if not _resolved_int(base):
|
|
397
|
+
ambiguous_names.append(str(name))
|
|
398
|
+
continue
|
|
399
|
+
size_bytes = _region_size_bytes(region)
|
|
400
|
+
if size_bytes is None:
|
|
401
|
+
ambiguous_names.append(str(name))
|
|
402
|
+
continue
|
|
403
|
+
hi = base + size_bytes
|
|
404
|
+
if base >= top or hi <= origin:
|
|
405
|
+
continue # Disjoint -- a different device, not this alias's business.
|
|
406
|
+
if not (base >= origin and hi <= top):
|
|
407
|
+
# Partial overlap with the WINDOW boundary -- neither cleanly
|
|
408
|
+
# inside nor cleanly outside. Rare/malformed; refuse rather
|
|
409
|
+
# than guess which side of the boundary the bytes belong to.
|
|
410
|
+
return (
|
|
411
|
+
f"flash device '{alias_name}' declares "
|
|
412
|
+
f"write_authority: {_DEFERRING_ALIAS_AUTHORITY!r} on SoM "
|
|
413
|
+
f"{som_preset.get('sku', '<unknown>')}, which defers "
|
|
414
|
+
f"eligibility to its contained memory_map rows -- but "
|
|
415
|
+
f"region {name!r} [0x{base:x}, 0x{hi:x}) only partially "
|
|
416
|
+
f"overlaps '{alias_name}'s 0x{origin:x}..0x{top:x} "
|
|
417
|
+
f"window, so it can't be cleanly attributed as contained "
|
|
418
|
+
f"or disjoint. Fix the addresses before targeting "
|
|
419
|
+
f"'{alias_name}' with a storage[].flash_device: (#2088)")
|
|
420
|
+
wa = region.get("write_authority")
|
|
421
|
+
if wa is None and aperture is not None:
|
|
422
|
+
return (
|
|
423
|
+
f"flash device '{alias_name}' declares "
|
|
424
|
+
f"write_authority: {_DEFERRING_ALIAS_AUTHORITY!r} on SoM "
|
|
425
|
+
f"{som_preset.get('sku', '<unknown>')}, which defers "
|
|
426
|
+
f"eligibility to its contained memory_map rows -- but "
|
|
427
|
+
f"region {name!r}, contained in '{alias_name}'s window, "
|
|
428
|
+
f"carries no write_authority, so whether it may host a "
|
|
429
|
+
f"runtime mount cannot be verified. Author "
|
|
430
|
+
f"write_authority: on {name!r} before targeting "
|
|
431
|
+
f"'{alias_name}' with a storage[].flash_device: (#2088)")
|
|
432
|
+
contained.append((base, hi, str(name)))
|
|
433
|
+
|
|
434
|
+
# Walk the CONTAINED, resolved spans in address order and require
|
|
435
|
+
# exact contiguity -- an overlap of N bytes plus a hole of N bytes
|
|
436
|
+
# must not cancel out in a sum (review round 2, Major 1).
|
|
437
|
+
contained.sort()
|
|
438
|
+
cursor = origin
|
|
439
|
+
prev_name = None
|
|
440
|
+
for lo, hi, name in contained:
|
|
441
|
+
if lo < cursor:
|
|
442
|
+
return (
|
|
443
|
+
f"flash device '{alias_name}' declares "
|
|
444
|
+
f"write_authority: {_DEFERRING_ALIAS_AUTHORITY!r} on SoM "
|
|
445
|
+
f"{som_preset.get('sku', '<unknown>')}, which defers "
|
|
446
|
+
f"eligibility to its contained memory_map rows -- but "
|
|
447
|
+
f"region {name!r} [0x{lo:x}, 0x{hi:x}) overlaps "
|
|
448
|
+
f"{prev_name!r}, which ends at 0x{cursor:x}, by "
|
|
449
|
+
f"{(cursor - lo) // 1024} KiB. Two rows claiming the same "
|
|
450
|
+
f"bytes means at most one of their `write_authority` "
|
|
451
|
+
f"values actually applies there; fix the addresses before "
|
|
452
|
+
f"targeting '{alias_name}' with a storage[].flash_device: "
|
|
453
|
+
f"(#2088)")
|
|
454
|
+
if lo > cursor:
|
|
455
|
+
break # Gap -- reported below, naming any candidate that might fill it.
|
|
456
|
+
cursor = hi
|
|
457
|
+
prev_name = name
|
|
458
|
+
|
|
459
|
+
if cursor < top:
|
|
460
|
+
gap_lo, gap_hi = cursor, top
|
|
461
|
+
candidate_note = (
|
|
462
|
+
f" -- {', '.join(sorted(ambiguous_names))} carries an "
|
|
463
|
+
f"unresolved base or size and cannot be ruled out as the "
|
|
464
|
+
f"row that belongs there"
|
|
465
|
+
if ambiguous_names else
|
|
466
|
+
f" and no memory_map row with an unresolved base or size "
|
|
467
|
+
f"exists either, so this is a genuinely undeclared row")
|
|
468
|
+
return (
|
|
469
|
+
f"flash device '{alias_name}' declares "
|
|
470
|
+
f"write_authority: {_DEFERRING_ALIAS_AUTHORITY!r} on SoM "
|
|
471
|
+
f"{som_preset.get('sku', '<unknown>')} "
|
|
472
|
+
f"({capacity_bytes // 1024} KiB, window 0x{origin:x}.."
|
|
473
|
+
f"0x{top:x}), but its contained memory_map rows leave a gap "
|
|
474
|
+
f"at 0x{gap_lo:x}..0x{gap_hi:x} ({(gap_hi - gap_lo) // 1024} "
|
|
475
|
+
f"KiB) unaccounted for{candidate_note}. Declare the missing "
|
|
476
|
+
f"row(s) before targeting '{alias_name}' with a "
|
|
477
|
+
f"storage[].flash_device: (#2088)")
|
|
478
|
+
return None
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def _resolve_flash_device(
|
|
482
|
+
flash_device: str,
|
|
483
|
+
som_preset: dict[str, Any],
|
|
484
|
+
metadata_root: Path,
|
|
485
|
+
aperture: Any = _APERTURE_UNSET,
|
|
486
|
+
) -> tuple[Optional[dict[str, Any]], Optional[str]]:
|
|
487
|
+
"""Resolve a board.yaml `flash_device:` reference to a device descriptor.
|
|
488
|
+
|
|
489
|
+
Returns `(descriptor, None)` on success; `(None, reason)` when the
|
|
490
|
+
device is known but cannot be allocated against (TBD capacity).
|
|
491
|
+
The descriptor carries:
|
|
492
|
+
name -- the SDK device name (verbatim from board.yaml)
|
|
493
|
+
dt_label -- the Zephyr DT label to decorate
|
|
494
|
+
size_bytes -- capacity in bytes (None when unresolvable)
|
|
495
|
+
|
|
496
|
+
Address allocation is offset-within-device; the descriptor does
|
|
497
|
+
NOT carry a physical base because Zephyr's flash-mapping layer
|
|
498
|
+
derives that from the DT controller node. The emitted overlay
|
|
499
|
+
references `&<dt_label>` and lets Zephyr handle the physical mapping.
|
|
500
|
+
|
|
501
|
+
`aperture` defaults to `_APERTURE_UNSET` (resolve fresh); pass an
|
|
502
|
+
already-resolved aperture to share it across many calls in one
|
|
503
|
+
resolve (see `_APERTURE_UNSET`'s module comment).
|
|
504
|
+
"""
|
|
505
|
+
aperture = _resolve_aperture_arg(som_preset, metadata_root, aperture)
|
|
506
|
+
# memory_map: region match first (covers the auto-derived MRAM /
|
|
507
|
+
# SRAM region table from resolve_memory_map()).
|
|
508
|
+
for region in resolve_memory_map(som_preset, metadata_root):
|
|
509
|
+
if region.get("name") != flash_device:
|
|
510
|
+
continue
|
|
511
|
+
if _is_flash_sub_partition(region, som_preset, metadata_root, aperture):
|
|
512
|
+
# Defense in depth for a hand-built project that skips the
|
|
513
|
+
# loader's `_known_flash_devices()` check: refuse rather than
|
|
514
|
+
# fabricate a `dt_label` from the region name. This region is
|
|
515
|
+
# a partition INSIDE a flash-class node (e.g. AEN's
|
|
516
|
+
# `mram_storage`), not a flash device -- it has no Devicetree
|
|
517
|
+
# label of its own and a `partitions { }` child on it targets
|
|
518
|
+
# a node the board tree never defines (#1484). alp-sdk#1365
|
|
519
|
+
# split B: derived against the declared MRAM aperture, not the
|
|
520
|
+
# legacy `carveout:` flag -- see `_is_flash_sub_partition()`.
|
|
521
|
+
return None, (
|
|
522
|
+
f"flash device '{flash_device}' is a partition inside a "
|
|
523
|
+
f"flash-class region on SoM "
|
|
524
|
+
f"{som_preset.get('sku', '<unknown>')}, not a flash device "
|
|
525
|
+
f"of its own -- it has no Devicetree label and cannot take "
|
|
526
|
+
f"a `partitions {{ }}` child")
|
|
527
|
+
# #2088: a resolved memory_map region is not automatically
|
|
528
|
+
# customer-writable at runtime -- that includes a whole-device
|
|
529
|
+
# alias (e.g. `mram_main`), which `_is_flash_sub_partition()`
|
|
530
|
+
# above correctly does NOT treat as a sub-partition, so it would
|
|
531
|
+
# otherwise fall straight through to a resolved descriptor. Per
|
|
532
|
+
# the schema (`memory_region.write_authority`), a row may take a
|
|
533
|
+
# runtime mount only when it claims `customer_runtime` directly,
|
|
534
|
+
# OR defers as a `composite` alias (see `_DEFERRING_ALIAS_AUTHORITY`
|
|
535
|
+
# above) AND every contained row it defers to is independently
|
|
536
|
+
# verified safe below -- every other AUTHORED value
|
|
537
|
+
# (customer_image/vendor_image/secure_enclave/none) means
|
|
538
|
+
# something else owns writes to this region and refuses outright.
|
|
539
|
+
wa = region.get("write_authority")
|
|
540
|
+
if wa is not None and wa not in (
|
|
541
|
+
_RUNTIME_WRITABLE_AUTHORITY, _DEFERRING_ALIAS_AUTHORITY):
|
|
542
|
+
return None, (
|
|
543
|
+
f"flash device '{flash_device}' declares "
|
|
544
|
+
f"write_authority: {wa!r} on SoM "
|
|
545
|
+
f"{som_preset.get('sku', '<unknown>')}, which is not "
|
|
546
|
+
f"customer-writable at runtime -- only a region declaring "
|
|
547
|
+
f"write_authority: {_RUNTIME_WRITABLE_AUTHORITY!r} (or "
|
|
548
|
+
f"{_DEFERRING_ALIAS_AUTHORITY!r} for a whole-device alias "
|
|
549
|
+
f"that defers to contained rows) may take a runtime mount "
|
|
550
|
+
f"(storage[].flash_device:). Either give '{flash_device}' "
|
|
551
|
+
f"write_authority: {_RUNTIME_WRITABLE_AUTHORITY!r} if it "
|
|
552
|
+
f"truly is the customer's runtime storage, or point "
|
|
553
|
+
f"flash_device: at the region that is (#2088)")
|
|
554
|
+
# An ABSENT value is different: the schema and ADR-0034 clause 4
|
|
555
|
+
# both say absent means UNRESOLVED, never `customer_runtime` -- "a
|
|
556
|
+
# consumer must treat an authored region carrying no
|
|
557
|
+
# write_authority as ineligible for ... runtime write ... rather
|
|
558
|
+
# than defaulting permissively" (`docs/porting-new-som.md`
|
|
559
|
+
# repeats this verbatim). `carveout.py`'s equivalent guard
|
|
560
|
+
# (`_region_ipc_eligibility`'s "unclassified"/"unresolved" legs)
|
|
561
|
+
# enforces this the same way -- refuse -- but ONLY once an
|
|
562
|
+
# on-die MRAM aperture actually resolves for this SoM (every
|
|
563
|
+
# non-Alif SoM, and any Alif SoC/variant that hasn't declared
|
|
564
|
+
# one, short-circuits before this rule even runs -- see
|
|
565
|
+
# `_candidate_regions()`). Mirrored here: refusing
|
|
566
|
+
# unconditionally would fire on every V2N/V2M preset's
|
|
567
|
+
# `memory_map:` rows, none of which author `write_authority` at
|
|
568
|
+
# all and none of which a customer can fix by authoring a field
|
|
569
|
+
# this guard doesn't even apply to.
|
|
570
|
+
if wa is None and aperture is not None:
|
|
571
|
+
return None, (
|
|
572
|
+
f"flash device '{flash_device}' on SoM "
|
|
573
|
+
f"{som_preset.get('sku', '<unknown>')} carries no "
|
|
574
|
+
f"write_authority -- ADR-0034 clause 4 and the schema "
|
|
575
|
+
f"both say an absent value means unresolved, not "
|
|
576
|
+
f"'customer_runtime': treat it as ineligible for a "
|
|
577
|
+
f"runtime write rather than defaulting permissively. "
|
|
578
|
+
f"Author write_authority: {_RUNTIME_WRITABLE_AUTHORITY!r} "
|
|
579
|
+
f"on '{flash_device}' if it is meant to take a runtime "
|
|
580
|
+
f"mount, or point flash_device: at a region that already "
|
|
581
|
+
f"declares it (#2088)")
|
|
582
|
+
size_bytes = _region_size_bytes(region)
|
|
583
|
+
if size_bytes is None:
|
|
584
|
+
return None, (
|
|
585
|
+
f"flash device '{flash_device}' resolves to memory_map "
|
|
586
|
+
f"region but size_mib / size_kib is unset or TBD on "
|
|
587
|
+
f"SoM {som_preset.get('sku', '<unknown>')}; the SoM "
|
|
588
|
+
f"hasn't been HW-mapped yet")
|
|
589
|
+
if wa == _DEFERRING_ALIAS_AUTHORITY:
|
|
590
|
+
# `composite` means "consult the contained rows instead" --
|
|
591
|
+
# so actually consult them, rather than trusting the alias's
|
|
592
|
+
# own tag as a pass. Every OTHER row in this SoM's memory_map
|
|
593
|
+
# must resolve to a concrete [base, base+size) span AND
|
|
594
|
+
# declare its own write_authority, and together they must
|
|
595
|
+
# fully tile this alias's capacity, before the alias can be
|
|
596
|
+
# called safe: a row this can't account for (unresolved base
|
|
597
|
+
# or size, absent authority, or a gap none of them cover) is
|
|
598
|
+
# exactly the #2088 hazard (a storage[] entry silently
|
|
599
|
+
# landing in the unaccounted-for band, e.g. the SE-owned
|
|
600
|
+
# `atoc` window when its base is still TBD) -- refuse the
|
|
601
|
+
# whole alias rather than resolve permissively around a hole
|
|
602
|
+
# this function can't see the shape of.
|
|
603
|
+
gap_reason = _composite_alias_coverage_gap(
|
|
604
|
+
flash_device, region.get("base"), size_bytes, som_preset,
|
|
605
|
+
metadata_root, aperture)
|
|
606
|
+
if gap_reason is not None:
|
|
607
|
+
return None, gap_reason
|
|
608
|
+
# #2088 review round 2, Major 2: the coverage-gap walk above
|
|
609
|
+
# proves the CONTAINED rows are individually sound, but
|
|
610
|
+
# placement itself runs through `_reserved_spans()` -- a
|
|
611
|
+
# SEPARATE, older function with its own (weaker) origin/
|
|
612
|
+
# window derivation. An out-of-window sibling (correctly
|
|
613
|
+
# ignored as disjoint above) can still push
|
|
614
|
+
# `_reserved_spans()`'s own `window_top = max(hi for sized)`
|
|
615
|
+
# past this alias's real top, failing its
|
|
616
|
+
# `origin + capacity == window_top` identity check and
|
|
617
|
+
# degrading it to `([], reason)` -- silently reserving
|
|
618
|
+
# NOTHING, including `mcuboot`, for any caller that placed a
|
|
619
|
+
# storage[] entry here. An alias whose contained rows can't
|
|
620
|
+
# actually be RESERVED is exactly as unconsultable as one
|
|
621
|
+
# whose rows can't be VERIFIED (the same principle already
|
|
622
|
+
# applied to an unresolved base above) -- refuse rather than
|
|
623
|
+
# let a caller believe the window this function just proved
|
|
624
|
+
# safe stays protected at placement time.
|
|
625
|
+
_reserved_probe, reserved_degraded_reason = _reserved_spans(
|
|
626
|
+
flash_device, size_bytes, som_preset, metadata_root)
|
|
627
|
+
if reserved_degraded_reason is not None:
|
|
628
|
+
return None, (
|
|
629
|
+
f"flash device '{flash_device}' declares "
|
|
630
|
+
f"write_authority: {_DEFERRING_ALIAS_AUTHORITY!r} on "
|
|
631
|
+
f"SoM {som_preset.get('sku', '<unknown>')} -- its "
|
|
632
|
+
f"contained memory_map rows verify safe, but "
|
|
633
|
+
f"{reserved_degraded_reason}, so placement cannot "
|
|
634
|
+
f"actually reserve them: an alias whose contained "
|
|
635
|
+
f"rows can't be reserved can't be consulted either "
|
|
636
|
+
f"(#2088)")
|
|
637
|
+
dt_label = (region.get("dt_label")
|
|
638
|
+
if isinstance(region.get("dt_label"), str)
|
|
639
|
+
else flash_device)
|
|
640
|
+
return {
|
|
641
|
+
"name": flash_device,
|
|
642
|
+
"dt_label": dt_label,
|
|
643
|
+
"size_bytes": size_bytes,
|
|
644
|
+
}, None
|
|
645
|
+
|
|
646
|
+
# on_module.ospi_memories: key match.
|
|
647
|
+
#
|
|
648
|
+
# alp-sdk#2311: refuse a device this SKU's preset declares
|
|
649
|
+
# `assembled: false` (e.g. `E1M-AEN801`'s `ospi0`/`ospi1`) BEFORE
|
|
650
|
+
# looking at `capacity_mbit` -- `capacity_mbit` is the DESIGNED-IN
|
|
651
|
+
# part's capacity (present on the shared PCB footprint), not evidence
|
|
652
|
+
# the module actually carries the part. `_known_flash_devices()` above
|
|
653
|
+
# already keeps an unassembled device out of the advertised set; this
|
|
654
|
+
# is the same guard applied where a hand-built project (or a stale
|
|
655
|
+
# caller) skips that check and calls this resolver directly (defense
|
|
656
|
+
# in depth). `optional` and an absent key (schema default: true) both
|
|
657
|
+
# still resolve -- `assembled` must be the literal `False` the schema
|
|
658
|
+
# uses for "not fitted".
|
|
659
|
+
#
|
|
660
|
+
# Ported line-for-line from alp-sdk's
|
|
661
|
+
# scripts/alp_orchestrate/partition.py::_resolve_flash_device; both
|
|
662
|
+
# sides move together (alp-sdk#2311).
|
|
663
|
+
om = som_preset.get("on_module") or {}
|
|
664
|
+
ospi = om.get("ospi_memories") or {}
|
|
665
|
+
if isinstance(ospi, dict) and flash_device in ospi:
|
|
666
|
+
entry = ospi[flash_device] or {}
|
|
667
|
+
if _is_ospi_key_unassembled(som_preset, flash_device):
|
|
668
|
+
sku = som_preset.get("sku")
|
|
669
|
+
where = f"SoM {sku}" if sku else "this SoM"
|
|
670
|
+
yaml_ref = (f"metadata/e1m_modules/{sku}.yaml" if sku
|
|
671
|
+
else "its SoM preset YAML")
|
|
672
|
+
return None, (
|
|
673
|
+
f"flash device '{flash_device}' is not assembled on "
|
|
674
|
+
f"{where} (assembled: false in {yaml_ref}); it cannot "
|
|
675
|
+
f"take a runtime storage mount")
|
|
676
|
+
cap = entry.get("capacity_mbit")
|
|
677
|
+
if isinstance(cap, str) and cap.strip().upper() == "TBD":
|
|
678
|
+
return None, (
|
|
679
|
+
f"on_module.ospi_memories.{flash_device}.capacity_mbit "
|
|
680
|
+
f"is TBD on SoM {som_preset.get('sku', '<unknown>')}; "
|
|
681
|
+
f"fill the value or move the storage entry to a sized "
|
|
682
|
+
f"flash device")
|
|
683
|
+
if not isinstance(cap, int):
|
|
684
|
+
return None, (
|
|
685
|
+
f"on_module.ospi_memories.{flash_device}.capacity_mbit "
|
|
686
|
+
f"is missing on SoM {som_preset.get('sku', '<unknown>')}; "
|
|
687
|
+
f"the storage allocator needs an authoritative capacity")
|
|
688
|
+
dt_label = (entry.get("dt_label")
|
|
689
|
+
if isinstance(entry.get("dt_label"), str)
|
|
690
|
+
else flash_device)
|
|
691
|
+
# capacity_mbit is megabits; convert to bytes.
|
|
692
|
+
size_bytes = int(cap) * 1024 * 1024 // 8
|
|
693
|
+
return {
|
|
694
|
+
"name": flash_device,
|
|
695
|
+
"dt_label": dt_label,
|
|
696
|
+
"size_bytes": size_bytes,
|
|
697
|
+
}, None
|
|
698
|
+
|
|
699
|
+
# Unknown device — the loader catches this earlier, but defend
|
|
700
|
+
# in depth in case a resolver is called with a hand-built project.
|
|
701
|
+
return None, (
|
|
702
|
+
f"flash device '{flash_device}' is neither a memory_map region "
|
|
703
|
+
f"nor an on_module.ospi_memories key on SoM "
|
|
704
|
+
f"{som_preset.get('sku', '<unknown>')}")
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
def _has_real_dt_label(
|
|
708
|
+
flash_device: str,
|
|
709
|
+
som_preset: dict[str, Any],
|
|
710
|
+
metadata_root: Path,
|
|
711
|
+
) -> bool:
|
|
712
|
+
"""True when `flash_device`'s Devicetree label is KNOWN, not fabricated.
|
|
713
|
+
|
|
714
|
+
`_resolve_flash_device()` succeeding is NOT enough to recommend a device
|
|
715
|
+
to a customer: its `dt_label` DEFAULTS to the device name whenever the
|
|
716
|
+
metadata omits an explicit override, and that default is unverified
|
|
717
|
+
against the board `.dts` (see the schema's `memory_region.dt_label`
|
|
718
|
+
description -- AEN's `mram_main` region, for example, names its flash
|
|
719
|
+
node `mram_storage`, not `mram_main`). Emitting `&<name>` for one of
|
|
720
|
+
those is the exact defect #1484 closes.
|
|
721
|
+
|
|
722
|
+
An `on_module.ospi_memories:` key does NOT qualify, despite its name
|
|
723
|
+
matching a controller node's label 1:1 on paper. Measured against the
|
|
724
|
+
generated Zephyr board trees (#1484 re-review): `ospi0` is the ONLY
|
|
725
|
+
`ospi<n>` label that exists anywhere under `zephyr/`
|
|
726
|
+
(`zephyr/dts/alif/ensemble_e8_peripherals.dtsi:688`), it is included by
|
|
727
|
+
only the two E1M-AEN801 board `.dts` files, and there it is the OSPI
|
|
728
|
+
CONTROLLER node (`compatible = "snps,designware-ospi"`,
|
|
729
|
+
`status = "disabled"`) -- neither AEN801 board tree enables it or gives
|
|
730
|
+
it a flash-chip child, so a `fixed-partitions` overlay under `&ospi0`
|
|
731
|
+
would not be a working flash area even on the one SKU where the label
|
|
732
|
+
exists. E1M-AEN401/601 have a board tree with no `ospi0` node at all;
|
|
733
|
+
E1M-AEN301/501/701 have no board tree in `zephyr/boards/alp/` at all.
|
|
734
|
+
`scripts/gen_zephyr_board.py` never generates an `ospi<n>` node either.
|
|
735
|
+
So until a real per-instance DT label is wired up and verified against
|
|
736
|
+
an ENABLED node, no `ospi_memories:` key is trustworthy here.
|
|
737
|
+
|
|
738
|
+
Only trust a `memory_map:` region that carries an EXPLICIT `dt_label:`
|
|
739
|
+
override -- nothing does yet, so this currently returns `False` for
|
|
740
|
+
every device on every SoM. That is the correct interim answer: it means
|
|
741
|
+
`alt_devices` never recommends a target this resolver cannot verify,
|
|
742
|
+
rather than recommending one that merely LOOKS verified.
|
|
743
|
+
|
|
744
|
+
alp-sdk#1556: `resolve_storage_partitions()` now gates the PRIMARY
|
|
745
|
+
resolution on this same predicate for a `memory_map:`-sourced device
|
|
746
|
+
(not just the alt-device remedy) -- naming `flash_device: mram_main`
|
|
747
|
+
(or any other unverified `memory_map:` region) can no longer reach
|
|
748
|
+
`status: ok`; it blocks with a reason instead of fabricating a DT
|
|
749
|
+
label the board tree never defines. `on_module.ospi_memories:`
|
|
750
|
+
devices are NOT gated by that change (`is_ospi_device` in the
|
|
751
|
+
resolver still short-circuits this predicate to `True` for them) --
|
|
752
|
+
this function already returns `False` for every `ospi_memories:` key
|
|
753
|
+
(the loop below only ever matches a `memory_map:` region name), so
|
|
754
|
+
gating the primary path on it unconditionally would also block every
|
|
755
|
+
`ospi0`-targeting entry, including the ones the existing storage test
|
|
756
|
+
suite (`tests/scripts/test_orchestrate_storage*.py`) deliberately uses
|
|
757
|
+
as its "device-independent allocator mechanics" fixture. Verifying
|
|
758
|
+
`ospi_memories:` against its own real, per-instance DT label is a
|
|
759
|
+
separate gap this issue's repro does not cover (see this function's
|
|
760
|
+
own paragraph above) -- left as tracked follow-up, not silently
|
|
761
|
+
widened here.
|
|
762
|
+
"""
|
|
763
|
+
for region in resolve_memory_map(som_preset, metadata_root):
|
|
764
|
+
if region.get("name") == flash_device:
|
|
765
|
+
return isinstance(region.get("dt_label"), str)
|
|
766
|
+
return False
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
def _verified_alt_devices(
|
|
770
|
+
exclude: str,
|
|
771
|
+
som_preset: dict[str, Any],
|
|
772
|
+
metadata_root: Path,
|
|
773
|
+
aperture: Any = _APERTURE_UNSET,
|
|
774
|
+
) -> list[str]:
|
|
775
|
+
"""Every known flash device other than `exclude` that both resolves
|
|
776
|
+
(`_resolve_flash_device()`) and carries a verified Devicetree label
|
|
777
|
+
(`_has_real_dt_label()`) -- the only kind of device safe to name back
|
|
778
|
+
to a customer as a `flash_device:` alternative (#1484 / #1556: naming
|
|
779
|
+
one that merely LOOKS verified reproduces the exact defect either
|
|
780
|
+
issue closes). Used by `resolve_storage_partitions()`'s #1556
|
|
781
|
+
unverified-dt_label block; kept separate from the near-identical
|
|
782
|
+
filter already inlined in the overlap-block remedy below (that one
|
|
783
|
+
also excludes the SoM's own reserved region names, which don't apply
|
|
784
|
+
here) rather than risk a shared-helper refactor rippling into that
|
|
785
|
+
already-reviewed code path.
|
|
786
|
+
|
|
787
|
+
`aperture` defaults to `_APERTURE_UNSET` (resolve fresh); pass an
|
|
788
|
+
already-resolved aperture to share it across many calls in one
|
|
789
|
+
resolve (see `_APERTURE_UNSET`'s module comment).
|
|
790
|
+
"""
|
|
791
|
+
aperture = _resolve_aperture_arg(som_preset, metadata_root, aperture)
|
|
792
|
+
return sorted(
|
|
793
|
+
d for d in _known_flash_devices(som_preset, metadata_root, aperture)
|
|
794
|
+
if d != exclude
|
|
795
|
+
and _has_real_dt_label(d, som_preset, metadata_root)
|
|
796
|
+
and _resolve_flash_device(d, som_preset, metadata_root, aperture)[0] is not None)
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
def _blocked_partition(
|
|
800
|
+
entry: StorageEntry,
|
|
801
|
+
reason: str,
|
|
802
|
+
) -> ResolvedPartition:
|
|
803
|
+
"""Project a StorageEntry into a blocked ResolvedPartition."""
|
|
804
|
+
return ResolvedPartition(
|
|
805
|
+
name=entry.name,
|
|
806
|
+
fs=entry.fs,
|
|
807
|
+
flash_device=entry.flash_device or "",
|
|
808
|
+
dt_label="",
|
|
809
|
+
base_kib=0,
|
|
810
|
+
size_kib=entry.size_kib,
|
|
811
|
+
mount=entry.mount,
|
|
812
|
+
status="blocked",
|
|
813
|
+
reason=reason,
|
|
814
|
+
)
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
def resolve_storage_partitions(
|
|
818
|
+
project: BoardProject,
|
|
819
|
+
) -> list[ResolvedPartition]:
|
|
820
|
+
"""Allocate physical offsets for every storage[] entry.
|
|
821
|
+
|
|
822
|
+
Algorithm (mirrors `resolve_carve_outs()`):
|
|
823
|
+
1. Group entries by `flash_device:` (entries without one block
|
|
824
|
+
immediately; the loader normally catches that, but this is
|
|
825
|
+
the resolver's failure mode for hand-built projects).
|
|
826
|
+
2. Within each group, sort by name for determinism (an explicit
|
|
827
|
+
`offset_kib:` doesn't change sort order — it's an override
|
|
828
|
+
the allocator simply respects).
|
|
829
|
+
3. Place every `offset_kib:`-pinned entry FIRST (page-aligned
|
|
830
|
+
check; overlap check against the SoM's own regions and prior
|
|
831
|
+
pins), then bump-allocate the rest bottom-up from the current
|
|
832
|
+
high-water mark, stepping over the pins. Pinning first is
|
|
833
|
+
what keeps a pin that sorts LATE alphabetically from being
|
|
834
|
+
dropped by a sibling the allocator had already placed on top
|
|
835
|
+
of it (#554).
|
|
836
|
+
4. Block on capacity overflow, TBD device, page-misaligned
|
|
837
|
+
offset, or sibling overlap. Blocked entries land in the
|
|
838
|
+
manifest with `status: blocked` + `reason:` so reviewers see
|
|
839
|
+
the gap; the slice build trips when the DTS overlay is
|
|
840
|
+
consumed.
|
|
841
|
+
|
|
842
|
+
Spec note: byte-stable OTA images require the orchestrator to pin
|
|
843
|
+
addresses (resolved in design Q1 for v0.6 -- option "Pin in
|
|
844
|
+
orchestrator"). Customers get reproducible per-rebuild addresses
|
|
845
|
+
by declaring stable `name:` slugs; partitions stay at the same
|
|
846
|
+
offset across rebuilds as long as their relative order doesn't
|
|
847
|
+
change. Explicit `offset_kib:` is the escape hatch.
|
|
848
|
+
"""
|
|
849
|
+
if not project.storage:
|
|
850
|
+
return []
|
|
851
|
+
|
|
852
|
+
# Group by flash device, preserving original order for the
|
|
853
|
+
# downstream resolver (we re-sort for allocation determinism below).
|
|
854
|
+
by_device: dict[str, list[StorageEntry]] = {}
|
|
855
|
+
no_device: list[StorageEntry] = []
|
|
856
|
+
for entry in project.storage:
|
|
857
|
+
if not entry.flash_device:
|
|
858
|
+
no_device.append(entry)
|
|
859
|
+
else:
|
|
860
|
+
by_device.setdefault(entry.flash_device, []).append(entry)
|
|
861
|
+
|
|
862
|
+
resolved: list[ResolvedPartition] = []
|
|
863
|
+
|
|
864
|
+
for entry in no_device:
|
|
865
|
+
resolved.append(_blocked_partition(entry, (
|
|
866
|
+
f"storage entry '{entry.name}' has no flash_device: "
|
|
867
|
+
f"declared; add one referencing a SoM memory_map region "
|
|
868
|
+
f"or an on_module.ospi_memories key")))
|
|
869
|
+
|
|
870
|
+
# alp-sdk#1365 split B (Also fix): hoist the aperture ONCE for this
|
|
871
|
+
# whole resolve, the way `carveout.py` hoists it once per
|
|
872
|
+
# `resolve_carve_outs()` call -- every downstream helper below
|
|
873
|
+
# (`_resolve_flash_device`, `_known_flash_devices` via
|
|
874
|
+
# `_verified_alt_devices`, the alt-device scan inlined in `_place()`)
|
|
875
|
+
# otherwise re-parses the SoC JSON and re-resolves the silicon variant
|
|
876
|
+
# once per region AND once per storage entry / alt-device enumeration.
|
|
877
|
+
aperture = resolve_aperture(
|
|
878
|
+
project.som_preset, project.effective_metadata_root())
|
|
879
|
+
|
|
880
|
+
# Iterate flash devices in alphabetical order; within each device,
|
|
881
|
+
# entries are name-sorted for byte-stable allocation.
|
|
882
|
+
for device_name in sorted(by_device.keys()):
|
|
883
|
+
descriptor, block_reason = _resolve_flash_device(
|
|
884
|
+
device_name, project.som_preset,
|
|
885
|
+
project.effective_metadata_root(), aperture)
|
|
886
|
+
if descriptor is None:
|
|
887
|
+
for entry in by_device[device_name]:
|
|
888
|
+
resolved.append(_blocked_partition(
|
|
889
|
+
entry, block_reason or "flash device unresolvable"))
|
|
890
|
+
continue
|
|
891
|
+
|
|
892
|
+
dt_label = descriptor["dt_label"]
|
|
893
|
+
capacity_bytes = descriptor["size_bytes"]
|
|
894
|
+
# True when `device_name` is an `on_module.ospi_memories:` key --
|
|
895
|
+
# an off-chip OSPI part that legitimately has no `memory_map:`
|
|
896
|
+
# presence at all, vs. an on-chip `memory_map:` device (`ddr_main`
|
|
897
|
+
# etc.) where the SoM's own regions really do live on the same
|
|
898
|
+
# address window. Used below to keep the reserved-spans warning
|
|
899
|
+
# for the case it exists to catch.
|
|
900
|
+
is_ospi_device = device_name in (
|
|
901
|
+
(project.som_preset.get("on_module") or {}).get(
|
|
902
|
+
"ospi_memories") or {})
|
|
903
|
+
# #1556: `descriptor["dt_label"]` DEFAULTS to the device name
|
|
904
|
+
# whenever the SoM preset sets no explicit `dt_label:` override,
|
|
905
|
+
# and that default was never checked against the generated
|
|
906
|
+
# Zephyr board tree -- `flash_device: mram_main` resolved
|
|
907
|
+
# `status: ok` with a fabricated `dt_label` of `mram_main` even
|
|
908
|
+
# though the generated tree only defines `mram_storage`. Gate on
|
|
909
|
+
# the exact predicate #1484 already built for this
|
|
910
|
+
# (`_has_real_dt_label()`) so an entry can never reach
|
|
911
|
+
# `status: ok` on a `memory_map:` device whose label is
|
|
912
|
+
# unverified -- applied in `_place()` below, AFTER the
|
|
913
|
+
# overlap/overflow checks, so an entry that was already going to
|
|
914
|
+
# block for one of THOSE reasons keeps that reason (the SoM's
|
|
915
|
+
# own regions are the more actionable fact to report).
|
|
916
|
+
# `on_module.ospi_memories:` devices are deliberately exempted
|
|
917
|
+
# (see `_has_real_dt_label()`'s docstring) -- that key's
|
|
918
|
+
# verification gap is separate follow-up.
|
|
919
|
+
verified_dt_label = is_ospi_device or _has_real_dt_label(
|
|
920
|
+
device_name, project.som_preset,
|
|
921
|
+
project.effective_metadata_root())
|
|
922
|
+
# Page-aligned high-water mark; sibling partitions allocate
|
|
923
|
+
# bottom-up from offset 0. Page = 4 KiB matches the IPC
|
|
924
|
+
# carve-out allocator; storage erase blocks on every silicon
|
|
925
|
+
# the SDK targets are 4 KiB-or-larger multiples.
|
|
926
|
+
high_water_bytes = 0
|
|
927
|
+
# Track allocated [lo, hi) ranges so explicit `offset_kib:`
|
|
928
|
+
# overrides can be checked against sibling allocations even
|
|
929
|
+
# when the bump allocator would have placed them differently.
|
|
930
|
+
allocated: list[tuple[int, int, str]] = [] # (lo, hi, name)
|
|
931
|
+
|
|
932
|
+
# Seed with the SoM's OWN regions (alp-sdk#1331). Without this the
|
|
933
|
+
# checks below only ever saw sibling storage[] entries, so mcuboot, a
|
|
934
|
+
# core's slot0 and the SE-owned atoc band were all invisible: on
|
|
935
|
+
# E1M-AEN801 a littlefs mount resolved to offset 0 of `mram_main` --
|
|
936
|
+
# MCUboot -- with no offset_kib: declared and no status: blocked.
|
|
937
|
+
reserved_spans, reserved_reason = _reserved_spans(
|
|
938
|
+
device_name, capacity_bytes, project.som_preset,
|
|
939
|
+
project.effective_metadata_root())
|
|
940
|
+
reserved_names = {name for _, _, name in reserved_spans}
|
|
941
|
+
allocated.extend(reserved_spans)
|
|
942
|
+
if reserved_reason is not None and not is_ospi_device:
|
|
943
|
+
# Degrading to sibling-only checking is exactly the silent
|
|
944
|
+
# false-PASS this fix exists to remove, so SAY so rather than
|
|
945
|
+
# letting the caller believe the SoM's regions were honoured.
|
|
946
|
+
# Suppressed for an `ospi_memories:` device: an off-chip OSPI
|
|
947
|
+
# part has no `memory_map:` presence to begin with, so sibling-
|
|
948
|
+
# only checking isn't a degradation there -- warning on it
|
|
949
|
+
# would fire on every AEN storage build using `ospi0` (#1484
|
|
950
|
+
# review).
|
|
951
|
+
print(f"alp_orchestrate.partition: WARNING: {reserved_reason}; "
|
|
952
|
+
f"storage offsets on '{device_name}' are checked against "
|
|
953
|
+
f"sibling partitions only", file=sys.stderr)
|
|
954
|
+
|
|
955
|
+
entries_sorted = sorted(
|
|
956
|
+
by_device[device_name], key=lambda e: e.name)
|
|
957
|
+
|
|
958
|
+
# (base_bytes, size_aligned, reason) per entry INDEX. Placement runs
|
|
959
|
+
# in TWO passes over the name-sorted list -- every `offset_kib:` pin
|
|
960
|
+
# first, then the bump allocator around them (#554). In one pass a
|
|
961
|
+
# pin only ever became an obstacle for the siblings that happened to
|
|
962
|
+
# sort BEFORE it, so `pinned_low` (offset_kib: 0, the shape
|
|
963
|
+
# docs/board-config-features.md itself documents) collided with an
|
|
964
|
+
# already-auto-allocated `app_data` and was silently dropped from
|
|
965
|
+
# dts-partitions.dtsi -- a validate-clean layout the allocator could
|
|
966
|
+
# have satisfied. Emission below still walks `entries_sorted`.
|
|
967
|
+
# Keyed by index rather than name because board.schema.json does not
|
|
968
|
+
# require `storage[].name` to be unique.
|
|
969
|
+
placement: dict[int, tuple[int, int, Optional[str]]] = {}
|
|
970
|
+
|
|
971
|
+
def _place(entry: StorageEntry) -> tuple[int, int, Optional[str]]:
|
|
972
|
+
size_bytes = entry.size_kib * 1024
|
|
973
|
+
size_aligned = ((size_bytes + _PAGE - 1) // _PAGE) * _PAGE
|
|
974
|
+
|
|
975
|
+
if entry.offset_kib is not None:
|
|
976
|
+
base_bytes = entry.offset_kib * 1024
|
|
977
|
+
if base_bytes % _PAGE != 0:
|
|
978
|
+
return 0, size_aligned, (
|
|
979
|
+
f"storage entry '{entry.name}' explicit "
|
|
980
|
+
f"offset_kib={entry.offset_kib} is not page-"
|
|
981
|
+
f"aligned (4 KiB)")
|
|
982
|
+
else:
|
|
983
|
+
# Allocate AROUND the reserved regions and the pins, not
|
|
984
|
+
# merely detect a collision with them (alp-sdk#1331 / #554):
|
|
985
|
+
# refusing here instead of skipping would break every
|
|
986
|
+
# project that names no addresses -- which is the common
|
|
987
|
+
# case -- rather than fix it.
|
|
988
|
+
base_bytes = _first_free(
|
|
989
|
+
high_water_bytes, size_aligned, allocated, capacity_bytes)
|
|
990
|
+
|
|
991
|
+
top_bytes = base_bytes + size_aligned
|
|
992
|
+
if top_bytes > capacity_bytes:
|
|
993
|
+
free_note = ""
|
|
994
|
+
if entry.offset_kib is None and reserved_spans:
|
|
995
|
+
free_note = (
|
|
996
|
+
f"; the SoM's own regions ("
|
|
997
|
+
f"{', '.join(sorted(reserved_names))}) already occupy "
|
|
998
|
+
f"{sum(hi - lo for lo, hi, _ in reserved_spans) // 1024} "
|
|
999
|
+
f"KiB of it")
|
|
1000
|
+
return 0, size_aligned, (
|
|
1001
|
+
f"storage entry '{entry.name}' ({entry.size_kib} "
|
|
1002
|
+
f"KiB at offset {base_bytes // 1024} KiB) overruns "
|
|
1003
|
+
f"flash device '{device_name}' "
|
|
1004
|
+
f"(capacity {capacity_bytes // 1024} KiB){free_note}")
|
|
1005
|
+
|
|
1006
|
+
# Overlap check -- against sibling partitions AND the SoM's own
|
|
1007
|
+
# regions seeded above.
|
|
1008
|
+
overlap_with: Optional[str] = None
|
|
1009
|
+
for lo, hi, peer_name in allocated:
|
|
1010
|
+
if not (top_bytes <= lo or base_bytes >= hi):
|
|
1011
|
+
overlap_with = peer_name
|
|
1012
|
+
break
|
|
1013
|
+
if overlap_with is not None:
|
|
1014
|
+
where = (f"offset_kib={entry.offset_kib}"
|
|
1015
|
+
if entry.offset_kib is not None
|
|
1016
|
+
else f"auto-allocated offset {base_bytes // 1024} KiB")
|
|
1017
|
+
if overlap_with in reserved_names:
|
|
1018
|
+
# `overlap_with` is one of the SoM's OWN regions (any
|
|
1019
|
+
# `memory_map:` region with an addressed `base:` is
|
|
1020
|
+
# seeded into `reserved_spans` regardless of its
|
|
1021
|
+
# `carveout:` value -- see `_reserved_spans()`) -- it
|
|
1022
|
+
# must NOT be named here as something to target
|
|
1023
|
+
# directly (#1484), and neither may any other reserved
|
|
1024
|
+
# region: they are exactly what this entry just failed
|
|
1025
|
+
# to overlap. A name in `_known_flash_devices()` is
|
|
1026
|
+
# not enough either -- e.g. an `ospi_memories:` entry
|
|
1027
|
+
# with a TBD capacity (E1M-AEN801's `ospi1`) is known
|
|
1028
|
+
# but does not actually resolve, and a device that DOES
|
|
1029
|
+
# resolve may still be recommending an undefined DT
|
|
1030
|
+
# node (`_resolve_flash_device()` defaults `dt_label`
|
|
1031
|
+
# to the device name when unset -- see
|
|
1032
|
+
# `_has_real_dt_label()`). Only recommend a device
|
|
1033
|
+
# that both resolves AND has a verified DT label.
|
|
1034
|
+
alt_devices = [
|
|
1035
|
+
d for d in _known_flash_devices(
|
|
1036
|
+
project.som_preset,
|
|
1037
|
+
project.effective_metadata_root(), aperture)
|
|
1038
|
+
if d != device_name
|
|
1039
|
+
and d not in reserved_names
|
|
1040
|
+
and _has_real_dt_label(
|
|
1041
|
+
d, project.som_preset,
|
|
1042
|
+
project.effective_metadata_root())
|
|
1043
|
+
and _resolve_flash_device(
|
|
1044
|
+
d, project.som_preset,
|
|
1045
|
+
project.effective_metadata_root(), aperture)[0]
|
|
1046
|
+
is not None]
|
|
1047
|
+
# Only lead with "pick an offset on <device>" when the
|
|
1048
|
+
# device actually has free room outside its reserved
|
|
1049
|
+
# spans. Reserved spans come from ANY addressed
|
|
1050
|
+
# memory_map region, `carveout: false` or not; AEN's
|
|
1051
|
+
# `mram_main` is the fully-tiled case (0 KiB free,
|
|
1052
|
+
# tiled exactly by its `carveout: false` sub-regions),
|
|
1053
|
+
# not the only case this branch can fire for.
|
|
1054
|
+
reserved_bytes = sum(
|
|
1055
|
+
hi - lo for lo, hi, _ in reserved_spans)
|
|
1056
|
+
if reserved_bytes < capacity_bytes:
|
|
1057
|
+
remedy = (
|
|
1058
|
+
f"pick an offset on '{device_name}' outside "
|
|
1059
|
+
f"the SoM's declared regions")
|
|
1060
|
+
if alt_devices:
|
|
1061
|
+
remedy += (
|
|
1062
|
+
f", or use a different flash_device: "
|
|
1063
|
+
f"({', '.join(alt_devices)})")
|
|
1064
|
+
elif alt_devices:
|
|
1065
|
+
remedy = (
|
|
1066
|
+
f"'{device_name}' is fully tiled by the SoM's "
|
|
1067
|
+
f"own regions ({capacity_bytes // 1024} KiB, "
|
|
1068
|
+
f"no free room) -- use a different "
|
|
1069
|
+
f"flash_device: ({', '.join(alt_devices)})")
|
|
1070
|
+
else:
|
|
1071
|
+
# NOT "no other flash_device: resolves" -- on an
|
|
1072
|
+
# AEN SoM, ospi0 typically DOES resolve
|
|
1073
|
+
# (`_resolve_flash_device()`), it just has no
|
|
1074
|
+
# verified Devicetree label yet (see
|
|
1075
|
+
# `_has_real_dt_label()`). Naming it here would
|
|
1076
|
+
# recommend the very label #1484 exists to stop
|
|
1077
|
+
# fabricating, so the message must stay true for
|
|
1078
|
+
# BOTH cases (nothing resolves / something
|
|
1079
|
+
# resolves but is unverified) without naming a
|
|
1080
|
+
# device either way.
|
|
1081
|
+
remedy = (
|
|
1082
|
+
f"'{device_name}' is fully tiled by the SoM's "
|
|
1083
|
+
f"own regions ({capacity_bytes // 1024} KiB, "
|
|
1084
|
+
f"no free room) and no other flash_device: on "
|
|
1085
|
+
f"this SoM both resolves and has a verified "
|
|
1086
|
+
f"Devicetree label (alp-sdk#1556)")
|
|
1087
|
+
return 0, size_aligned, (
|
|
1088
|
+
f"storage entry '{entry.name}' {where} overlaps SoM "
|
|
1089
|
+
f"region '{overlap_with}' on device '{device_name}' "
|
|
1090
|
+
f"-- that region is not customer-writable. "
|
|
1091
|
+
f"{remedy}.")
|
|
1092
|
+
return 0, size_aligned, (
|
|
1093
|
+
f"storage entry '{entry.name}' {where} overlaps "
|
|
1094
|
+
f"sibling partition '{overlap_with}' on device "
|
|
1095
|
+
f"'{device_name}'")
|
|
1096
|
+
|
|
1097
|
+
if not verified_dt_label:
|
|
1098
|
+
# #1556: this entry cleared every other check (page
|
|
1099
|
+
# alignment, capacity, sibling/reserved overlap) and was
|
|
1100
|
+
# about to resolve `status: ok` -- but `dt_label` is the
|
|
1101
|
+
# device NAME, defaulted because no SoM preset declares
|
|
1102
|
+
# an explicit `dt_label:` override for '{device_name}',
|
|
1103
|
+
# and that default has never been checked against the
|
|
1104
|
+
# generated board tree. Refuse rather than emit a
|
|
1105
|
+
# `partitions { }` overlay under a label the board `.dts`
|
|
1106
|
+
# may not define (`grep -rn '{dt_label}' zephyr/` is the
|
|
1107
|
+
# check to run). Checked HERE, not earlier, so an entry
|
|
1108
|
+
# that would ALSO overlap a reserved/sibling region still
|
|
1109
|
+
# gets that more actionable reason instead.
|
|
1110
|
+
alt = _verified_alt_devices(
|
|
1111
|
+
device_name, project.som_preset,
|
|
1112
|
+
project.effective_metadata_root(), aperture)
|
|
1113
|
+
remedy = (
|
|
1114
|
+
f"use a different flash_device: ({', '.join(alt)})"
|
|
1115
|
+
if alt else
|
|
1116
|
+
"no other flash_device: on this SoM both resolves "
|
|
1117
|
+
"and has a verified Devicetree label")
|
|
1118
|
+
return 0, size_aligned, (
|
|
1119
|
+
f"storage entry '{entry.name}' would resolve on "
|
|
1120
|
+
f"flash device '{device_name}', but its Devicetree "
|
|
1121
|
+
f"label defaults to '{dt_label}' (the device name) "
|
|
1122
|
+
f"-- no SoM preset declares an explicit dt_label: "
|
|
1123
|
+
f"override for '{device_name}', so this default is "
|
|
1124
|
+
f"unverified against the generated board tree and "
|
|
1125
|
+
f"may decorate a `partitions {{ }}` child under a "
|
|
1126
|
+
f"node the board `.dts` never defines; {remedy}.")
|
|
1127
|
+
|
|
1128
|
+
allocated.append((base_bytes, top_bytes, entry.name))
|
|
1129
|
+
return base_bytes, size_aligned, None
|
|
1130
|
+
|
|
1131
|
+
for index, entry in enumerate(entries_sorted):
|
|
1132
|
+
if entry.offset_kib is not None:
|
|
1133
|
+
placement[index] = _place(entry)
|
|
1134
|
+
for index, entry in enumerate(entries_sorted):
|
|
1135
|
+
if entry.offset_kib is None:
|
|
1136
|
+
base_bytes, size_aligned, reason = _place(entry)
|
|
1137
|
+
placement[index] = (base_bytes, size_aligned, reason)
|
|
1138
|
+
if reason is None:
|
|
1139
|
+
# Only bump the allocator when we used it; explicit
|
|
1140
|
+
# offsets don't shift the high-water mark (they may be
|
|
1141
|
+
# below it deliberately, e.g. to reserve a low slot) --
|
|
1142
|
+
# `allocated` is what makes the allocator step over them.
|
|
1143
|
+
high_water_bytes = base_bytes + size_aligned
|
|
1144
|
+
|
|
1145
|
+
for index, entry in enumerate(entries_sorted):
|
|
1146
|
+
base_bytes, size_aligned, reason = placement[index]
|
|
1147
|
+
if reason is not None:
|
|
1148
|
+
resolved.append(_blocked_partition(entry, reason))
|
|
1149
|
+
continue
|
|
1150
|
+
resolved.append(ResolvedPartition(
|
|
1151
|
+
name=entry.name,
|
|
1152
|
+
fs=entry.fs,
|
|
1153
|
+
flash_device=device_name,
|
|
1154
|
+
dt_label=dt_label,
|
|
1155
|
+
base_kib=base_bytes // 1024,
|
|
1156
|
+
size_kib=size_aligned // 1024,
|
|
1157
|
+
mount=entry.mount,
|
|
1158
|
+
))
|
|
1159
|
+
|
|
1160
|
+
return resolved
|