From 2fbdb3a4d83214d9bea5974c5a7f59af6286fbdd Mon Sep 17 00:00:00 2001
From: Anders Roxell <anders.roxell@linaro.org>
Date: Thu, 11 Dec 2025 17:28:09 +0100
Subject: [PATCH] minor refactoring of Bens patches

Signed-off-by: Anders Roxell <anders.roxell@linaro.org>
---
 .../device_dicts}/ampereone-ac04.jinja2       |   2 +-
 .../device_dicts}/ampereone.jinja2            |   2 +-
 .../device_dicts}/bcm2711-rpi-4-b.jinja2      |   2 +-
 .../device_dicts}/dragonboard-410c.jinja2     |   2 +-
 .../device_dicts}/dragonboard-845c.jinja2     |   2 +-
 .../device_dicts}/gs101-oriole.jinja2         |   2 +-
 .../device_dicts}/juno-r2.jinja2              |   2 +-
 .../device_dicts}/orion-o6.jinja2             |   2 +-
 .../device_dicts}/qrb5165-rb5.jinja2          |   2 +-
 .../device_dicts}/s32g399a-rdb3.jinja2        |   2 +-
 tuxlava/__main__.py                           |   6 +-
 tuxlava/argparse.py                           |   9 +-
 tuxlava/devices/__init__.py                   | 132 ++++--------------
 tuxlava/devices/avh.py                        |   4 +-
 tuxlava/devices/fastboot.py                   |  37 ++---
 tuxlava/devices/fvp.py                        |   4 +-
 tuxlava/devices/nfs.py                        |  42 ++----
 tuxlava/devices/qemu.py                       |   4 +-
 tuxlava/devices/ssh.py                        |   4 +-
 tuxlava/jobs.py                               |  34 ++---
 .../devices/fastboot-base.yaml.jinja2         |  52 +++++++
 ...inja2 => fastboot-device-dict.yaml.jinja2} |  67 +++------
 .../devices/fastboot-standard.yaml.jinja2     |  12 ++
 .../templates/devices/nfs-base.yaml.jinja2    |  57 ++++++++
 ...aml.jinja2 => nfs-device-dict.yaml.jinja2} |  69 +++------
 .../devices/nfs-standard.yaml.jinja2          |  10 ++
 26 files changed, 264 insertions(+), 299 deletions(-)
 rename {tuxlava/devices/laa_configs => test/device_dicts}/ampereone-ac04.jinja2 (89%)
 rename {tuxlava/devices/laa_configs => test/device_dicts}/ampereone.jinja2 (93%)
 rename {tuxlava/devices/laa_configs => test/device_dicts}/bcm2711-rpi-4-b.jinja2 (84%)
 rename {tuxlava/devices/laa_configs => test/device_dicts}/dragonboard-410c.jinja2 (95%)
 rename {tuxlava/devices/laa_configs => test/device_dicts}/dragonboard-845c.jinja2 (95%)
 rename {tuxlava/devices/laa_configs => test/device_dicts}/gs101-oriole.jinja2 (97%)
 rename {tuxlava/devices/laa_configs => test/device_dicts}/juno-r2.jinja2 (89%)
 rename {tuxlava/devices/laa_configs => test/device_dicts}/orion-o6.jinja2 (91%)
 rename {tuxlava/devices/laa_configs => test/device_dicts}/qrb5165-rb5.jinja2 (95%)
 rename {tuxlava/devices/laa_configs => test/device_dicts}/s32g399a-rdb3.jinja2 (82%)
 create mode 100644 tuxlava/templates/devices/fastboot-base.yaml.jinja2
 rename tuxlava/templates/devices/{fastboot.yaml.jinja2 => fastboot-device-dict.yaml.jinja2} (60%)
 create mode 100644 tuxlava/templates/devices/fastboot-standard.yaml.jinja2
 create mode 100644 tuxlava/templates/devices/nfs-base.yaml.jinja2
 rename tuxlava/templates/devices/{nfs.yaml.jinja2 => nfs-device-dict.yaml.jinja2} (86%)
 create mode 100644 tuxlava/templates/devices/nfs-standard.yaml.jinja2

diff --git a/tuxlava/devices/laa_configs/ampereone-ac04.jinja2 b/test/device_dicts/ampereone-ac04.jinja2
similarity index 89%
rename from tuxlava/devices/laa_configs/ampereone-ac04.jinja2
rename to test/device_dicts/ampereone-ac04.jinja2
index 3cb7aeedd504..5fc9a848ec74 100644
--- a/tuxlava/devices/laa_configs/ampereone-ac04.jinja2
+++ b/test/device_dicts/ampereone-ac04.jinja2
@@ -1,4 +1,4 @@
-{# LAA config for AmpereOne AC04 (NFS boot with GRUB via IPMI SOL) #}
+{# device-dict config for AmpereOne AC04 (NFS boot with GRUB via IPMI SOL) #}
 
 {% set boot_method = 'grub' %}
 
diff --git a/tuxlava/devices/laa_configs/ampereone.jinja2 b/test/device_dicts/ampereone.jinja2
similarity index 93%
rename from tuxlava/devices/laa_configs/ampereone.jinja2
rename to test/device_dicts/ampereone.jinja2
index 09730cbe85b3..7ce4cb3b3224 100644
--- a/tuxlava/devices/laa_configs/ampereone.jinja2
+++ b/test/device_dicts/ampereone.jinja2
@@ -1,4 +1,4 @@
-{# LAA config for AmpereOne (NFS boot with GRUB) #}
+{# device-dict config for AmpereOne (NFS boot with GRUB) #}
 
 {% set boot_method = 'grub' %}
 
diff --git a/tuxlava/devices/laa_configs/bcm2711-rpi-4-b.jinja2 b/test/device_dicts/bcm2711-rpi-4-b.jinja2
similarity index 84%
rename from tuxlava/devices/laa_configs/bcm2711-rpi-4-b.jinja2
rename to test/device_dicts/bcm2711-rpi-4-b.jinja2
index 56bacaf54436..bff0e694254c 100644
--- a/tuxlava/devices/laa_configs/bcm2711-rpi-4-b.jinja2
+++ b/test/device_dicts/bcm2711-rpi-4-b.jinja2
@@ -1,4 +1,4 @@
-{# LAA config for Raspberry Pi 4B (NFS boot with U-Boot) #}
+{# device-dict config for Raspberry Pi 4B (NFS boot with U-Boot) #}
 
 {% set boot_method = 'u-boot' %}
 
diff --git a/tuxlava/devices/laa_configs/dragonboard-410c.jinja2 b/test/device_dicts/dragonboard-410c.jinja2
similarity index 95%
rename from tuxlava/devices/laa_configs/dragonboard-410c.jinja2
rename to test/device_dicts/dragonboard-410c.jinja2
index f56cfe776772..e575901591f8 100644
--- a/tuxlava/devices/laa_configs/dragonboard-410c.jinja2
+++ b/test/device_dicts/dragonboard-410c.jinja2
@@ -1,4 +1,4 @@
-{# LAA config for Dragonboard 410c (fastboot) #}
+{# device-dict config for Dragonboard 410c (fastboot) #}
 
 {% set boot_method = 'fastboot' %}
 
diff --git a/tuxlava/devices/laa_configs/dragonboard-845c.jinja2 b/test/device_dicts/dragonboard-845c.jinja2
similarity index 95%
rename from tuxlava/devices/laa_configs/dragonboard-845c.jinja2
rename to test/device_dicts/dragonboard-845c.jinja2
index b6d3c7026e1a..d29f79d3307b 100644
--- a/tuxlava/devices/laa_configs/dragonboard-845c.jinja2
+++ b/test/device_dicts/dragonboard-845c.jinja2
@@ -1,4 +1,4 @@
-{# LAA config for Dragonboard 845c (fastboot) #}
+{# device-dict config for Dragonboard 845c (fastboot) #}
 
 {% set boot_method = 'fastboot' %}
 
diff --git a/tuxlava/devices/laa_configs/gs101-oriole.jinja2 b/test/device_dicts/gs101-oriole.jinja2
similarity index 97%
rename from tuxlava/devices/laa_configs/gs101-oriole.jinja2
rename to test/device_dicts/gs101-oriole.jinja2
index 2a9889d2358e..9c9b1827b921 100644
--- a/tuxlava/devices/laa_configs/gs101-oriole.jinja2
+++ b/test/device_dicts/gs101-oriole.jinja2
@@ -1,4 +1,4 @@
-{# LAA config for Google Pixel 6 gs101-oriole (fastboot) #}
+{# device-dict config for Google Pixel 6 gs101-oriole (fastboot) #}
 
 {% set boot_method = 'fastboot' %}
 
diff --git a/tuxlava/devices/laa_configs/juno-r2.jinja2 b/test/device_dicts/juno-r2.jinja2
similarity index 89%
rename from tuxlava/devices/laa_configs/juno-r2.jinja2
rename to test/device_dicts/juno-r2.jinja2
index 0abe44533982..b7b8caaef33b 100644
--- a/tuxlava/devices/laa_configs/juno-r2.jinja2
+++ b/test/device_dicts/juno-r2.jinja2
@@ -1,4 +1,4 @@
-{# LAA config for ARM Juno R2 (NFS boot with U-Boot) #}
+{# device-dict config for ARM Juno R2 (NFS boot with U-Boot) #}
 
 {% set boot_method = 'u-boot' %}
 
diff --git a/tuxlava/devices/laa_configs/orion-o6.jinja2 b/test/device_dicts/orion-o6.jinja2
similarity index 91%
rename from tuxlava/devices/laa_configs/orion-o6.jinja2
rename to test/device_dicts/orion-o6.jinja2
index 128afc0cc0b6..b22089efcf3b 100644
--- a/tuxlava/devices/laa_configs/orion-o6.jinja2
+++ b/test/device_dicts/orion-o6.jinja2
@@ -1,4 +1,4 @@
-{# LAA config for Orion O6 (NFS boot with GRUB) #}
+{# device-dict config for Orion O6 (NFS boot with GRUB) #}
 
 {% set boot_method = 'grub' %}
 {% set grub_needs_interrupt = false %}
diff --git a/tuxlava/devices/laa_configs/qrb5165-rb5.jinja2 b/test/device_dicts/qrb5165-rb5.jinja2
similarity index 95%
rename from tuxlava/devices/laa_configs/qrb5165-rb5.jinja2
rename to test/device_dicts/qrb5165-rb5.jinja2
index 6372400a94c1..b65b1e6be9a8 100644
--- a/tuxlava/devices/laa_configs/qrb5165-rb5.jinja2
+++ b/test/device_dicts/qrb5165-rb5.jinja2
@@ -1,4 +1,4 @@
-{# LAA config for Qualcomm RB5 qrb5165-rb5 (fastboot) #}
+{# device-dict config for Qualcomm RB5 qrb5165-rb5 (fastboot) #}
 
 {% set boot_method = 'fastboot' %}
 
diff --git a/tuxlava/devices/laa_configs/s32g399a-rdb3.jinja2 b/test/device_dicts/s32g399a-rdb3.jinja2
similarity index 82%
rename from tuxlava/devices/laa_configs/s32g399a-rdb3.jinja2
rename to test/device_dicts/s32g399a-rdb3.jinja2
index 045514215c81..604560109be4 100644
--- a/tuxlava/devices/laa_configs/s32g399a-rdb3.jinja2
+++ b/test/device_dicts/s32g399a-rdb3.jinja2
@@ -1,4 +1,4 @@
-{# LAA config for NXP S32G399A RDB3 (NFS boot with U-Boot) #}
+{# device-dict config for NXP S32G399A RDB3 (NFS boot with U-Boot) #}
 
 {% set boot_method = 'u-boot' %}
 
diff --git a/tuxlava/__main__.py b/tuxlava/__main__.py
index 3dd10a0f5665..ecb12b8a9c9c 100644
--- a/tuxlava/__main__.py
+++ b/tuxlava/__main__.py
@@ -82,9 +82,13 @@ def main() -> int:
             job_definition=options.job_definition,
             shared=options.shared,
             visibility=options.visibility,
+            device_dict=options.device_dict,
         )
         job.initialize()
-        sys.stdout.write(job.render())
+        if options.device_dict:
+            sys.stdout.write(job.device.device_dict({}, job.d_dict_config))
+        else:
+            sys.stdout.write(job.render())
     except TuxLavaException as exc:
         parser.error(str(exc))
     except Exception as exc:
diff --git a/tuxlava/argparse.py b/tuxlava/argparse.py
index 6c23a7c6d196..12cf8da1728e 100644
--- a/tuxlava/argparse.py
+++ b/tuxlava/argparse.py
@@ -28,8 +28,6 @@ def filter_options(options):
         "device",
         "device_dict",
         "extra_assets",
-        "laa",
-        "laa_config",
         "lava_definition",
         "qemu_binary",
         "qemu_image",
@@ -352,6 +350,13 @@ def setup_parser() -> argparse.ArgumentParser:
         action="store_true",
         help="Enable network",
     )
+    group.add_argument(
+        "--device-dict",
+        default=None,
+        type=Path,
+        metavar="PATH",
+        help="Device dictionary using the specified Jinja2 template (download by the user)",
+    )
 
     group = parser.add_argument_group("runtime")
     group.add_argument(
diff --git a/tuxlava/devices/__init__.py b/tuxlava/devices/__init__.py
index 49607c90a3c2..8bf1c6d208c0 100644
--- a/tuxlava/devices/__init__.py
+++ b/tuxlava/devices/__init__.py
@@ -6,111 +6,10 @@
 #
 # SPDX-License-Identifier: MIT
 
-from pathlib import Path
 from typing import Any, Dict, List, Optional
 
-from jinja2 import Environment, FileSystemLoader, nodes
-
 from tuxlava.exceptions import InvalidArgument
-
-# Directory containing LAA device config Jinja2 files
-LAA_CONFIGS_DIR = Path(__file__).parent / "laa_configs"
-
-# Jinja2 environment for LAA configs
-_laa_env = None
-
-
-def _get_laa_env():
-    """Lazy initialization of Jinja2 environment."""
-    global _laa_env
-    if _laa_env is None and LAA_CONFIGS_DIR.exists():
-        _laa_env = Environment(loader=FileSystemLoader(LAA_CONFIGS_DIR))
-    return _laa_env
-
-
-def _eval_jinja2_node(node: nodes.Node) -> Any:
-    """Evaluate a Jinja2 AST node to a Python value."""
-    if isinstance(node, nodes.Const):
-        return node.value
-    elif isinstance(node, nodes.List):
-        return [_eval_jinja2_node(item) for item in node.items]
-    elif isinstance(node, nodes.Dict):
-        return {
-            _eval_jinja2_node(pair.key): _eval_jinja2_node(pair.value)
-            for pair in node.items
-        }
-    elif isinstance(node, nodes.Tuple):
-        return tuple(_eval_jinja2_node(item) for item in node.items)
-    elif isinstance(node, nodes.Neg):
-        inner = _eval_jinja2_node(node.node)
-        return -inner if inner is not None else None
-    elif isinstance(node, nodes.Name):
-        if node.name in ("True", "true"):
-            return True
-        elif node.name in ("False", "false"):
-            return False
-        elif node.name in ("None", "none"):
-            return None
-        return None
-    else:
-        return None
-
-
-def get_laa_config(device_name: str) -> Optional[Dict[str, Any]]:
-    """Load LAA config by extracting {% set %} variables from a Jinja2 template.
-
-    Args:
-        device_name: Full device name (e.g., 'nfs-orion-o6', 'fastboot-gs101-oriole')
-
-    Returns:
-        Dict containing LAA config variables, or None if no config exists
-    """
-    env = _get_laa_env()
-    if env is None:
-        return None
-
-    # Strip common device prefixes to get base hardware name
-    for prefix in ["nfs-", "fastboot-", "qemu-", "flasher-"]:
-        if device_name.startswith(prefix):
-            base_name = device_name[len(prefix):]
-            break
-    else:
-        base_name = device_name
-
-    config_path = LAA_CONFIGS_DIR / f"{base_name}.jinja2"
-    if not config_path.exists():
-        return None
-
-    # Parse the template and extract {% set %} assignments
-    source = config_path.read_text()
-    ast = env.parse(source)
-
-    config: Dict[str, Any] = {}
-    for node in ast.body:
-        if isinstance(node, nodes.Assign):
-            if isinstance(node.target, nodes.Name):
-                var_name = node.target.name
-                value = _eval_jinja2_node(node.node)
-                if value is not None:
-                    config[var_name] = value
-
-    return config
-
-
-def list_laa_supported_devices() -> List[str]:
-    """List all device names that have LAA configs.
-
-    Returns:
-        Sorted list of device names (e.g., ['nfs-orion-o6', 'fastboot-gs101-oriole'])
-    """
-    # Import here to avoid circular import
-    from tuxlava.devices import Device
-
-    laa_devices = []
-    for device_cls in Device.list():
-        if get_laa_config(device_cls.name) is not None:
-            laa_devices.append(device_cls.name)
-    return sorted(laa_devices)
+from tuxlava import templates
 
 
 def subclasses(cls):
@@ -155,18 +54,41 @@ class Device:
         raise NotImplementedError  # pragma: no cover
 
     def device_dict(
-        self, context: Dict, laa_config: Optional[Dict[str, Any]] = None
+        self, context: Dict, d_dict_config: Optional[Dict[str, Any]] = None
     ) -> str:
         """This will be used by tuxrun in order to supply the device dictionary
         for all virtual devices that will be run via lava worker in tuxrun.
 
         Args:
             context: LAVA context variables
-            laa_config: Optional LAA device config. When provided, generates
-                        LAA-specific device dictionary with laacli commands.
+            d_dict_config: Optional device dict config. When provided, generates
+                           device dictionary with power/serial commands.
         """
         raise NotImplementedError  # pragma: no cover
 
+    def _render_device_dict(
+        self,
+        template_name: str,
+        context: Dict[str, Any],
+        d_dict_config: Optional[Dict[str, Any]] = None,
+        d_dict_defaults: Optional[Dict[str, str]] = None
+    ) -> str:
+        if hasattr(self, 'test_character_delay') and self.test_character_delay:
+            context["test_character_delay"] = self.test_character_delay
+
+        if not d_dict_config:
+            return templates.devices().get_template(template_name).render(**context)
+
+        d_dict_context = context.copy()
+        d_dict_context["d_dict_mode"] = True
+        d_dict_context.update(d_dict_config)
+
+        if d_dict_defaults:
+            for key, default_value in d_dict_defaults.items():
+                d_dict_context.setdefault(key, default_value)
+
+        return templates.devices().get_template(template_name).render(**d_dict_context)
+
     def extra_assets(self, tmpdir, **kwargs) -> List[str]:
         return []
 
diff --git a/tuxlava/devices/avh.py b/tuxlava/devices/avh.py
index 55af7f730d0d..b801dba44b47 100644
--- a/tuxlava/devices/avh.py
+++ b/tuxlava/devices/avh.py
@@ -126,9 +126,9 @@ class AvhDevice(Device):
         ) + "".join(tests)
 
     def device_dict(
-        self, context: Dict[str, Any], laa_config: Optional[Dict[str, Any]] = None
+        self, context: Dict[str, Any], d_dict_config: Optional[Dict[str, Any]] = None
     ) -> str:
-        # AVH devices don't use LAA, but accept the parameter for interface consistency
+        # AVH devices don't use device-dict, but accept the parameter for interface consistency
         if self.test_character_delay:
             context["test_character_delay"] = self.test_character_delay
         return templates.devices().get_template("avh.yaml.jinja2").render(**context)
diff --git a/tuxlava/devices/fastboot.py b/tuxlava/devices/fastboot.py
index 9ef9344f49fc..67b5d50b0e15 100644
--- a/tuxlava/devices/fastboot.py
+++ b/tuxlava/devices/fastboot.py
@@ -151,44 +151,25 @@ class FastbootDevice(Device):
         )
 
     def device_dict(
-        self, context: Dict[str, Any], laa_config: Optional[Dict[str, Any]] = None
+        self, context: Dict[str, Any], d_dict_config: Optional[Dict[str, Any]] = None
     ) -> str:
         """Generate device dictionary.
 
         Args:
             context: LAVA context variables
-            laa_config: Optional LAA device config. When provided, generates
-                        LAA-specific device dictionary with laacli commands.
+            d_dict_config: Optional device dict config. When provided, generates
+                           device dictionary with power/serial commands.
 
         Returns:
             Rendered device dictionary YAML string
         """
-        if self.test_character_delay:
-            context["test_character_delay"] = self.test_character_delay
+        template_name = "fastboot-device-dict.yaml.jinja2" if d_dict_config else "fastboot-standard.yaml.jinja2"
 
-        if not laa_config:
-            # Standard device dictionary (for LAVA farm)
-            return templates.devices().get_template("fastboot.yaml.jinja2").render(
-                **context
-            )
-
-        # LAA device dictionary - merge config into context
-        # The laa_config comes from Jinja2 template with {% set var = value %}
-        # Variables are extracted as flat dict: connection_command, hard_reset_command, etc.
-        laa_context = context.copy()
-        laa_context["laa_mode"] = True
-
-        # Merge all LAA config variables directly into context
-        # This includes: connection_command, hard_reset_command, power_on_command,
-        # power_off_command, pre_power_command, pre_os_command, fastboot_serial_number, etc.
-        laa_context.update(laa_config)
-
-        # Set defaults for required fields if not in config
-        if "connection_command" not in laa_context:
-            laa_context["connection_command"] = "telnet localhost 2000"
-
-        return templates.devices().get_template("fastboot.yaml.jinja2").render(
-            **laa_context
+        return self._render_device_dict(
+            template_name,
+            context,
+            d_dict_config,
+            d_dict_defaults={"connection_command": "telnet localhost 2000"}
         )
 
 
diff --git a/tuxlava/devices/fvp.py b/tuxlava/devices/fvp.py
index 66193c64a10a..f1d9cd2754ab 100644
--- a/tuxlava/devices/fvp.py
+++ b/tuxlava/devices/fvp.py
@@ -23,9 +23,9 @@ class FVPDevice(Device):
     deploy_timeout = 5
 
     def device_dict(
-        self, context: Dict[str, Any], laa_config: Optional[Dict[str, Any]] = None
+        self, context: Dict[str, Any], d_dict_config: Optional[Dict[str, Any]] = None
     ) -> str:
-        # FVP devices don't use LAA, but accept the parameter for interface consistency
+        # FVP devices don't use device-dict, but accept the parameter for interface consistency
         return templates.devices().get_template("fvp.yaml.jinja2").render(**context)
 
 
diff --git a/tuxlava/devices/nfs.py b/tuxlava/devices/nfs.py
index 896f852392ed..46f01a10a88d 100644
--- a/tuxlava/devices/nfs.py
+++ b/tuxlava/devices/nfs.py
@@ -135,43 +135,29 @@ class NfsDevice(Device):
         ) + "".join(tests)
 
     def device_dict(
-        self, context: Dict[str, Any], laa_config: Optional[Dict[str, Any]] = None
+        self, context: Dict[str, Any], d_dict_config: Optional[Dict[str, Any]] = None
     ) -> str:
         """Generate device dictionary.
 
         Args:
             context: LAVA context variables
-            laa_config: Optional LAA device config. When provided, generates
-                        LAA-specific device dictionary with laacli commands.
+            d_dict_config: Optional device dict config. When provided, generates
+                           device dictionary with power/serial commands.
 
         Returns:
             Rendered device dictionary YAML string
         """
-        if self.test_character_delay:
-            context["test_character_delay"] = self.test_character_delay
-
-        if not laa_config:
-            # Standard device dictionary (for LAVA farm)
-            return templates.devices().get_template("nfs.yaml.jinja2").render(**context)
-
-        # LAA device dictionary - merge config into context
-        # The laa_config comes from Jinja2 template with {% set var = value %}
-        # Variables are extracted as flat dict: connection_command, hard_reset_command, etc.
-        laa_context = context.copy()
-        laa_context["laa_mode"] = True
-
-        # Merge all LAA config variables directly into context
-        # This includes: connection_command, hard_reset_command, power_on_command,
-        # power_off_command, boot_method, pre_power_command, pre_os_command, etc.
-        laa_context.update(laa_config)
-
-        # Set defaults for required fields if not in config
-        if "connection_command" not in laa_context:
-            laa_context["connection_command"] = "telnet localhost 2000"
-        if "boot_method" not in laa_context:
-            laa_context["boot_method"] = "u-boot"
-
-        return templates.devices().get_template("nfs.yaml.jinja2").render(**laa_context)
+        template_name = "nfs-device-dict.yaml.jinja2" if d_dict_config else "nfs-standard.yaml.jinja2"
+
+        return self._render_device_dict(
+            template_name,
+            context,
+            d_dict_config,
+            d_dict_defaults={
+                "connection_command": "telnet localhost 2000",
+                "boot_method": "u-boot"
+            }
+        )
 
 
 class NfsJunoR2(NfsDevice):
diff --git a/tuxlava/devices/qemu.py b/tuxlava/devices/qemu.py
index 03af0b55ddf6..8fdc1bc87b60 100644
--- a/tuxlava/devices/qemu.py
+++ b/tuxlava/devices/qemu.py
@@ -150,9 +150,9 @@ class QemuDevice(Device):
         ) + "".join(tests)
 
     def device_dict(
-        self, context: Dict[str, Any], laa_config: Optional[Dict[str, Any]] = None
+        self, context: Dict[str, Any], d_dict_config: Optional[Dict[str, Any]] = None
     ) -> str:
-        # QEMU devices don't use LAA, but accept the parameter for interface consistency
+        # QEMU devices don't use device-dict, but accept the parameter for interface consistency
         if self.test_character_delay:
             context["test_character_delay"] = self.test_character_delay
         return templates.devices().get_template("qemu.yaml.jinja2").render(**context)
diff --git a/tuxlava/devices/ssh.py b/tuxlava/devices/ssh.py
index 0bc4b630aea1..29c1afe35a8f 100644
--- a/tuxlava/devices/ssh.py
+++ b/tuxlava/devices/ssh.py
@@ -82,9 +82,9 @@ class SSHDevice(Device):
         ) + "".join(tests)
 
     def device_dict(
-        self, context: Dict[str, Any], laa_config: Optional[Dict[str, Any]] = None
+        self, context: Dict[str, Any], d_dict_config: Optional[Dict[str, Any]] = None
     ) -> str:
-        # SSH devices don't use LAA, but accept the parameter for interface consistency
+        # SSH devices don't use device-dict, but accept the parameter for interface consistency
         context["ssh_host"] = self.ssh_host
         context["ssh_user"] = self.ssh_user
         context["ssh_port"] = self.ssh_port
diff --git a/tuxlava/jobs.py b/tuxlava/jobs.py
index fdda0bc7470c..73485fe2dea8 100644
--- a/tuxlava/jobs.py
+++ b/tuxlava/jobs.py
@@ -16,7 +16,7 @@ from pathlib import Path
 from typing import Dict, List, Any, Optional
 from tuxlava.argparse import filter_options
 from tuxlava.exceptions import InvalidArgument, MissingArgument, TuxLavaError
-from tuxlava.devices import Device, get_laa_config
+from tuxlava.devices import Device
 from tuxlava.tests import Test
 from tuxlava.tuxmake import TuxBuildBuild, TuxMakeBuild
 from tuxlava.utils import pathurlnone
@@ -87,11 +87,9 @@ class Job:
         tmpdir: Path = None,
         cache_dir: Path = None,
         visibility: str = "public",
-        laa: bool = False,
         device_dict: Path = None,
     ) -> None:
         self.device = device
-        self.laa = laa
         self.device_dict = device_dict
         self.bios = bios
         self.bl1 = bl1
@@ -229,26 +227,16 @@ class Job:
         self.device.validate(**filter_options(self))
         self.device.default(self)
 
-        # Load LAA config if running on LAA appliance
-        self.laa_config: Optional[Dict[str, Any]] = None
-        if self.laa:
-            if self.device_dict:
-                # Load from external Jinja2 file (provided by kci-runner/baklaweb)
-                env = Environment(loader=FileSystemLoader(self.device_dict.parent))
-                template = env.get_template(self.device_dict.name)
-                module = template.module
-                self.laa_config = {}
-                for name in dir(module):
-                    if not name.startswith("_"):
-                        self.laa_config[name] = getattr(module, name)
-            else:
-                # Fall back to embedded configs (temporary stopgap)
-                self.laa_config = get_laa_config(self.device.name)
-                if self.laa_config is None:
-                    raise InvalidArgument(
-                        f"No LAA configuration found for device '{self.device.name}'. "
-                        f"Provide --device-dict or ensure a config exists at laa_configs/<device>.jinja2"
-                    )
+        # Load device dict config if device-dict is provided
+        self.d_dict_config: Optional[Dict[str, Any]] = None
+        if self.device_dict:
+            env = Environment(loader=FileSystemLoader(self.device_dict.parent))
+            template = env.get_template(self.device_dict.name)
+            module = template.module
+            self.d_dict_config = {}
+            for name in dir(module):
+                if not name.startswith("_"):
+                    self.d_dict_config[name] = getattr(module, name)
 
         if self.shared is not None and not self.device.name.startswith("qemu-"):
             raise InvalidArgument("--shared options is only available for qemu devices")
diff --git a/tuxlava/templates/devices/fastboot-base.yaml.jinja2 b/tuxlava/templates/devices/fastboot-base.yaml.jinja2
new file mode 100644
index 000000000000..4a02ecc60282
--- /dev/null
+++ b/tuxlava/templates/devices/fastboot-base.yaml.jinja2
@@ -0,0 +1,52 @@
+{# Base template for Fastboot devices - shared blocks #}
+{% extends 'base.yaml.jinja2' %}
+
+{% block vland %}
+{# disabled #}
+{% endblock vland %}
+
+{% block body %}
+
+{% block available_architectures %}
+{# Override in child templates #}
+{% endblock available_architectures %}
+
+{% block actions_deploy %}
+actions:
+  deploy:
+    connections:
+      serial:
+{% block deploy_connections_extra %}
+      lxc:
+{% endblock deploy_connections_extra %}
+    methods:
+{% block deploy_methods %}
+      nfs:
+      lxc:
+{% endblock deploy_methods %}
+{% endblock actions_deploy %}
+
+{% block actions_boot %}
+  boot:
+    connections:
+      serial:
+{% block boot_connections_extra %}
+      ssh:
+      lxc:
+{% endblock boot_connections_extra %}
+{% block boot_methods %}
+    method: fastboot
+
+          options:
+{% endblock boot_methods %}
+{% endblock actions_boot %}
+
+{% block device_dict_config %}
+{# device-dict config - empty for standard #}
+{% endblock device_dict_config %}
+
+{% endblock body %}
+
+{% block commands %}
+{# Device commands - override in device-dict template #}
+{% endblock commands %}
diff --git a/tuxlava/templates/devices/fastboot.yaml.jinja2 b/tuxlava/templates/devices/fastboot-device-dict.yaml.jinja2
similarity index 60%
rename from tuxlava/templates/devices/fastboot.yaml.jinja2
rename to tuxlava/templates/devices/fastboot-device-dict.yaml.jinja2
index 177ed7e45ebb..3a3e6b688193 100644
--- a/tuxlava/templates/devices/fastboot.yaml.jinja2
+++ b/tuxlava/templates/devices/fastboot-device-dict.yaml.jinja2
@@ -1,57 +1,33 @@
-{# device_type: fastboot based #}
-{% extends 'base.yaml.jinja2' %}
-
-{% block vland %}
-{# disabled #}
-{% endblock vland %}
-
-{% block body %}
+{# Fastboot device with device-dict config #}
+{% extends 'fastboot-base.yaml.jinja2' %}
 
+{% block available_architectures %}
 available_architectures:
-{% if laa_mode %}
 - arm64
 - arm
-{% else %}
-- dragonboard-410c
-- dragonboard-845c
-- e850-96
-- hi6220-hikey
-- x15
-- qrb5165-rb5
-{% endif %}
+{% endblock available_architectures %}
 
-actions:
-  deploy:
-    connections:
-      serial:
-      lxc:
-    methods:
-{% if laa_mode %}
+{% block deploy_connections_extra %}
+{# device-dict mode doesn't use LXC for deploy #}
+{% endblock deploy_connections_extra %}
+
+{% block deploy_methods %}
       fastboot:
       lxc:
-{% else %}
-      nfs:
-      lxc:
-{% endif %}
+{% endblock deploy_methods %}
 
-  boot:
-    connections:
-      serial:
-{% if not laa_mode %}
-      ssh:
-{% endif %}
-      lxc:
-    method{% if laa_mode %}s{% endif %}:
-{% if laa_mode %}
+{% block boot_connections_extra %}
+{# device-dict mode only uses serial, not SSH #}
+{% endblock boot_connections_extra %}
+
+{% block boot_methods %}
+    methods:
       fastboot:
       minimal:
-{% else %}
- fastboot
+{% endblock boot_methods %}
 
-          options:
-{% endif %}
+{% block device_dict_config %}
 
-{% if laa_mode %}
 {% if environment %}
 environment: {{ environment }}
 {% endif %}
@@ -67,12 +43,10 @@ docker_shell_extra_arguments:
   - '--volume=/usr/bin/laacli:/usr/bin/laacli:ro'
   - '--volume=/usr/bin/lsibcli:/usr/bin/lsibcli:ro'
   - '--volume=/run/dbus/system_bus_socket:/run/dbus/system_bus_socket:rw'
-{% endif %}
-
-{% endblock body %}
+{% endblock device_dict_config %}
 
-{% if laa_mode %}
 {% block commands %}
+
 commands:
 {% if connection_command %}
     connect: {{ connection_command }}
@@ -87,4 +61,3 @@ commands:
     power_on: {{ power_on_command }}
 {% endif %}
 {% endblock commands %}
-{% endif %}
diff --git a/tuxlava/templates/devices/fastboot-standard.yaml.jinja2 b/tuxlava/templates/devices/fastboot-standard.yaml.jinja2
new file mode 100644
index 000000000000..c62e598c22e2
--- /dev/null
+++ b/tuxlava/templates/devices/fastboot-standard.yaml.jinja2
@@ -0,0 +1,12 @@
+{# Standard Fastboot device (non-device-dict, for LAVA) #}
+{% extends 'fastboot-base.yaml.jinja2' %}
+
+{% block available_architectures %}
+available_architectures:
+- dragonboard-410c
+- dragonboard-845c
+- e850-96
+- hi6220-hikey
+- x15
+- qrb5165-rb5
+{% endblock available_architectures %}
diff --git a/tuxlava/templates/devices/nfs-base.yaml.jinja2 b/tuxlava/templates/devices/nfs-base.yaml.jinja2
new file mode 100644
index 000000000000..12b85c463649
--- /dev/null
+++ b/tuxlava/templates/devices/nfs-base.yaml.jinja2
@@ -0,0 +1,57 @@
+{# Base template for NFS devices - shared blocks #}
+{% extends 'base.yaml.jinja2' %}
+
+{% block vland %}
+{# disabled #}
+{% endblock vland %}
+
+{% block body %}
+
+{% block available_architectures %}
+{# Override in child templates #}
+{% endblock available_architectures %}
+
+{% block actions_deploy %}
+actions:
+  deploy:
+    connections:
+      serial:
+{% block deploy_connections_extra %}
+      lxc:
+{% endblock deploy_connections_extra %}
+    methods:
+{% block deploy_methods %}
+      nfs:
+      lxc:
+{% endblock deploy_methods %}
+{% endblock actions_deploy %}
+
+{% block actions_boot %}
+  boot:
+    connections:
+      serial:
+{% block boot_connections_extra %}
+      ssh:
+      lxc:
+{% endblock boot_connections_extra %}
+{% block boot_methods %}
+    method: ipxe
+    commands: nfs
+
+          options:
+{% endblock boot_methods %}
+{% endblock actions_boot %}
+
+{% block device_dict_docker_config %}
+{# device-dict Docker config - empty for standard #}
+{% endblock device_dict_docker_config %}
+
+{% block device_dict_parameters %}
+{# device-dict parameters - empty for standard #}
+{% endblock device_dict_parameters %}
+
+{% endblock body %}
+
+{% block commands %}
+{# Device commands - override in device-dict template #}
+{% endblock commands %}
diff --git a/tuxlava/templates/devices/nfs.yaml.jinja2 b/tuxlava/templates/devices/nfs-device-dict.yaml.jinja2
similarity index 86%
rename from tuxlava/templates/devices/nfs.yaml.jinja2
rename to tuxlava/templates/devices/nfs-device-dict.yaml.jinja2
index 0a7cee9dba98..52d8b733c34a 100644
--- a/tuxlava/templates/devices/nfs.yaml.jinja2
+++ b/tuxlava/templates/devices/nfs-device-dict.yaml.jinja2
@@ -1,32 +1,17 @@
-{# device_type: nfs based #}
-{% extends 'base.yaml.jinja2' %}
-
-{% block vland %}
-{# disabled #}
-{% endblock vland %}
-
-{% block body %}
+{# NFS device with device-dict config #}
+{% extends 'nfs-base.yaml.jinja2' %}
 
+{% block available_architectures %}
 available_architectures:
-{% if laa_mode %}
 - arm64
 - arm
-{% else %}
-- bcm2711-rpi-4-b
-- juno-r2
-- x86_64
-- i386
-{% endif %}
+{% endblock available_architectures %}
 
-actions:
-  deploy:
-    connections:
-      serial:
-{% if not laa_mode %}
-      lxc:
-{% endif %}
-    methods:
-{% if laa_mode %}
+{% block deploy_connections_extra %}
+{# device-dict mode doesn't use LXC #}
+{% endblock deploy_connections_extra %}
+
+{% block deploy_methods %}
 {% if deploy_method|default('tftp') == 'tftp' %}
       tftp:
       nfs:
@@ -41,19 +26,13 @@ actions:
           - {{ cmd }}
 {% endfor %}
 {% endif %}
-{% else %}
-      nfs:
-      lxc:
-{% endif %}
+{% endblock deploy_methods %}
 
-  boot:
-    connections:
-      serial:
-{% if not laa_mode %}
-      ssh:
-      lxc:
-{% endif %}
-{% if laa_mode %}
+{% block boot_connections_extra %}
+{# device-dict mode only uses serial #}
+{% endblock boot_connections_extra %}
+
+{% block boot_methods %}
     methods:
 {% if boot_method == 'grub' %}
       grub:
@@ -122,14 +101,10 @@ actions:
           - '{BOOTX}'
       minimal:
 {% endif %}
-{% else %}
-    method: ipxe
-    commands: nfs
+{% endblock boot_methods %}
 
-          options:
-{% endif %}
+{% block device_dict_docker_config %}
 
-{% if laa_mode %}
 {% if environment %}
 environment: {{ environment }}
 {% endif %}
@@ -139,6 +114,9 @@ docker_shell_extra_arguments:
   - '--volume=/usr/bin/laacli:/usr/bin/laacli:ro'
   - '--volume=/usr/bin/lsibcli:/usr/bin/lsibcli:ro'
   - '--volume=/run/dbus/system_bus_socket:/run/dbus/system_bus_socket:rw'
+{% endblock device_dict_docker_config %}
+
+{% block device_dict_parameters %}
 
 {% if booti_kernel_addr %}
 parameters:
@@ -151,12 +129,10 @@ parameters:
     ramdisk: '{{ booti_ramdisk_addr }}'
     dtb: '{{ booti_dtb_addr }}'
 {% endif %}
-{% endif %}
-
-{% endblock body %}
+{% endblock device_dict_parameters %}
 
-{% if laa_mode %}
 {% block commands %}
+
 commands:
 {% if connection_command %}
     connect: {{ connection_command }}
@@ -176,4 +152,3 @@ commands:
         enable: {{ usbg_ms_commands.enable }}
 {% endif %}
 {% endblock commands %}
-{% endif %}
diff --git a/tuxlava/templates/devices/nfs-standard.yaml.jinja2 b/tuxlava/templates/devices/nfs-standard.yaml.jinja2
new file mode 100644
index 000000000000..166b863f3f99
--- /dev/null
+++ b/tuxlava/templates/devices/nfs-standard.yaml.jinja2
@@ -0,0 +1,10 @@
+{# Standard NFS device (non-device-dict, for LAVA) #}
+{% extends 'nfs-base.yaml.jinja2' %}
+
+{% block available_architectures %}
+available_architectures:
+- bcm2711-rpi-4-b
+- juno-r2
+- x86_64
+- i386
+{% endblock available_architectures %}
-- 
2.51.0

