Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions lopper/assists/zephyr_linker.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
LayoutError, parse_layout, zephyr_argument_parser,
)

TCM_REGION_ENABLE = 0x1


def is_compat(node, compat_string_to_test):
"""Identify the assist compatibility string.
Expand Down Expand Up @@ -104,6 +106,30 @@ def _render_linker(layout, user_contents=None):
template = template.replace(
"#include <zephyr/linker/common-ram.ld>", common_ram)
custom_lines = []
if layout.profile == "r52-tcm":
memories_by_kind = {memory.kind: memory for memory in layout.memories}
atcm = memories_by_kind.get("ATCM")
btcm = memories_by_kind.get("BTCM")
ctcm = memories_by_kind.get("CTCM")
if atcm is None:
raise LayoutError("R52 TCM profile requires ATCM")
custom_lines.extend((
" /* Cortex-R52 TCM configuration consumed before stack setup.",
" * Bit 0 enables a local-address mapping; a zero word leaves",
" * the bank's existing boot-firmware configuration unchanged.",
" */",
" .tcm_config :",
" {",
" . = ALIGN(4);",
" z_arm_tcm_a_region = .;",
" LONG(0x00000000)",
" z_arm_tcm_b_region = .;",
f" LONG(0x{((btcm.origin | TCM_REGION_ENABLE) if btcm else 0):08x})",
" z_arm_tcm_c_region = .;",
f" LONG(0x{((ctcm.origin | TCM_REGION_ENABLE) if ctcm else 0):08x})",
f" }} > {atcm.name}",
"",
))
for section in (item for item in layout.sections if item.custom):
custom_address = ""
if section.offset is not None:
Expand Down
25 changes: 12 additions & 13 deletions lopper/assists/zephyr_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import re
import sys

from lopper.log import _warning

sys.path.append(os.path.dirname(__file__))

try:
Expand Down Expand Up @@ -43,6 +45,10 @@
"readable", "writable", "executable", "cacheable", "shareable",
"userspace", "static",
}
TCM_LOCAL_ORIGINS = {
"cortexr5": {"ATCM": 0x0, "BTCM": 0x20000},
"cortexr52": {"ATCM": 0x0, "BTCM": 0x10000, "CTCM": 0x18000},
}


class LayoutError(ValueError):
Expand Down Expand Up @@ -598,14 +604,6 @@ def _infer_profile(processor, memories, sections, entry):
if is_r52 and vector_offset % 32:
raise LayoutError(
"Cortex-R52 vector_table offset must be 32-byte aligned")
expected = ({"BTCM": 0x10000, "CTCM": 0x20000} if is_r52
else {"BTCM": 0x20000})
for kind, origin in expected.items():
memory = next((item for item in memories if item.kind == kind), None)
if memory and memory.origin != origin:
raise LayoutError(
f"{kind} must use local address 0x{origin:x} for "
f"processor '{processor}'")
return "r52-tcm" if is_r52 else "r5-tcm"
if vector_memory.kind == "DDR" and is_r52:
return "r52-ddr"
Expand Down Expand Up @@ -753,11 +751,12 @@ def _normalized_memory(node, processor):
name = _linker_name(node)
kind = _memory_kind(node)
origin, length = _memory_range(node)
if processor == "cortexr52":
origin = {"ATCM": 0x0, "BTCM": 0x10000,
"CTCM": 0x20000}.get(kind, origin)
elif processor == "cortexr5":
origin = {"ATCM": 0x0, "BTCM": 0x20000}.get(kind, origin)
local_origin = TCM_LOCAL_ORIGINS.get(processor, {}).get(kind)
if local_origin is not None and origin != local_origin:
_warning(
f"{node.abs_path}: SDT declares {kind} at 0x{origin:x}; "
f"normalizing to 0x{local_origin:x} for {processor}")
origin = local_origin
return Memory(name, node, origin, length, _memory_policy(node, kind), kind)


Expand Down
4 changes: 2 additions & 2 deletions lopper/selftest/domains/openamp-zephyr-linker-r52.dts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
label = "BTCM";
mpu-policy = "readable", "writable", "cacheable", "static";
};
ctcm: r52_0a_ctcm_global@20000 {
reg = <0x20000 0x8000>;
ctcm: r52_0a_ctcm_global@18000 {
reg = <0x18000 0x8000>;
label = "CTCM";
mpu-policy = "readable", "writable", "cacheable", "static";
};
Expand Down
11 changes: 9 additions & 2 deletions lopper_sanity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2365,6 +2365,13 @@ def zephyr_linker_generator_sanity_test():
r52_passed = r52_passed and \
"DATA_LOAD_REGION ATCM" in contents and \
"TEXT_ADDRESS ORIGIN(DDR) + 0x20" in contents and \
". = ALIGN(4);" in contents and \
"z_arm_tcm_a_region = .;" in contents and \
"LONG(0x00000000)" in contents and \
"z_arm_tcm_b_region = .;" in contents and \
"LONG(0x00010001)" in contents and \
"z_arm_tcm_c_region = .;" in contents and \
"LONG(0x00018001)" in contents and \
"SECTION_PROLOGUE(_TEXT_SECTION_NAME " \
"TEXT_ADDRESS,,)" in contents
if r52_passed:
Expand Down Expand Up @@ -2492,7 +2499,7 @@ def zephyr_linker_generator_sanity_test():
overlap_fixture = "/tmp/openamp-zephyr-overlapping-mpu.dts"
Path(overlap_fixture).write_text(
r52_fixture.replace("reg = <0x100000 0x80000>;",
"reg = <0x21000 0x80000>;", 1)
"reg = <0x19000 0x80000>;", 1)
.replace(', "static";', ';'),
encoding="utf-8")
overlap_command = [
Expand All @@ -2506,7 +2513,7 @@ def zephyr_linker_generator_sanity_test():
check=False)
overlap_log = overlap_result.stdout + overlap_result.stderr
if "MPU regions overlap:" in overlap_log and \
"overlap [0x21000, 0x28000)" in overlap_log:
"overlap [0x19000, 0x20000)" in overlap_log:
test_passed("OpenAMP Zephyr MPU overlap validation")
else:
print(overlap_log)
Expand Down
123 changes: 123 additions & 0 deletions tests/test_zephyr_linker_tcm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""Tests for Cortex-R52 TCM configuration in generated Zephyr linkers."""

# Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause

import os
import re
import sys

import pytest

# Lopper loads assists as top-level modules. Mirror that here so the classes
# the generator consumes and raises are the ones this test compares against.
sys.path.append(
os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"lopper", "assists"))

from zephyr_linker import _render_linker # noqa: E402
from zephyr_memory import ( # noqa: E402
Layout,
LayoutError,
Memory,
MemoryPolicy,
Section,
)

TCM_POLICY = (MemoryPolicy.READABLE | MemoryPolicy.WRITABLE |
MemoryPolicy.EXECUTABLE)
CODE_SECTIONS = ("vector_table", "text", "rodata")
DATA_SECTIONS = ("data", "bss", "noinit", "heap", "stack")


def _memory(kind, origin, length=0x8000):
"""Build one normalized linker memory of the given bank kind."""
return Memory(kind, None, origin, length, TCM_POLICY, kind)


def _layout(*memories, profile="r52-tcm", code="ATCM", data="BTCM"):
"""Build a minimal R52 layout that renders a complete linker script."""
sections = tuple(
[Section(name, code) for name in CODE_SECTIONS] +
[Section(name, data) for name in DATA_SECTIONS])
return Layout("cortexr52", profile, "_vector_table", "4.3",
tuple(memories), sections, None, "RPU_ZEPHYR.ld")


def _tcm_words(script):
"""Return the ordered symbol and word pairs of the .tcm_config block."""
return re.findall(
r"(z_arm_tcm_[abc]_region) = \.;\s*\n\s*LONG\((0x[0-9a-f]{8})\)",
script)


def test_tcm_config_declares_the_zephyr_startup_symbols():
"""Zephyr reads these exact symbols before its stack is usable.

The names are a cross-repository ABI: renaming one side alone makes
Zephyr silently fall back to its weak defaults instead of failing.
"""
script = _render_linker(_layout(
_memory("ATCM", 0x0, 0x10000),
_memory("BTCM", 0x10000),
_memory("CTCM", 0x18000)))

assert _tcm_words(script) == [
("z_arm_tcm_a_region", "0x00000000"),
("z_arm_tcm_b_region", "0x00010001"),
("z_arm_tcm_c_region", "0x00018001"),
]


def test_tcm_config_is_placed_in_the_vector_bank():
"""The words are read at reset, so they load with the vector image."""
script = _render_linker(_layout(
_memory("ATCM", 0x0, 0x10000),
_memory("BTCM", 0x10000),
_memory("CTCM", 0x18000)))

start = script.index(".tcm_config")
assert script[script.index("} >", start):].startswith("} > ATCM")


def test_tcm_config_words_follow_the_selected_bank_origins():
"""Each word carries its bank's local base, not a hard-coded address."""
script = _render_linker(_layout(
_memory("ATCM", 0x0, 0x10000),
_memory("BTCM", 0x20000),
_memory("CTCM", 0x30000)))

assert _tcm_words(script) == [
("z_arm_tcm_a_region", "0x00000000"),
("z_arm_tcm_b_region", "0x00020001"),
("z_arm_tcm_c_region", "0x00030001"),
]


def test_tcm_config_leaves_an_unselected_bank_unchanged():
"""A domain without CTCM emits a zero word, clearing the enable bit."""
script = _render_linker(_layout(
_memory("ATCM", 0x0, 0x10000),
_memory("BTCM", 0x10000)))

assert _tcm_words(script) == [
("z_arm_tcm_a_region", "0x00000000"),
("z_arm_tcm_b_region", "0x00010001"),
("z_arm_tcm_c_region", "0x00000000"),
]


def test_ddr_profile_emits_no_tcm_config():
"""DDR-booted R52 images do not remap their local banks."""
script = _render_linker(_layout(
_memory("DDR", 0x9800100, 0x5de00),
profile="r52-ddr", code="DDR", data="DDR"))

assert ".tcm_config" not in script
assert "z_arm_tcm_" not in script


def test_tcm_profile_requires_atcm():
"""Without ATCM there is no reset-reachable home for the words."""
with pytest.raises(LayoutError, match="requires ATCM"):
_render_linker(_layout(_memory("BTCM", 0x10000)))
25 changes: 25 additions & 0 deletions tests/test_zephyr_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause

from lopper.assists import zephyr_memory
from lopper.assists.zephyr_memory import _domain_memory_nodes, _normalized_memories
from lopper.tree import LopperNode, LopperTree

Expand Down Expand Up @@ -79,3 +80,27 @@ def test_linker_name_retains_unit_address_without_label():
memories = _normalized_memories((first, second), "cortexr5")

assert tuple(memory.name for memory in memories) == ("SRAM_0", "SRAM_20000")


def test_r52_tcm_origin_normalization_warns(monkeypatch):
"""An SDT/local-address mismatch is visible while retaining normalization."""
tree = LopperTree()
axi = LopperNode(-1, "/axi")
tree + axi
axi["#address-cells"] = [1]
axi["#size-cells"] = [1]
ctcm = LopperNode(-1, "/axi/ctcm@20000")
tree + ctcm
ctcm["reg"] = [0x20000, 0x8000]
ctcm["zephyr,memory-region"] = ["CTCM"]
ctcm["mpu-policy"] = ["readable", "writable", "static"]
warnings = []
monkeypatch.setattr(zephyr_memory, "_warning", warnings.append)

memory = zephyr_memory._normalized_memory(ctcm, "cortexr52")

assert memory.origin == 0x18000
assert warnings == [
"/axi/ctcm@20000: SDT declares CTCM at 0x20000; "
"normalizing to 0x18000 for cortexr52"
]
Loading