From 94e2971bd497323509597233a3c9468083fdf037 Mon Sep 17 00:00:00 2001 From: Arkadiusz Grzelka Date: Tue, 14 Jul 2026 13:16:23 +0200 Subject: [PATCH] Match nested model keys against a node's own compatible list renode_model_overlay()'s nested-key match notation (used for entries like "st,stm32-ethernet") only checked the SoC/board root compatible strings returned by get_overlays(). It never looked at the node's own "compatible" property. This silently breaks any device whose node compat list reuses another SoC family's identifier for a shared IP block. STM32H5's Ethernet MAC is the same Synopsys DWC EQOS IP as STM32H7's, and Zephyr's H5 binding reflects that by listing "st,stm32h7-ethernet" alongside the node's own "st,stm32h5-ethernet"/"st,stm32-ethernet" compat strings. Since the H5 SoC/root compat never contains "st,stm32h7", the nested key in models.json's "st,stm32-ethernet" entry never matched, and every non-H7 STM32 with this Ethernet IP silently fell through to the "_" default model (Network.SynopsysEthernetMAC) instead of the correct Network.SynopsysDWCEthernetQualityOfService. Fix by also matching nested keys against the generating node's own compatible list, passed down from every call site that already has it in scope. Verified against the full current zephyr_sim dashboard replkit (17436 generated dts/board/sample combinations): exactly 145 outputs across 5 STM32 boards (H5, N6 and MP1 Ethernet-capable families) change, all changing only the Ethernet MAC model line in the expected direction (SynopsysEthernetMAC -> SynopsysDWCEthernetQualityOfService with its MTL/DMA regions and clock); zero other differences anywhere in the corpus. Fixes #13 --- dts2repl/dts2repl.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/dts2repl/dts2repl.py b/dts2repl/dts2repl.py index e7df9fb..1fbb543 100755 --- a/dts2repl/dts2repl.py +++ b/dts2repl/dts2repl.py @@ -390,7 +390,7 @@ def get_node_prop(node, prop, default=None, inherit=False): pass return None -def renode_model_overlay(compat, mcu, overlays): +def renode_model_overlay(compat, mcu, overlays, node_compatible=tuple()): def _try_decode(e): if e is None: # Null mapping -> reject model creation @@ -423,9 +423,15 @@ def _try_decode(e): if model is None: # let's look for a special match notation containing multiple entries separated with '|' or a '_' else entry + # + # `overlays` only carries the SoC/board root compatible strings (see get_overlays()), so a + # nested key never matched a peripheral's OWN "compatible" list. That silently broke devices + # whose node reuses another SoC family's compat string for a shared IP block (e.g. an STM32H5 + # ethernet node also lists "st,stm32h7-ethernet" because it's the same Synopsys DWC EQOS IP as + # H7) -- see antmicro/dts2repl#13. Check the node's own compat list too. for entry in models_entry: for subentry in entry.split("|"): - if subentry == "_" or any(subentry in o for o in overlays): + if subentry == "_" or any(subentry in o for o in overlays) or any(subentry in c for c in node_compatible): model, attribs, irq_mappings = _try_decode(models_entry[entry]) return model, compat, attribs, irq_mappings @@ -1125,9 +1131,10 @@ def can_be_memory(node): def get_model(node, mcu=None, overlays=tuple()): - node_compatible = next(filter(lambda x: x in MODELS, get_node_prop(node, 'compatible', [])), None) + compatible = get_node_prop(node, 'compatible', []) + node_compatible = next(filter(lambda x: x in MODELS, compatible), None) if node_compatible: - node_model, _, _, _ = renode_model_overlay(node_compatible, mcu, overlays) + node_model, _, _, _ = renode_model_overlay(node_compatible, mcu, overlays, compatible) return node_model return None @@ -1335,7 +1342,7 @@ def node_sort_key(node): # the node is an address-translating bus container whose children are the real peripherals - skip it if compat == 'zephyr,memory-region': def is_memory_compat(c): - return c in MODELS and (renode_model_overlay(c, mcu_compat, overlays)[0] or '').startswith('Memory') + return c in MODELS and (renode_model_overlay(c, mcu_compat, overlays, compatible)[0] or '').startswith('Memory') memory_compat = next((c for c in compatible if c != 'zephyr,memory-region' and is_memory_compat(c)), None) if memory_compat: @@ -1375,7 +1382,7 @@ def is_memory_compat(c): logging.info(f'Node {node.name} mapped to {name}...') # decide which Renode model to use - model, _, attribs, irq_mappings = renode_model_overlay(compat, mcu_compat, overlays) + model, _, attribs, irq_mappings = renode_model_overlay(compat, mcu_compat, overlays, compatible) if model is None: # There is no model for the given "specialized" SoC compat string, but they might exist for other SoC variants logging.info(f'Node {node.name}, compat {compat} has no matching specific model - does the JSON have "_" clause? Skipping...') @@ -1798,7 +1805,8 @@ def arm_gic_get_region(addr, size, name): found = True if found: - _, _, timer_attribs, timer_irq_mappings = renode_model_overlay(get_node_prop(timer_node, 'compatible', [''])[0], mcu_compat, overlays) + timer_node_compatible = get_node_prop(timer_node, 'compatible', ['']) + _, _, timer_attribs, timer_irq_mappings = renode_model_overlay(timer_node_compatible[0], mcu_compat, overlays, timer_node_compatible) if timer_irq_mappings: generic_timer_irq_names = timer_irq_mappings @@ -2227,7 +2235,7 @@ def process_node(node, node_type, mcu, overlays, get_snippets, skip_disabled): label = node.labels[0] if compat in MODELS: - model, _, _, _ = renode_model_overlay(compat, mcu, overlays) + model, _, _, _ = renode_model_overlay(compat, mcu, overlays, compats) if node_type == "cpu": if id := node.unit_addr: @@ -2362,7 +2370,7 @@ def generate_bus_sensors(filename, overlays): compat = compats[0] if compat in MODELS: mcu = get_mcu_compat(filename) - model, compat, _, _ = renode_model_overlay(compat, mcu, overlays) + model, compat, _, _ = renode_model_overlay(compat, mcu, overlays, compats) else: model = ''