From 959f432b0c805c6de62fc09e9ad6ad7362a6ee03 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Thu, 25 Jun 2026 16:14:53 +1000 Subject: [PATCH 01/15] (wip) Timer and i2c acacia adaptation Signed-off-by: Lesley Rossouw Timer tests out with acacia Signed-off-by: Lesley Rossouw Serial tests out with Acacia Signed-off-by: Lesley Rossouw Refactor: acacia_sddf is now a python module that inherits all subfiles, sDDF itself is a python module for import! Needed for SDK Signed-off-by: Lesley Rossouw Fix issues with i2c.py - maps were swapped. Signed-off-by: Lesley Rossouw Further fixes to keep up with Acacia PR request changes Signed-off-by: Lesley Rossouw --- acacia_sddf/__init__.py | 5 + acacia_sddf/board.py | 183 +++++++++++++ acacia_sddf/driver_manifest.py | 79 ++++++ acacia_sddf/i2c.py | 225 ++++++++++++++++ acacia_sddf/sddf.py | 192 ++++++++++++++ acacia_sddf/serial.py | 459 +++++++++++++++++++++++++++++++++ acacia_sddf/timer.py | 178 +++++++++++++ examples/i2c/i2c.mk | 20 +- examples/i2c/meta.py | 111 +++----- examples/serial/meta.py | 92 ++----- examples/serial/serial.mk | 10 +- examples/timer/meta.py | 60 ++--- examples/timer/timer.mk | 2 +- tools/meta/board.py | 211 --------------- 14 files changed, 1430 insertions(+), 397 deletions(-) create mode 100644 acacia_sddf/__init__.py create mode 100644 acacia_sddf/board.py create mode 100644 acacia_sddf/driver_manifest.py create mode 100644 acacia_sddf/i2c.py create mode 100644 acacia_sddf/sddf.py create mode 100644 acacia_sddf/serial.py create mode 100644 acacia_sddf/timer.py delete mode 100644 tools/meta/board.py diff --git a/acacia_sddf/__init__.py b/acacia_sddf/__init__.py new file mode 100644 index 000000000..eae88ef7a --- /dev/null +++ b/acacia_sddf/__init__.py @@ -0,0 +1,5 @@ +from .i2c import sDDFI2C +from .timer import sDDFTimer +from .serial import sDDFSerial +from .sddf import sDDFDriverClass, sDDFDriverConfig, sDDFDriverManifest +from .board import BOARDS, Board diff --git a/acacia_sddf/board.py b/acacia_sddf/board.py new file mode 100644 index 000000000..f9814bde4 --- /dev/null +++ b/acacia_sddf/board.py @@ -0,0 +1,183 @@ +# Copyright 2025, UNSW +# SPDX-License-Identifier: BSD-2-Clause +from dataclasses import dataclass +from typing import List, Optional, Tuple +from acacia import System, ProtectionDomain, aarch64, riscv64, x86_64, Arch +from importlib.metadata import version + +@dataclass(frozen=True) +class DriverDouble: + compatible: str + node_path: str + +@dataclass +class Board: + name: str + arch: Arch + paddr_top: int + # Driver mappings -> (compatible, preferred_node) tuples + serial: Optional[DriverDouble] = DriverDouble(None, None) + ethernet: Optional[DriverDouble] = DriverDouble(None, None) + timer: Optional[DriverDouble] = DriverDouble(None, None) + i2c: Optional[DriverDouble] = DriverDouble(None, None) + blk: Optional[DriverDouble] = DriverDouble(None, None) + partition: int = 0 + baud_rate: Optional[int] = None + + +# Keep this list in alphabetical order by board name +# TODO: convert to Dictionary +BOARDS: List[Board] = [ + Board( + name="cheshire", + arch=riscv64, + paddr_top=0x90000000, + serial=DriverDouble("ns16550a", "soc/serial@3002000"), + i2c=DriverDouble("eth,i2c", "soc/i2c@3003000"), + ), + Board( + name="hifive_p550", + arch=riscv64, + paddr_top=0xA0000000, + serial=DriverDouble("snps,dw-apb-uart", "soc/serial@0x50900000"), + ), + Board( + name="imx8mm_evk", + arch=aarch64, + paddr_top=0x70000000, + serial=DriverDouble("fsl,imx8mm-uart", "soc@0/bus@30800000/spba-bus@30800000/serial@30890000"), + timer=DriverDouble("fsl,imx8mm-gpt", "soc@0/bus@30000000/timer@302d0000"), + ethernet=DriverDouble("", "soc@0/bus@30800000/ethernet@30be0000"), + ), + Board( + name="imx8mp_evk", + arch=aarch64, + paddr_top=0x70000000, + serial=DriverDouble("fsl,imx8mp-uart", "soc@0/bus@30800000/spba-bus@30800000/serial@30890000"), + timer=DriverDouble("fsl,imx8mp-gpt", "soc@0/bus@30000000/timer@302d0000"), + ethernet=DriverDouble("", "soc@0/bus@30800000/ethernet@30bf0000"), + ), + Board( + name="imx8mp_iotgate", + arch=aarch64, + paddr_top=0x70000000, + serial=DriverDouble("fsl,imx8mp-uart", "soc@0/bus@30800000/serial@30890000"), + timer=DriverDouble("fsl,imx8mp-gpt", "soc@0/bus@30000000/timer@302d0000"), + ethernet=DriverDouble("", "soc@0/bus@30800000/ethernet@30bf0000"), + ), + Board( + name="imx8mq_evk", + arch=aarch64, + paddr_top=0x70000000, + serial=DriverDouble("fsl,imx8mq-uart", "soc@0/bus@30800000/serial@30860000"), + timer=DriverDouble("fsl,imx8mq-gpt", "soc@0/bus@30000000/timer@302d0000"), + ethernet=DriverDouble("", "soc@0/bus@30800000/ethernet@30be0000"), + ), + Board( + name="kria_k26", + arch=aarch64, + paddr_top=0x70000000, + timer=DriverDouble("cdns,ttc", "axi/timer@ff140000"), + serial=DriverDouble("xlnx,zynqmp-uart", "axi/serial@ff010000"), + ), + Board( + name="maaxboard", + arch=aarch64, + paddr_top=0x70000000, + serial=DriverDouble("fsl,imx8mq-uart", "soc@0/bus@30800000/serial@30860000"), + timer=DriverDouble("fsl,imx8mq-gpt", "soc@0/bus@30000000/timer@302d0000"), + ethernet=DriverDouble("", "soc@0/bus@30800000/ethernet@30be0000"), + blk=DriverDouble("", "soc@0/bus@30800000/mmc@30b40000"), + partition=2, + ), + Board( + name="odroidc2", + arch=aarch64, + paddr_top=0x60000000, + serial=DriverDouble("amlogic,meson-gx-uart", "soc/bus@c8100000/serial@4c0"), + timer=DriverDouble("amlogic,meson-gxbb-wdt", "soc/bus@c1100000/watchdog@98d0"), + ethernet=DriverDouble("", "soc/ethernet@c9410000"), + baud_rate=115200 + ), + Board( + name="odroidc4", + arch=aarch64, + paddr_top=0x60000000, + i2c=DriverDouble("amlogic,meson-axg-i2c", "soc/bus@ffd00000/i2c@1d000"), + serial=DriverDouble("amlogic,meson-gx-uart", "soc/bus@ff800000/serial@3000"), + timer=DriverDouble("amlogic,meson-gxbb-wdt", "soc/bus@ffd00000/watchdog@f0d0"), + ethernet=DriverDouble("amlogic,meson-gx-uart", "soc/ethernet@ff3f0000"), + baud_rate=115200 + ), + Board( + name="qemu_virt_aarch64", + arch=aarch64, + paddr_top=0x6_0000_000, + serial=DriverDouble("arm,pl011", "pl011@9000000"), + timer=DriverDouble("arm,armv8-timer", "timer"), + blk=DriverDouble("", "virtio_mmio@a000200"), + ethernet=DriverDouble("", "virtio_mmio@a000000"), + i2c=None, + ), + Board( + name="qemu_virt_riscv64", + arch=riscv64, + paddr_top=0xA_0000_000, + serial=DriverDouble("ns16550a", "soc/serial@10000000"), + timer=DriverDouble("google,goldfish-rtc", "soc/rtc@101000"), + ethernet=DriverDouble("", "soc/virtio_mmio@10001000"), + blk=DriverDouble("", "soc/virtio_mmio@10002000"), + partition=0, + i2c=None, + ), + Board( + name="rock3b", + arch=aarch64, + paddr_top=0xEC000000, + serial=DriverDouble("snps,dw-apb-uart", "serial@fe660000"), + timer=DriverDouble("rockchip,rk3568-timer", "rktimer@fe5f0000"), + ethernet=DriverDouble("", "ethernet@fe2a0000"), + baud_rate=1500000, + ), + Board( + name="rpi4b_1gb", + arch=aarch64, + paddr_top=0x2_000_000, + serial=DriverDouble("brcm,bcm2835-aux-uart", "soc/serial@7e215040"), + timer=DriverDouble("brcm,bcm2835-system-timer", "soc/timer@7e003000"), + ethernet=DriverDouble("", "scb/ethernet@7d580000"), + ), + Board( + name="serengeti", + arch=riscv64, + paddr_top=0x90000000, + serial=DriverDouble("ns16550a", "soc/serial@3002000"), + i2c=DriverDouble("eth,i2c", "soc/i2c@3003000"), + timer=DriverDouble("pulp,apb_timer", "soc/timer@300B000"), + ), + Board( + name="star64", + arch=riscv64, + paddr_top=0x100000000, + serial=DriverDouble("starfive,jh7110-uart", "soc/serial@10000000"), + timer=DriverDouble("starfive,jh7110-timer", "soc/timer@13050000"), + ethernet=DriverDouble("", "soc/ethernet@16030000"), + ), + Board( + name="zcu102", + arch=aarch64, + paddr_top=0x80000000, + timer=DriverDouble("cdns,ttc", "axi/timer@ff140000"), + serial=DriverDouble("xlnx,zynqmp-uart", "axi/serial@ff000000"), + ), + Board( + name="x86_64_generic", + arch=x86_64, + paddr_top=0x7FFDF000, + ), + Board( + name="x86_64_generic_vtx", + arch=x86_64, + paddr_top=0x7FFDF000, + ), +] diff --git a/acacia_sddf/driver_manifest.py b/acacia_sddf/driver_manifest.py new file mode 100644 index 000000000..ee900612e --- /dev/null +++ b/acacia_sddf/driver_manifest.py @@ -0,0 +1,79 @@ +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +from dataclasses import dataclass +from typing import List, Dict, Type, Union, Optional +from collections import defaultdict + +@dataclass +class DTSRegion: + name: str + perms: str = None + size: int = None + dt_idx: int = None + +@dataclass +class DTSIRQ: + dt_index: int + +@dataclass +class sDDFDriverConfig: + """ + Encapsulation of device tree fields describing an instance + of a driver. + + WARNING: the order of regions and irqs affects the order they are + mapped into the driver in config structs! We REALLY shouldn't have + this be the case. This is a hangover from `config.json` and sdfgen. + + TODO: make this better in future + """ + compatible: Union[List[str], str] + regions: List[DTSRegion] + irqs: List[DTSIRQ] + def __post_init__(self): + if type(self.compatible) is str: + self.compatible = [self.compatible] + assert type(self.regions) is list + assert type(self.irqs) is list + + +class __sDDFDriverManifest: + """ + Wrapper class encapsulating sDDF driver manifest. This is a + mapping of driver subsystem type -> list of driver names -> + DTS fields. I.e. this encodes: + * Which drivers are compatible with what devices, according to the + device tree, + * What drivers are available in each driver class, + * Which driver subsystem types in sdfgen map to which drivers. + + You should NOT make a new instance of this class! Use the + `sDDFDriverManifest()` function to get the global instance. + """ + def __init__(self): + self.map: Dict[Type[sDDFDeviceClass], Dict[str, sDDFDriverConfig]] = defaultdict(dict) + + def add_driver_config(self, + subsystem_type: Type[sDDFDriverConfig], + driver_name: str, + config: sDDFDriverConfig): + # Refuse namespace collisions + if driver_name in self.map[subsystem_type]: + raise ValueError(f"Driver named {driver_name} already exists for " + f"{subsystem_type}!") + self.map[subsystem_type][driver_name] = config + + def __getitem__(self, item): + # Allow array syntax for indexing into dict of driver names per class type + return self.map[item] + + def get_configs_matching_compatible(self, subsystem_type: Type[sDDFDriverConfig], compat: str) -> List[sDDFDriverConfig]: + return [c for c in self.map[subsystem_type].values() if compat in c.compatible] + +module_manifest = __sDDFDriverManifest() + +def sDDFDriverManifest(): + return module_manifest + + diff --git a/acacia_sddf/i2c.py b/acacia_sddf/i2c.py new file mode 100644 index 000000000..9e7d174ab --- /dev/null +++ b/acacia_sddf/i2c.py @@ -0,0 +1,225 @@ +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +from acacia import System, Subsystem, ProtectionDomain, Channel, Map, MemoryRegion, DTBNode, DeviceTreeBlob, SchedulingProperties, ConfigStruct +import sys, os +from .driver_manifest import sDDFDriverManifest, sDDFDriverConfig, DTSIRQ, DTSRegion +from .sddf import sDDFDriverClass, DeviceResourcesFactory, RegionResourceFactory +from collections import defaultdict +from typing import List, Dict, Type, Union, Optional + +I2C_DATA_SZ = 0x1000 +I2C_NUM_BUFS = 128 # TODO: add support for dynamically sized queues +I2C_PROTOCOL_MAGIC = "sDDF" + chr(0x4) + +class sDDFI2C(sDDFDriverClass): + def __init__(self, + dev_compatible: str, + dev_dt_path: str, + sdf: System, + driver_prio: int, + virt_prio: int, + cpu:Optional[int]=None, + virt_elf: str="i2c_virt.elf", + driver_elf: str="i2c_driver.elf" + ): + super().__init__("i2c", dev_compatible, dev_dt_path, sdf, magic="sDDF"+chr(0x1)) + # assert driver_prio > virt_prio > 0 + self.cpu = cpu + + # Internal bookkeeping + self.driver = None + self.virt = None + + # ELF names. This is required because acacia is tragically detached + # from the build system itself and cannot guarantee the names of sDDF + # compiled objects itself. This will not be required once we start using + # the sDDF with an SDK model, but that is for the future. + self.virt_elf = virt_elf + self.driver = ProtectionDomain("i2c_driver", driver_elf, scheduling=SchedulingProperties(driver_prio), cpu=self.cpu) + + # We must make the driver BEFORE we get here + self.driver_dev_resources = self.create_dtb_resources(self.driver) + + # Stubs of config structs that we need to collect in construct_infrastructure and connect_clients + self.virt_config = None + self.driver_config = None + self.virt_driver_config = None + self.client_configs = [] + + # We create queues etc. AFTER setting up the device resources to ensure that IRQ channels + # have a lower value than any other channels. This is necessary because Microkit will + # deliver notifications in ascending channel_id order, which can end up mattering in certain + # cases. + self.construct_infrastructure(virt_prio) + + def construct_infrastructure(self, virt_prio): + self.virt = ProtectionDomain("i2c_virt", self.virt_elf, scheduling=SchedulingProperties(virt_prio), cpu=self.cpu) + self.pds.extend([self.driver, self.virt]) + + # Make queues + driver_req_q_mr = MemoryRegion("i2c_driver_request", 0x1000) + driver_resp_q_mr = MemoryRegion("i2c_driver_response", 0x1000) + driver_req_map = self.driver.create_automap(driver_req_q_mr, Map.Permissions(r=True, w=True)) + driver_resp_map = self.driver.create_automap(driver_resp_q_mr, Map.Permissions(r=True, w=True)) + virt_req_map = self.virt.create_automap(driver_req_q_mr, Map.Permissions(r=True, w=True)) + virt_resp_map = self.virt.create_automap(driver_resp_q_mr, Map.Permissions(r=True, w=True)) + + # Need to keep a reference to MRs to serialise them! + self.mrs.extend([driver_req_q_mr, driver_resp_q_mr]) + + # Create channels + driver_virt_ch = Channel( + Channel.End(self.driver, can_notify=True, can_pp=False), + Channel.End(self.virt, can_notify=True, can_pp=False) + ) + self.channels.append(driver_virt_ch) + + # Create config structs + self.virt_driver_config = self.i2c_connection_resource_factory( + virt_req_map, virt_resp_map, I2C_NUM_BUFS, driver_virt_ch.id_for_pd(self.virt) + ) + driver_virt_connection = self.i2c_connection_resource_factory( + driver_req_map, driver_resp_map, I2C_NUM_BUFS, driver_virt_ch.id_for_pd(self.driver) + ) + self.driver_config = self.i2c_driver_config_factory(self.driver, I2C_PROTOCOL_MAGIC, driver_virt_connection) + + def connect_clients(self): + assert self.virt is not None + assert self.driver is not None + + # Clients are connected with: + # a. request queue + # b. response queue + # c. data region shared with driver + # c. channel for notifications and PPCs + virt_client_configs = [] + for c in self.clients: + if c.priority >= self.virt.priority: + raise SubsystemBuildError(f"Client {c} has a priority higher " + f"than virt's ({self.driver.priority})!") + # Make channel + ch = Channel( + Channel.End(c, can_notify=True, can_pp=True), + Channel.End(self.virt, can_notify=True, can_pp=False) + ) + self.channels.append(ch) + + # Add request and response queue + c_req_q_mr = MemoryRegion(f"i2c_client_request_{c.name}", 0x1000) + c_resp_q_mr = MemoryRegion(f"i2c_client_response_{c.name}", 0x1000) + c_data_mr = MemoryRegion(f"i2c_client_data_{c.name}", I2C_DATA_SZ) + self.mrs.extend([c_req_q_mr, c_resp_q_mr, c_data_mr]) + + # Create maps for clients + c_req_map = c.create_automap(c_req_q_mr, Map.Permissions(r=True, w=True)) + c_resp_map = c.create_automap(c_resp_q_mr, Map.Permissions(r=True, w=True)) + c_data_map = c.create_automap(c_data_mr, Map.Permissions(r=True, w=True)) + + # Maps for virt / driver + req_map = self.virt.create_automap(c_req_q_mr, Map.Permissions(r=True, w=True)) + resp_map = self.virt.create_automap(c_resp_q_mr, Map.Permissions(r=True, w=True)) + data_map = self.driver.create_automap(c_data_mr, Map.Permissions(r=True, w=True)) + + # Prep config structs + virt_connection = self.i2c_connection_resource_factory(req_map, resp_map, I2C_NUM_BUFS, ch.id_for_pd(self.virt)) + client_connection = self.i2c_connection_resource_factory(c_req_map, c_resp_map, I2C_NUM_BUFS, ch.id_for_pd(c)) + client_data = RegionResourceFactory(c_data_map) + + self.client_configs.append( + self.i2c_client_config_factory(c, client_connection, client_data) + ) + virt_client_configs.append( + self.i2c_virt_client_config_factory( + virt_connection, I2C_DATA_SZ, data_map.vaddr, c_data_map.vaddr + ) + ) + # Clients added. Finally, create virt config + self.virt_config = self.i2c_virt_config_factory( + self.virt, I2C_PROTOCOL_MAGIC, len(self.clients), self.virt_driver_config, virt_client_configs + ) + + + def generate_config_structs(self): + # We've already made our structs, just return them as a list for the serialiser + driver_resources = [self.driver_dev_resources, self.driver_config] + virt_resources = [self.virt_config] + return driver_resources + virt_resources + self.client_configs + + # ### connection config struct factory functions ### + def i2c_connection_resource_factory(self, req_q: Map, resp_q: Map, num_bufs: int, id: int) -> ConfigStruct: + fields = { + "req_queue": RegionResourceFactory(req_q), + "resp_queue": RegionResourceFactory(resp_q), + "num_buffers": num_bufs, + "id": id + } + return ConfigStruct("i2c_connection_resource_t", fields=fields) + + def i2c_client_config_factory(self, client_pd: ProtectionDomain, virt_connection: ConfigStruct, data_region: ConfigStruct) -> ConfigStruct: + """ + Create i2c_client_config for client_pd with serial id n + """ + # invariant: this PD only is a client to i2c one time. + end = next(x.end_a for x in self.channels if x.end_a.pd is client_pd) + ch_id = end.ch_id + fields = { + "magic": I2C_PROTOCOL_MAGIC, + "virt": virt_connection, + "data": data_region + } + return ConfigStruct("i2c_client_config_t", target_file=client_pd.prog_image, section_name="i2c_client_config", fields=fields) + + def i2c_virt_client_config_factory(self, client_connection: ConfigStruct, data_size: int, driver_d_vaddr: int, client_d_vaddr: int) -> ConfigStruct: + """ + Create a i2c_virt_client_config for some client. + """ + fields = { + "conn": client_connection, + "data_size": data_size, + "driver_data_vaddr": driver_d_vaddr, + "client_data_vaddr": client_d_vaddr + } + return ConfigStruct("i2c_virt_client_config_t", fields=fields) + + def i2c_virt_config_factory(self, virt_pd: ProtectionDomain, magic: str, num_clients: int, driver_connection: ConfigStruct, client_connections: List[ConfigStruct]) -> ConfigStruct: + fields = { + "magic": magic, + "num_clients": num_clients, + "driver": driver_connection, + "clients": client_connections + } + return ConfigStruct("i2c_virt_config_t", target_file=virt_pd.prog_image, section_name="i2c_virt_config", fields=fields) + + def i2c_driver_config_factory(self, driver_pd: ProtectionDomain, magic: str, virt_connection: ConfigStruct) -> ConfigStruct: + fields = { + "magic": magic, + "virt": virt_connection, + } + return ConfigStruct("i2c_driver_config_t", target_file=driver_pd.prog_image, section_name="i2c_driver_config", fields=fields) + +# Driver configs +i2c_driver_configs: Dict[str, List[sDDFDriverConfig]] = defaultdict(list) + +def add_driver_config(driver_name: str, config: sDDFDriverConfig): + sDDFDriverManifest().add_driver_config(sDDFI2C, driver_name, config) + +# meson +add_driver_config( + "meson", + sDDFDriverConfig( + compatible="amlogic,meson-axg-i2c", + regions=[DTSRegion("regs", "rw", 4096, 0)], + irqs=[DTSIRQ(0), DTSIRQ(1)] + ) +) + +# opentitan +add_driver_config( + "opentitan", + sDDFDriverConfig( + compatible="eth,i2c", + regions=[DTSRegion("regs", "rw", 4096, 0)], + irqs=[DTSIRQ(4), DTSIRQ(0), DTSIRQ(1), DTSIRQ(7), DTSIRQ(9)] + ) +) diff --git a/acacia_sddf/sddf.py b/acacia_sddf/sddf.py new file mode 100644 index 000000000..f5068296f --- /dev/null +++ b/acacia_sddf/sddf.py @@ -0,0 +1,192 @@ +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause +import sys, os +from typing import List, Optional, Tuple +from acacia import Subsystem, ProtectionDomain, Channel, Map, MemoryRegion, DTBNode, DeviceTreeBlob, SchedulingProperties, ConfigStruct, IRQ, System +from .driver_manifest import sDDFDriverManifest, sDDFDriverConfig, DTSIRQ, DTSRegion + +class sDDFDriverClass(Subsystem): + """ + This abstract class is inherited by all sDDF driver class implementations. + It handles: + a) Mapping of sDDF drivers to their corresponding device tree blobs + b) Parsing the device tree to set up device resources + c) Providing some common utility functions for generating config structs, etc. + """ + def __init__(self, + class_name: str, + dev_compatible: str, + dev_dt_path: str, + system: System, + magic: str): + super().__init__(self, class_name) + + self.sdf = system + self.dtb = system.dtb + self.driver_magic = magic + if system.dtb is None: + print(f"Initialising {class_name} driver with no DTB. Assuming this is x86 and no DTB is needed") + return + + # Find real DTB node + print(f"Finding {class_name} compatible for {dev_compatible} -- {dev_dt_path} from {self.dtb.file_path}") + target_node = self.dtb.get_node_by_path(dev_dt_path) + + # make sure compatible matches! + if dev_compatible not in (a_c := self.dtb.get_compatible(target_node)): + raise IOError(f"Target node {dev_dt_path} has compatible {a_c}... " + f"doesn't match expected {dev_compatible}!") + + # check if DTB node is "okay" if it has a status + ok = self.dtb.get_node_prop(target_node, "status") + if ok is not None and ok.as_str() != "okay": + raise RuntimeError(f"{target_node} has bad status {ok.as_str()}!") + + self.dtb_node = target_node + + # Find sDDF driver matching this node + matching_configs = sDDFDriverManifest().get_configs_matching_compatible(type(self), dev_compatible) + + if len(matching_configs) == 0: + raise RuntimeError(f"No driver config matches {dev_compatible} -> {dev_dt_path}!") + elif len(matching_configs) != 1: + raise RuntimeError(f"Multiple sDDF drivers satisfy {dev_compatible}! "\ + f"There whould be only one.\n{matching_configs}") + self.driver_config = matching_configs[0] + + def create_dtb_resources(self, driver_pd: ProtectionDomain) -> ConfigStruct: + """ + Given the driver PD and the DTB+driver_config we were initialised with, + create all regions, maps, and IRQs required. Creates a DeviceResources ConfigStruct + for the subsystem to use upon calling `create_config_structs`. + + This method will not run twice; it will return the existing ConfigStruct. I.e. + there is no risk of creating duplicate MRs, maps or IRQs from this method. + + Args: + driver_pd: ProtectionDomain + Returns: + ConfigStruct -> DeviceResources. + """ + # Cache DTB regions. Don't want to accidentally make these multiple times. + if hasattr(self, "__device_resources"): + return self.__device_resources + + if self.dtb is None: + print(f"sddf.py: no DTB! Creating dummy device resources.") + # x86 or otherwise no DTB! + # We generate an empty deviceresources despite it being useless, as our build system expects it. + # TODO: fix that? + self.__device_resources = DeviceResourcesFactory( + self.driver_magic, + [], + [], + target_file=driver_pd.prog_image + ) + return self.__device_resources + + region_maps = [] # track fields to store in DeviceResources. tuples of vaddr, offset + for region in self.driver_config.regions: + mr = None + # We name regions as [region_name]_[node_path] to avoid collisions with + # duplicate driver classes on different nodes + region_name = region.name + "_" + self.dtb_node.path + + # First: create or find matching MR + if region.dt_idx is not None: + # First: find reg property + regs = self.dtb.get_node_regs(self.dtb_node) + r_addr, r_sz = regs[region.dt_idx] + r_sz = self.sdf.arch.roundup_to_page(r_sz) + + # Check we can turn this into a region + if region.size is not None: + if r_sz < region.size: + raise RuntimeError() # todo + + if (region.size & (self.sdf.arch.default_page_size()-1)) != 0: + raise RuntimeError(f"Region {region} with size={region.size} is not aligned to"\ + f"system page size!") + mr_sz = region.size if region.size is not None else r_sz + d_paddr = self.dtb.get_reg_paddr(self.sdf.arch, self.dtb_node, r_addr) + d_reg_offset = r_addr % self.sdf.arch.default_page_size() + + # Check if this page is shared (i.e. a matching region is already existing). + # If regions overlap but don't have the same start, we do nothing and let microkit + # reject this. Should we reject here? TODO + existing_mr = [mr for mr in self.sdf.mrs if mr.paddr == d_paddr] + if len(existing_mr) == 1: + mr = existing_mr[0] + elif len(existing_mr) > 1: + raise RuntimeError(f"Multiple MRs with paddr={d_paddr}! -> {existing_mr}") + else: + # This is new (or overlapping with a different start) + mr = MemoryRegion(region_name, mr_sz, paddr=d_paddr, cached=False) + else: + # This is a MR that doesn't correspond to physical memory + # mr = MemoryRegion(region_name, region.size) + # d_reg_offset = 0 + + # If you've run into this, open an issue. Old sdfgen supports this but it seems like a bug. + raise NotImplementedError("Config region doesn't correspond to DTB!") + + # Second: set up map + # Assumes permission string is correctly formatted. Non r/w/x chars are ignored + d_map = driver_pd.create_automap(mr, region.perms if region.perms else "rw") + region_maps.append((d_map, d_reg_offset)) + self.sdf.add_memory_region(mr) + + # Next: set up IRQs + irqs_from_prop = self.dtb.get_parsed_irqs(self.dtb_node, self.sdf.arch) + if len(irqs_from_prop) == 0 and (t:= len(self.driver_config.irqs)) != 0: + raise RuntimeError(f"Driver config expects {t} irqs but none found in node!") + + irq_ids = [] + for irq in self.driver_config.irqs: + dt_irq = irqs_from_prop[irq.dt_index] + irq_ids.append(driver_pd.add_irq(dt_irq)) + + # Finally: make config struct + self.__device_resources = DeviceResourcesFactory( + self.driver_magic, + region_maps, + irq_ids, + target_file=driver_pd.prog_image + ) + return self.__device_resources + + +def RegionResourceFactory(map: Map, section_name: Optional[str] = None, offset = 0): + fields = { + "vaddr": map.vaddr + offset, + "size": map.mr.size + } + return ConfigStruct("region_resource_t", section_name=section_name, fields=fields) + +def DeviceRegionResourceFactory(region: ConfigStruct, io_addr: int): + fields = { + "region": region, + "io_addr": io_addr + } + return ConfigStruct("device_region_resource_t", fields=fields) + +def DeviceIRQResourceFactory(id: int): + fields = { + "id": id + } + return ConfigStruct("device_irq_resource_t", fields=fields) + +def DeviceResourcesFactory(magic_str: str, maps_offsets: List[Tuple[Map,int]], irq_ids: List[int], target_file: str, section_name = "device_resources"): + region_structs = [ + DeviceRegionResourceFactory(RegionResourceFactory(m, offset=o), m.mr.paddr) + for m, o in maps_offsets + ] + irq_structs = [DeviceIRQResourceFactory(i) for i in irq_ids] + fields = { + "magic": magic_str, + "num_regions": len(region_structs), + "num_irqs": len(irq_structs), + "regions": region_structs, + "irqs": irq_structs + } + return ConfigStruct("device_resources_t", section_name=section_name, fields=fields, target_file=target_file) diff --git a/acacia_sddf/serial.py b/acacia_sddf/serial.py new file mode 100644 index 000000000..3632ce8cd --- /dev/null +++ b/acacia_sddf/serial.py @@ -0,0 +1,459 @@ +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +from acacia import System, Subsystem, ProtectionDomain, Channel, Map, MemoryRegion, DTBNode, DeviceTreeBlob, SchedulingProperties, ConfigStruct, SubsystemBuildError +from acacia.x86 import IOPort +from acacia.irq import IrqIoapic +import sys, os +from .driver_manifest import sDDFDriverManifest, sDDFDriverConfig, DTSIRQ, DTSRegion +from .sddf import sDDFDriverClass, DeviceResourcesFactory, RegionResourceFactory +from collections import defaultdict +from typing import List, Dict, Type, Union, Optional + +SERIAL_DEFAULT_BEGIN_STR = "Begin input\r\n" +SERIAL_MAX_BEGIN_STR_LEN = 128 +SERIAL_PROTOCOL_MAGIC = "sDDF" + chr(0x3) + +class sDDFSerial(sDDFDriverClass): + def __init__(self, + dev_compatible: str, + dev_dt_path: str, + sdf: System, + driver_prio: int, + virt_tx_prio: int, + allow_rx: bool = False, + virt_rx_prio: Optional[int] = None, + cpu: Optional[int] = None, + enable_color: bool = True, + baud_rate: int = 115200, + begin_str: str = SERIAL_DEFAULT_BEGIN_STR, + # We leave this as configurable just in case... + data_size: int = 0x10000, + queue_size: int = 0x1000, + virt_rx_elf: str = "serial_virt_rx.elf", + virt_tx_elf: str = "serial_virt_tx.elf", + driver_elf: str = "serial_driver.elf" + ): + super().__init__("serial", dev_compatible, dev_dt_path, sdf, magic="sDDF"+chr(0x1)) + assert driver_prio > virt_tx_prio > 0 + if allow_rx: + # Default RX prio == TX prio + if virt_rx_prio is None: + virt_rx_prio = virt_tx_prio + assert driver_prio > virt_rx_prio > 0 + + self.cpu = cpu + self.allow_rx = allow_rx + self.data_size = data_size + self.queue_size = queue_size + self.enable_color = enable_color + self.baud_rate = baud_rate + if len(begin_str) > SERIAL_MAX_BEGIN_STR_LEN: + raise SubsystemBuildError( + f"begin_str length {len(begin_str)} exceeds max {SERIAL_MAX_BEGIN_STR_LEN}" + ) + self.begin_str = begin_str + + self.virt_tx = None + self.virt_rx = None + self.virt_rx_elf = virt_rx_elf + self.virt_tx_elf = virt_tx_elf + self.driver = ProtectionDomain( + "serial_driver", driver_elf, + scheduling=SchedulingProperties(driver_prio), cpu=self.cpu + ) + # Do x86 stuff + if self.sdf.dtb is None: + self.add_x86_serial_port() + + # We must make the driver BEFORE we get here + self.driver_dev_resources = self.create_dtb_resources(self.driver) + + # Stubs of config structs that we need to collect in construct_infrastructure and connect_clients + self.virt_tx_config = None + self.virt_rx_config = None + self.driver_config = None + self.virt_tx_driver_conn = None + self.virt_rx_driver_conn = None + self.client_configs = [] + self.construct_infrastructure(virt_rx_prio if virt_rx_prio else -1, virt_tx_prio) + + def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): + self.virt_tx = ProtectionDomain( + "serial_virt_tx", self.virt_tx_elf, + scheduling=SchedulingProperties(virt_tx_prio), cpu=self.cpu + ) + self.pds.append(self.driver) + self.pds.append(self.virt_tx) + + if self.allow_rx and virt_rx_prio > 0: + self.virt_rx = ProtectionDomain( + "serial_virt_rx", self.virt_rx_elf, + scheduling=SchedulingProperties(virt_rx_prio), cpu=self.cpu + ) + self.pds.append(self.virt_rx) + + driver_tx_queue_mr = MemoryRegion("serial_driver_tx_queue", self.queue_size) + driver_tx_data_mr = MemoryRegion( + "serial_driver_tx_data", + self.data_size * 2 if self.enable_color else self.data_size, + cached=True + ) + self.mrs.extend([driver_tx_queue_mr, driver_tx_data_mr]) + + driver_tx_queue_map = self.driver.create_automap( + driver_tx_queue_mr, Map.Permissions(r=True, w=True) + ) + driver_tx_data_map = self.driver.create_automap( + driver_tx_data_mr, Map.Permissions(r=True, w=True) + ) + virt_tx_queue_map = self.virt_tx.create_automap( + driver_tx_queue_mr, Map.Permissions(r=True, w=True) + ) + virt_tx_data_map = self.virt_tx.create_automap( + driver_tx_data_mr, Map.Permissions(r=True, w=True) + ) + + driver_virt_tx_ch = Channel( + Channel.End(self.driver, can_notify=True, can_pp=False), + Channel.End(self.virt_tx, can_notify=True, can_pp=False) + ) + self.channels.append(driver_virt_tx_ch) + + driver_tx_conn = self.serial_connection_resource_factory( + driver_tx_queue_map, driver_tx_data_map, + driver_virt_tx_ch.id_for_pd(self.driver) + ) + self.virt_tx_driver_conn = self.serial_connection_resource_factory( + virt_tx_queue_map, virt_tx_data_map, + driver_virt_tx_ch.id_for_pd(self.virt_tx) + ) + + driver_rx_conn = None + if self.virt_rx: + driver_rx_queue_mr = MemoryRegion( + "serial_driver_rx_queue", self.queue_size + ) + driver_rx_data_mr = MemoryRegion( + "serial_driver_rx_data", self.data_size + ) + self.mrs.extend([driver_rx_queue_mr, driver_rx_data_mr]) + + driver_rx_queue_map = self.driver.create_automap( + driver_rx_queue_mr, Map.Permissions(r=True, w=True) + ) + driver_rx_data_map = self.driver.create_automap( + driver_rx_data_mr, Map.Permissions(r=True, w=True) + ) + virt_rx_queue_map = self.virt_rx.create_automap( + driver_rx_queue_mr, Map.Permissions(r=True, w=True) + ) + virt_rx_data_map = self.virt_rx.create_automap( + driver_rx_data_mr, Map.Permissions(r=True, w=True) + ) + + driver_virt_rx_ch = Channel( + Channel.End(self.driver, can_notify=True, can_pp=False), + Channel.End(self.virt_rx, can_notify=True, can_pp=False) + ) + self.channels.append(driver_virt_rx_ch) + + driver_rx_conn = self.serial_connection_resource_factory( + driver_rx_queue_map, driver_rx_data_map, + driver_virt_rx_ch.id_for_pd(self.driver) + ) + self.virt_rx_driver_conn = self.serial_connection_resource_factory( + virt_rx_queue_map, virt_rx_data_map, + driver_virt_rx_ch.id_for_pd(self.virt_rx) + ) + + self.driver_config = self.serial_driver_config_factory( + self.driver, SERIAL_PROTOCOL_MAGIC, self.baud_rate, 1 if self.virt_rx else 0, + driver_tx_conn, driver_rx_conn + ) + + def connect_clients(self): + assert self.virt_tx is not None + assert self.driver is not None + + virt_tx_client_structs = [] + virt_rx_client_conns = [] + client_configs = [] + + for c in self.clients: + if c.priority >= self.virt_tx.priority: + raise SubsystemBuildError( + f"Client {c} has a priority higher than virt_tx's " + f"({self.virt_tx.priority})!" + ) + if self.virt_rx and c.priority >= self.virt_rx.priority: + raise SubsystemBuildError( + f"Client {c} has a priority higher than virt_rx's " + f"({self.virt_rx.priority})!" + ) + + # TX connection: virt_tx -> client + tx_queue_mr = MemoryRegion(f"serial_tx_queue_{c.name}", self.queue_size) + tx_data_mr = MemoryRegion(f"serial_tx_data_{c.name}", self.data_size) + self.mrs.extend([tx_queue_mr, tx_data_mr]) + + virt_tx_tx_queue_map = self.virt_tx.create_automap( + tx_queue_mr, Map.Permissions(r=True, w=True) + ) + virt_tx_tx_data_map = self.virt_tx.create_automap( + tx_data_mr, Map.Permissions(r=True, w=True) + ) + c_tx_queue_map = c.create_automap( + tx_queue_mr, Map.Permissions(r=True, w=True) + ) + c_tx_data_map = c.create_automap( + tx_data_mr, Map.Permissions(r=True, w=True) + ) + + tx_ch = Channel( + Channel.End(self.virt_tx, can_notify=True, can_pp=False), + Channel.End(c, can_notify=True, can_pp=False) + ) + self.channels.append(tx_ch) + + virt_tx_conn = self.serial_connection_resource_factory( + virt_tx_tx_queue_map, virt_tx_tx_data_map, + tx_ch.id_for_pd(self.virt_tx) + ) + client_tx_conn = self.serial_connection_resource_factory( + c_tx_queue_map, c_tx_data_map, tx_ch.id_for_pd(c) + ) + + virt_tx_client_structs.append( + self.serial_virt_tx_client_config_factory(c.name, virt_tx_conn) + ) + + # RX connection (if enabled): virt_rx -> client + client_rx_conn = None + if self.virt_rx: + rx_queue_mr = MemoryRegion(f"serial_rx_queue_{c.name}", self.queue_size) + rx_data_mr = MemoryRegion(f"serial_rx_data_{c.name}", self.data_size) + self.mrs.extend([rx_queue_mr, rx_data_mr]) + + virt_rx_rx_queue_map = self.virt_rx.create_automap( + rx_queue_mr, Map.Permissions(r=True, w=True) + ) + virt_rx_rx_data_map = self.virt_rx.create_automap( + rx_data_mr, Map.Permissions(r=True, w=True) + ) + c_rx_queue_map = c.create_automap( + rx_queue_mr, Map.Permissions(r=True, w=True) + ) + c_rx_data_map = c.create_automap( + rx_data_mr, Map.Permissions(r=True, w=True) + ) + + rx_ch = Channel( + Channel.End(self.virt_rx, can_notify=True, can_pp=False), + Channel.End(c, can_notify=True, can_pp=False) + ) + self.channels.append(rx_ch) + + virt_rx_conn = self.serial_connection_resource_factory( + virt_rx_rx_queue_map, virt_rx_rx_data_map, + rx_ch.id_for_pd(self.virt_rx) + ) + client_rx_conn = self.serial_connection_resource_factory( + c_rx_queue_map, c_rx_data_map, rx_ch.id_for_pd(c) + ) + virt_rx_client_conns.append(virt_rx_conn) + + client_configs.append( + self.serial_client_config_factory(c, SERIAL_PROTOCOL_MAGIC, client_tx_conn, client_rx_conn) + ) + + self.virt_tx_config = self.serial_virt_tx_config_factory( + self.virt_tx, SERIAL_PROTOCOL_MAGIC, len(self.clients), self.virt_tx_driver_conn, + virt_tx_client_structs, 1 if self.enable_color else 0, + 1 if self.virt_rx else 0, self.begin_str + ) + + if self.virt_rx: + self.virt_rx_config = self.serial_virt_rx_config_factory( + self.virt_rx, SERIAL_PROTOCOL_MAGIC, len(self.clients), self.virt_rx_driver_conn, + virt_rx_client_conns + ) + + self.client_configs = client_configs + + def generate_config_structs(self): + # We've already made our structs, just return them as a list for the serialiser + driver_resources = [self.driver_dev_resources, self.driver_config] + virt_resources = [] + if self.virt_tx_config: + virt_resources.append(self.virt_tx_config) + if self.virt_rx_config: + virt_resources.append(self.virt_rx_config) + return driver_resources + virt_resources + self.client_configs + + # ### connection config struct factory functions ### + + def serial_connection_resource_factory( + self, queue_map: Map, data_map: Map, ch_id: int + ) -> ConfigStruct: + fields = { + "queue": RegionResourceFactory(queue_map), + "data": RegionResourceFactory(data_map), + "id": ch_id, + } + return ConfigStruct("serial_connection_resource_t", fields=fields) + + def serial_driver_config_factory( + self, driver_pd: ProtectionDomain, magic: str, baud_rate: int, rx_enabled: int, + tx_connection: ConfigStruct, + rx_connection: Optional[ConfigStruct] = None + ) -> ConfigStruct: + fields = { + "magic": magic, + "default_baud": baud_rate, + "rx_enabled": rx_enabled, + "tx": tx_connection, + "rx": rx_connection if rx_connection else 0 + } + return ConfigStruct( + "serial_driver_config_t", target_file=driver_pd.prog_image, + section_name="serial_driver_config", fields=fields + ) + + def serial_virt_rx_config_factory( + self, virt_rx_pd: ProtectionDomain, magic:str, num_clients: int, + driver_connection: ConfigStruct, + client_connections: List[ConfigStruct] + ) -> ConfigStruct: + fields = { + "magic": magic, + "num_clients": num_clients, + "driver": driver_connection, + "clients": client_connections, + "switch_char": chr(28), + "terminate_num_char": '\r', + } + return ConfigStruct( + "serial_virt_rx_config_t", target_file=virt_rx_pd.prog_image, + section_name="serial_virt_rx_config", fields=fields + ) + + def serial_virt_tx_client_config_factory( + self, name: str, conn: ConfigStruct + ) -> ConfigStruct: + fields = { + "conn": conn, + "name": name, + } + return ConfigStruct("serial_virt_tx_client_t", fields=fields) + + def serial_virt_tx_config_factory( + self, virt_tx_pd: ProtectionDomain, magic:str, num_clients: int, + driver_connection: ConfigStruct, + client_connections: List[ConfigStruct], enable_colour: int, + enable_rx: int, begin_str: str + ) -> ConfigStruct: + fields = { + "magic": magic, + "driver": driver_connection, + "clients": client_connections, + "num_clients": num_clients, + "begin_str": begin_str, + "enable_colour": enable_colour, + "enable_rx": enable_rx, + } + return ConfigStruct( + "serial_virt_tx_config_t", target_file=virt_tx_pd.prog_image, + section_name="serial_virt_tx_config", fields=fields + ) + + def serial_client_config_factory( + self, client_pd: ProtectionDomain, magic, tx_connection: ConfigStruct, + rx_connection: Optional[ConfigStruct] = None + ) -> ConfigStruct: + fields = { + "magic": magic, + "tx": tx_connection, + "rx": rx_connection if rx_connection else 0 + } + return ConfigStruct( + "serial_client_config_t", target_file=client_pd.prog_image, + section_name="serial_client_config", fields=fields + ) + + # x86 Util + def add_x86_serial_port(self): + # The serial device does not located on PCIe and the interrupts are + # conventionally configured by BIOS. The IRQ number can be read from + # Linux or APCI tables. + self.driver.add_ioport(IOPort(0x3f8, 8, 0)) + self.driver.add_irq(IrqIoapic(0, 4, 0, id=1)) + +# Driver configs +serial_driver_configs: Dict[str, List[sDDFDriverConfig]] = defaultdict(list) + +def add_driver_config(driver_name: str, config: sDDFDriverConfig): + sDDFDriverManifest().add_driver_config(sDDFSerial, driver_name, config) + +add_driver_config( + "meson", + sDDFDriverConfig( + ["amlogic,meson-gx-uart", "amlogic,meson-ao-uart"], + [DTSRegion("regs", "rw", 4096, 0)], + [DTSIRQ(0)] + ) +) + +add_driver_config( + "pl011", + sDDFDriverConfig( + compatible="arm,pl011", + regions=[DTSRegion("regs", "rw", 4096, 0)], + irqs=[DTSIRQ(0)] + ) +) + +add_driver_config( + "imx", + sDDFDriverConfig( + compatible=["fsl,imx8mq-uart", "fsl,imx8mm-uart", "fsl,imx8mp-uart"], + regions=[DTSRegion("regs", "rw", 4096, 0)], + irqs=[DTSIRQ(0)] + ) +) + +# ns16550a +add_driver_config( + "ns16550a", + sDDFDriverConfig( + compatible=["starfive,jh7110-uart", "ns16550a", "brcm,bcm2835-aux-uart", "snps,dw-apb-uart"], + regions=[DTSRegion("regs", "rw", 4096, 0)], + irqs=[DTSIRQ(0)] + ) +) + +# virtio +add_driver_config( + "virtio", + sDDFDriverConfig( + compatible="virtio,mmio", + regions=[ + DTSRegion("regs", "rw", 4096, 0), + DTSRegion("hw_ring_buffer", size=65536), + DTSRegion("virtio_rx_buf", size=4096), + DTSRegion("virtio_tx_buf", size=4096) + ], + irqs=[DTSIRQ(0)] + ) +) + +# xlnx +add_driver_config( + "xlnx", + sDDFDriverConfig( + compatible="xlnx,zynqmp-uart", + regions=[DTSRegion("regs", dt_idx=0)], + irqs=[DTSIRQ(0)] + ) +) + diff --git a/acacia_sddf/timer.py b/acacia_sddf/timer.py new file mode 100644 index 000000000..26c0e641a --- /dev/null +++ b/acacia_sddf/timer.py @@ -0,0 +1,178 @@ +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +from acacia import System, Subsystem, ProtectionDomain, Channel, Map, MemoryRegion, DTBNode, DeviceTreeBlob, SchedulingProperties, ConfigStruct, IRQ, SubsystemBuildError +import sys, os +from .driver_manifest import sDDFDriverManifest, sDDFDriverConfig, DTSIRQ, DTSRegion +from .sddf import sDDFDriverClass, DeviceResourcesFactory, RegionResourceFactory +from collections import defaultdict +from typing import List, Dict, Type, Union, Optional + +class sDDFTimer(sDDFDriverClass): + def __init__(self, + dev_compatible: str, + dev_dt_path: str, + sdf: System, + driver_prio: int=254, + cpu:Optional[int]=None, + driver_elf: str="timer_driver.elf" + ): + super().__init__("timer", dev_compatible, dev_dt_path, sdf, magic="sDDF"+chr(1)) + self.driver = ProtectionDomain("timer_driver", driver_elf, scheduling=SchedulingProperties(driver_prio, passive=True)) + self.pds.append(self.driver) + self.cpu = cpu + + # Create driver resources before doing anything else + self.driver_dev_resources = self.create_dtb_resources(self.driver) + self.client_configs = [] + + def connect_clients(self): + # Clients are connected with: + # a. channel allowing PPCs -> driver, notifications -> clienet + # ... that's it! + for c in self.clients: + if c.priority > self.driver.priority: + raise SubsystemBuildError(f"Client {c} has higher priority than timer driver!") + ch = Channel( + Channel.End(c, can_notify=False, can_pp=True), + Channel.End(self.driver, can_notify=True, can_pp=False) + ) + self.channels.append(ch) + self.client_configs.append( + self.timer_client_config_factory(c, ch.id_for_pd(c)) + ) + + def generate_config_structs(self): + # We've already made our structs + return [self.driver_dev_resources] + self.client_configs + + def timer_client_config_factory(self, client_pd: ProtectionDomain, driver_id: int) -> ConfigStruct: + """ + create timer_client_config for client_pd with serial id n + """ + # invariant: this PD only is a client to timer one time. + fields = { + "magic": "sDDF"+chr(6), + "driver_id": driver_id + } + return ConfigStruct("timer_client_config_t", target_file=client_pd.prog_image, section_name="timer_client_config", fields=fields) + + + # x86 utility + # NOTE: is this safe to call automatically? I currently am assuming we want manual + # control over this since we didn't bake it into sdfgen before. + def add_x86_hpet(self, sdf: System): + # Timer IRQ must be the highest priority (highest vector) to ensure they are delivered + # as close as possible to the timer expiry. The highest vector is defined by (irq_user_max - irq_user_min) in seL4 source + # Since our HPET driver uses legacy IRQ routing, comparator 0's IRQ will always arrives at + # I/O APIC 0's pin 2. + from acacia.irq import IrqIoapic + hpet_irq = IrqIoapic( + ioapic_id=0, + pin=2, + vector=107, + id=0, + trigger=IRQ.Trigger.EDGE + ) + self.driver.add_irq(hpet_irq) + # paddr=0xFED00000 is a x86 convention for HPET, though it may be different on some machines depending on their BIOS. + hpet_regs = MemoryRegion( + "hpet_regs", 0x1000, paddr=0xFED00000 + ) + hpet_regs_map = Map(hpet_regs, 0x5000_0000, "rw") + self.driver.add_map(hpet_regs_map) + sdf.add_memory_region(hpet_regs) + +# Driver configs +def add_driver_config(driver_name: str, config: sDDFDriverConfig): + sDDFDriverManifest().add_driver_config(sDDFTimer, driver_name, config) + +# pulp +add_driver_config( + "apb_timer", + sDDFDriverConfig( + compatible="pulp,apb_timer", + regions=[DTSRegion("regs", "rw", 4096, 0)], + irqs=[DTSIRQ(0), DTSIRQ(1), DTSIRQ(2), DTSIRQ(3)] + ) +) +# armv8 +add_driver_config( + "arm", + sDDFDriverConfig( + compatible="arm,armv8-timer", + regions=[], + irqs=[DTSIRQ(1)] + ) +) + +# bcm2835 +add_driver_config( + "bcm2835", + sDDFDriverConfig( + compatible="brcm,bcm2835-system-timer", + regions=[DTSRegion("regs", dt_idx=0)], + irqs=[DTSIRQ(1)] + ) +) + +# cdns +add_driver_config( + "cdns", + sDDFDriverConfig( + compatible="cdns,ttc", + regions=[DTSRegion("regs", dt_idx=0)], + irqs=[DTSIRQ(0), DTSIRQ(1)] + ) +) + +# goldfish +add_driver_config( + "goldfish", + sDDFDriverConfig( + compatible="google,goldfish-rtc", + regions=[DTSRegion("regs", dt_idx=0)], + irqs=[DTSIRQ(0)] + ) +) + +# imx8 +add_driver_config( + "imx", + sDDFDriverConfig( + compatible=["fsl,imx8mm-gpt", "fsl,imx8mq-gpt", "fsl,imx8mp-gpt"], + regions=[DTSRegion("regs", "rw", 65536, 0)], + irqs=[DTSIRQ(0)] + ) +) + +# jh7110 +add_driver_config( + "jh7110", + sDDFDriverConfig( + compatible="starfive,jh7110-timer", + regions=[DTSRegion("regs", "rw", 4096, 0)], + irqs=[DTSIRQ(0), DTSIRQ(1)] + ) +) + +# meson_gxbb +add_driver_config( + "meson", + sDDFDriverConfig( + compatible="amlogic,meson-gxbb-wdt", + regions=[DTSRegion("regs", "rw", 4096, 0)], + irqs=[DTSIRQ(0)] + ) +) + +# rk3568 +add_driver_config( + "rk3568", + sDDFDriverConfig( + compatible="rockchip,rk3568-timer", + regions=[DTSRegion("regs", dt_idx=0)], + irqs=[DTSIRQ(0), DTSIRQ(1)] + ) +) + diff --git a/examples/i2c/i2c.mk b/examples/i2c/i2c.mk index 693f2b037..f2b2662d7 100644 --- a/examples/i2c/i2c.mk +++ b/examples/i2c/i2c.mk @@ -80,17 +80,17 @@ $(SYSTEM_FILE): $(METAPROGRAM) $(IMAGES) $(DTB) $(PYTHON) $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) --dtb $(DTB) --output . --sdf $(SYSTEM_FILE) $(OBJCOPY) --update-section .device_resources=timer_driver_device_resources.data timer_driver.elf $(OBJCOPY) --update-section .device_resources=i2c_driver_device_resources.data i2c_driver.elf - $(OBJCOPY) --update-section .i2c_driver_config=i2c_driver.data i2c_driver.elf - $(OBJCOPY) --update-section .i2c_virt_config=i2c_virt.data i2c_virt.elf - $(OBJCOPY) --update-section .i2c_client_config=i2c_client_client_ds3231.data client_ds3231.elf - $(OBJCOPY) --update-section .timer_client_config=timer_client_client_ds3231.data client_ds3231.elf - $(OBJCOPY) --update-section .i2c_client_config=i2c_client_client_pn532.data client_pn532.elf - $(OBJCOPY) --update-section .timer_client_config=timer_client_client_pn532.data client_pn532.elf + $(OBJCOPY) --update-section .i2c_driver_config=i2c_driver_i2c_driver_config.data i2c_driver.elf + $(OBJCOPY) --update-section .i2c_virt_config=i2c_virt_i2c_virt_config.data i2c_virt.elf + $(OBJCOPY) --update-section .i2c_client_config=client_ds3231_i2c_client_config.data client_ds3231.elf + $(OBJCOPY) --update-section .timer_client_config=client_ds3231_timer_client_config.data client_ds3231.elf + $(OBJCOPY) --update-section .i2c_client_config=client_pn532_i2c_client_config.data client_pn532.elf + $(OBJCOPY) --update-section .timer_client_config=client_pn532_timer_client_config.data client_pn532.elf $(OBJCOPY) --update-section .device_resources=serial_driver_device_resources.data serial_driver.elf - $(OBJCOPY) --update-section .serial_driver_config=serial_driver_config.data serial_driver.elf - $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx.data serial_virt_tx.elf - $(OBJCOPY) --update-section .serial_client_config=serial_client_client_pn532.data client_pn532.elf - $(OBJCOPY) --update-section .serial_client_config=serial_client_client_ds3231.data client_ds3231.elf + $(OBJCOPY) --update-section .serial_driver_config=serial_driver_serial_driver_config.data serial_driver.elf + $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx_serial_virt_tx_config.data serial_virt_tx.elf + $(OBJCOPY) --update-section .serial_client_config=client_pn532_serial_client_config.data client_pn532.elf + $(OBJCOPY) --update-section .serial_client_config=client_ds3231_serial_client_config.data client_ds3231.elf touch $@ $(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) diff --git a/examples/i2c/meta.py b/examples/i2c/meta.py index 750e085d6..6d57aa80f 100644 --- a/examples/i2c/meta.py +++ b/examples/i2c/meta.py @@ -4,82 +4,58 @@ import argparse from typing import List from dataclasses import dataclass -from sdfgen import SystemDescription, Sddf, DeviceTree +from acacia import System, ProtectionDomain, MemoryRegion, Channel, DeviceTreeBlob, Map sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../tools/meta") + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../") ) -from board import BOARDS +from acacia_sddf import BOARDS, sDDFI2C, sDDFSerial, sDDFTimer -ProtectionDomain = SystemDescription.ProtectionDomain -MemoryRegion = SystemDescription.MemoryRegion -Map = SystemDescription.Map - - -def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): - serial_driver = ProtectionDomain("serial_driver", "serial_driver.elf", priority=200) - serial_virt_tx = ProtectionDomain( - "serial_virt_tx", "serial_virt_tx.elf", priority=199 - ) - - timer_driver = ProtectionDomain("timer_driver", "timer_driver.elf", priority=4) - i2c_driver = ProtectionDomain("i2c_driver", "i2c_driver.elf", priority=3) - i2c_virt = ProtectionDomain("i2c_virt", "i2c_virt.elf", priority=2) +def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): client_pn532 = ProtectionDomain("client_pn532", "client_pn532.elf", priority=1) client_ds3231 = ProtectionDomain("client_ds3231", "client_ds3231.elf", priority=1) + i2c = sDDFI2C(board.i2c.compatible, board.i2c.node_path, sdf, driver_prio=200, virt_prio=199) + i2c.add_client(client_ds3231) + i2c.add_client(client_pn532) + sdf.add_subsystem(i2c) + + timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) + timer.add_client(client_ds3231) + timer.add_client(client_pn532) + sdf.add_subsystem(timer) + + serial = sDDFSerial( + board.serial.compatible, + board.serial.node_path, + sdf, + driver_prio=201, + virt_tx_prio=200, + allow_rx=False, + enable_color=False, + baud_rate=board.baud_rate if board.baud_rate else 115200 + ) + serial.add_client(client_ds3231) + serial.add_client(client_pn532) + sdf.add_subsystem(serial) + if board.name == "odroidc4": # Odroid-C4 I2C requires clocks/GPIO setup, for now we give the I2C driver # direct access. - clk_mr = MemoryRegion(sdf, "clk", 0x1000, paddr=0xFF63C000) - gpio_mr = MemoryRegion(sdf, "gpio", 0x1000, paddr=0xFF634000) - sdf.add_mr(clk_mr) - sdf.add_mr(gpio_mr) - i2c_driver.add_map(Map(clk_mr, 0x30_000_000, "rw", cached=False)) - i2c_driver.add_map(Map(gpio_mr, 0x30_100_000, "rw", cached=False)) - - i2c_node = dtb.node(board.i2c) - assert i2c_node is not None - timer_node = dtb.node(board.timer) - assert timer_node is not None - serial_node = dtb.node(board.serial) - assert serial_node is not None - - i2c_system = Sddf.I2c(sdf, i2c_node, i2c_driver, i2c_virt) - i2c_system.add_client(client_ds3231) - i2c_system.add_client(client_pn532) - - timer_system = Sddf.Timer(sdf, timer_node, timer_driver) - timer_system.add_client(client_pn532) - timer_system.add_client(client_ds3231) - - serial_system = Sddf.Serial( - sdf, serial_node, serial_driver, serial_virt_tx, enable_color=False - ) - serial_system.add_client(client_pn532) - serial_system.add_client(client_ds3231) - - pds = [ - serial_driver, - serial_virt_tx, - timer_driver, - i2c_driver, - i2c_virt, - client_pn532, - client_ds3231, - ] - for pd in pds: + clk_mr = MemoryRegion("clk", 0x1000, paddr=0xFF63C000, cached=False) + gpio_mr = MemoryRegion("gpio", 0x1000, paddr=0xFF634000, cached=False) + sdf.add_memory_region(clk_mr) + sdf.add_memory_region(gpio_mr) + i2c.driver.add_map(Map(clk_mr, 0x30_000_000, "rw")) + i2c.driver.add_map(Map(gpio_mr, 0x30_100_000, "rw")) + + for pd in [client_ds3231, client_pn532]: sdf.add_pd(pd) - assert i2c_system.connect() - assert i2c_system.serialise_config(output_dir) - assert serial_system.connect() - assert serial_system.serialise_config(output_dir) - assert timer_system.connect() - assert timer_system.serialise_config(output_dir) - - with open(f"{output_dir}/{sdf_file}", "w+") as f: - f.write(sdf.render()) + out_file = f"{output_dir}/{sdf_file}" + sdf.make_config_structs() + print(f"Saving to {out_file}") + sdf.write_xml_file(out_file) if __name__ == "__main__": @@ -94,10 +70,7 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): board = next(filter(lambda b: b.name == args.board, BOARDS)) - sdf = SystemDescription(board.arch, board.paddr_top) - sddf = Sddf(args.sddf) - - with open(args.dtb, "rb") as f: - dtb = DeviceTree(f.read()) + dtb = DeviceTreeBlob(args.dtb) + sdf = System(board.arch, board.paddr_top, dtb) generate(args.sdf, args.output, dtb) diff --git a/examples/serial/meta.py b/examples/serial/meta.py index 183fd0d40..21a0e0dc5 100644 --- a/examples/serial/meta.py +++ b/examples/serial/meta.py @@ -4,73 +4,38 @@ import argparse from typing import List from dataclasses import dataclass -from sdfgen import SystemDescription, Sddf, DeviceTree +from acacia import System, ProtectionDomain, MemoryRegion, Channel, DeviceTreeBlob +from acacia.arch import x86_64 sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../tools/meta") + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../") ) -from board import BOARDS - -ProtectionDomain = SystemDescription.ProtectionDomain - - -def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): - serial_driver = ProtectionDomain("serial_driver", "serial_driver.elf", priority=200) - serial_virt_tx = ProtectionDomain( - "serial_virt_tx", "serial_virt_tx.elf", priority=199 - ) - serial_virt_rx = ProtectionDomain( - "serial_virt_rx", "serial_virt_rx.elf", priority=199 - ) - - if board.arch == SystemDescription.Arch.X86_64: - serial_port = SystemDescription.IoPort(0x3F8, 8, 0) - serial_driver.add_ioport(serial_port) - - # The serial device does not located on PCIe and the interrupts are - # conventionally configured by BIOS. The IRQ number can be read from - # Linux or APCI tables. - serial_irq = SystemDescription.IrqIoapic(0, 4, 0, id=1) - serial_driver.add_irq(serial_irq) +from acacia_sddf import sDDFSerial, BOARDS +def generate(sdf_file: str, output_dir: str): client0 = ProtectionDomain("client0", "client0.elf", priority=1) client1 = ProtectionDomain("client1", "client1.elf", priority=1) - serial_node = None - if dtb is not None: - serial_node = dtb.node(board.serial) - assert serial_node is not None - - baud_rate = board.baud_rate - - serial_system = Sddf.Serial( - sdf, - serial_node, - serial_driver, - serial_virt_tx, - virt_rx=serial_virt_rx, - enable_color=True, - baud_rate=baud_rate, + serial = sDDFSerial( + board.serial.compatible, + board.serial.node_path, + sdf, + driver_prio=200, + virt_tx_prio=199, + allow_rx=True, + enable_color=True, + baud_rate=board.baud_rate if board.baud_rate else 115200 ) - serial_system.add_client(client0) - serial_system.add_client(client1) - pds = [ - serial_driver, - serial_virt_tx, - serial_virt_rx, - client0, - client1, - ] - for pd in pds: + for pd in [client0, client1]: + serial.add_client(pd) sdf.add_pd(pd) - assert serial_system.connect() - assert serial_system.serialise_config(output_dir) - - with open(f"{output_dir}/{sdf_file}", "w+") as f: - f.write(sdf.render()) - + sdf.add_subsystem(serial) + sdf.make_config_structs() + out_file = f"{output_dir}/{sdf_file}" + print(f"Saving to {out_file}") + sdf.write_xml_file(out_file) if __name__ == "__main__": parser = argparse.ArgumentParser() @@ -83,13 +48,10 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): args = parser.parse_args() board = next(filter(lambda b: b.name == args.board, BOARDS)) + if board.arch != x86_64: + dtb = DeviceTreeBlob(args.dtb) + else: + dtb = None + sdf = System(board.arch, board.paddr_top, dtb) - sdf = SystemDescription(board.arch, board.paddr_top) - sddf = Sddf(args.sddf) - - dtb = None - if board.arch != SystemDescription.Arch.X86_64: - with open(args.dtb, "rb") as f: - dtb = DeviceTree(f.read()) - - generate(args.sdf, args.output, dtb) + generate(args.sdf, args.output) diff --git a/examples/serial/serial.mk b/examples/serial/serial.mk index bdf307155..a1e896a60 100644 --- a/examples/serial/serial.mk +++ b/examples/serial/serial.mk @@ -85,11 +85,11 @@ else $(PYTHON) $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) --output . --sdf $(SYSTEM_FILE) endif $(OBJCOPY) --update-section .device_resources=serial_driver_device_resources.data serial_driver.elf - $(OBJCOPY) --update-section .serial_driver_config=serial_driver_config.data serial_driver.elf - $(OBJCOPY) --update-section .serial_virt_rx_config=serial_virt_rx.data serial_virt_rx.elf - $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx.data serial_virt_tx.elf - $(OBJCOPY) --update-section .serial_client_config=serial_client_client0.data client0.elf - $(OBJCOPY) --update-section .serial_client_config=serial_client_client1.data client1.elf + $(OBJCOPY) --update-section .serial_driver_config=serial_driver_serial_driver_config.data serial_driver.elf + $(OBJCOPY) --update-section .serial_virt_rx_config=serial_virt_rx_serial_virt_rx_config.data serial_virt_rx.elf + $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx_serial_virt_tx_config.data serial_virt_tx.elf + $(OBJCOPY) --update-section .serial_client_config=client0_serial_client_config.data client0.elf + $(OBJCOPY) --update-section .serial_client_config=client1_serial_client_config.data client1.elf touch $@ $(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) diff --git a/examples/timer/meta.py b/examples/timer/meta.py index a823628b6..825a0323f 100644 --- a/examples/timer/meta.py +++ b/examples/timer/meta.py @@ -3,44 +3,34 @@ import os import sys import argparse -from sdfgen import SystemDescription, Sddf, DeviceTree import importlib +from acacia import System, ProtectionDomain, MemoryRegion, Channel, DeviceTreeBlob +from acacia.arch import x86_64 +# Use importlib to dynamically load. Using `from` import below other code is bad style. +# board_module = importlib.import_module("board") sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../tools/meta") + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..") ) +from acacia_sddf import BOARDS, sDDFTimer -# Use importlib to dynamically load. Using `from` import below other code is bad style. -board_module = importlib.import_module("board") -BOARDS = board_module.BOARDS - -ProtectionDomain = SystemDescription.ProtectionDomain - - -def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): - timer_node = None - timer_driver = ProtectionDomain("timer_driver", "timer_driver.elf", priority=253) +def generate(sdf_file: str, output_dir: str): client = ProtectionDomain("client", "client.elf", priority=1) - if board.arch == SystemDescription.Arch.X86_64: - board_module.add_x86_hpet(sdf, timer_driver) - else: - timer_node = dtb.node(board.timer) - assert timer_node is not None - - timer_system = Sddf.Timer(sdf, timer_node, timer_driver) - timer_system.add_client(client) - - pds = [timer_driver, client] - for pd in pds: - sdf.add_pd(pd) - assert timer_system.connect() - assert timer_system.serialise_config(output_dir) + timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) + timer.add_client(client) + sdf.add_subsystem(timer) + sdf.add_pd(client) - with open(f"{output_dir}/{sdf_file}", "w+") as f: - f.write(sdf.render()) + # Add HPET if x86 + if board.arch == x86_64: + timer.add_x86_hpet(sdf) + sdf.make_config_structs() + out_file = f"{output_dir}/{sdf_file}" + print(f"Saving to {out_file}") + sdf.write_xml_file(out_file) if __name__ == "__main__": parser = argparse.ArgumentParser() @@ -54,12 +44,10 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): board = next(filter(lambda b: b.name == args.board, BOARDS)) - sdf = SystemDescription(board.arch, board.paddr_top) - sddf = Sddf(args.sddf) - - dtb = None - if board.arch != SystemDescription.Arch.X86_64: - with open(args.dtb, "rb") as f: - dtb = DeviceTree(f.read()) + if board.arch != x86_64: + dtb = DeviceTreeBlob(args.dtb) + else: + dtb = None + sdf = System(board.arch, board.paddr_top, dtb) - generate(args.sdf, args.output, dtb) + generate(args.sdf, args.output) diff --git a/examples/timer/timer.mk b/examples/timer/timer.mk index 42a4c9523..33e682665 100644 --- a/examples/timer/timer.mk +++ b/examples/timer/timer.mk @@ -80,7 +80,7 @@ else $(PYTHON) $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) --output . --sdf $(SYSTEM_FILE) endif $(OBJCOPY) --update-section .device_resources=timer_driver_device_resources.data timer_driver.elf - $(OBJCOPY) --update-section .timer_client_config=timer_client_client.data client.elf + $(OBJCOPY) --update-section .timer_client_config=client_timer_client_config.data client.elf touch $@ $(IMAGE_FILE) $(REPORT_FILE): $(SYSTEM_FILE) diff --git a/tools/meta/board.py b/tools/meta/board.py deleted file mode 100644 index fba22a67a..000000000 --- a/tools/meta/board.py +++ /dev/null @@ -1,211 +0,0 @@ -# Copyright 2025, UNSW -# SPDX-License-Identifier: BSD-2-Clause -from dataclasses import dataclass -from typing import List, Optional -from sdfgen import SystemDescription -from importlib.metadata import version - -ProtectionDomain = SystemDescription.ProtectionDomain - -# This file is imported by most of our meta.py scripts, so add this check -# here so that we can catch this error consistently. -assert version("sdfgen").split(".")[1] == "33", "Unexpected sdfgen version" - - -def add_x86_hpet(sdf: SystemDescription, timer_driver: ProtectionDomain): - # Timer IRQ must be the highest priority (highest vector) to ensure they are delivered - # as close as possible to the timer expiry. The highest vector is defined by (irq_user_max - irq_user_min) in seL4 source - # Since our HPET driver uses legacy IRQ routing, comparator 0's IRQ will always arrives at - # I/O APIC 0's pin 2. - hpet_irq = SystemDescription.IrqIoapic( - ioapic_id=0, - pin=2, - vector=107, - id=0, - trigger=SystemDescription.IrqIoapic.Trigger.EDGE, - ) - timer_driver.add_irq(hpet_irq) - - # paddr=0xFED00000 is a x86 convention for HPET, though it may be different on some machines depending on their BIOS. - hpet_regs = SystemDescription.MemoryRegion( - sdf, "hpet_regs", 0x1000, paddr=0xFED00000 - ) - hpet_regs_map = SystemDescription.Map(hpet_regs, 0x5000_0000, "rw", cached=False) - timer_driver.add_map(hpet_regs_map) - sdf.add_mr(hpet_regs) - - -@dataclass -class Board: - name: str - arch: SystemDescription.Arch - paddr_top: int - serial: Optional[str] = None - ethernet: Optional[str] = None - timer: Optional[str] = None - i2c: Optional[str] = None - partition: int = 0 - blk: Optional[str] = None - baud_rate: Optional[int] = None - - -# Keep this list in alphabetical order by board name -# TODO: convert to Dictionary -BOARDS: List[Board] = [ - Board( - name="cheshire", - arch=SystemDescription.Arch.RISCV64, - paddr_top=0x90000000, - serial="soc/serial@3002000", - i2c="soc/i2c@3003000", - ), - Board( - name="hifive_p550", - arch=SystemDescription.Arch.RISCV64, - paddr_top=0xA0000000, - serial="soc/serial@0x50900000", - ), - Board( - name="imx8mm_evk", - arch=SystemDescription.Arch.AARCH64, - paddr_top=0x70000000, - serial="soc@0/bus@30800000/spba-bus@30800000/serial@30890000", - timer="soc@0/bus@30000000/timer@302d0000", - ethernet="soc@0/bus@30800000/ethernet@30be0000", - ), - Board( - name="imx8mp_evk", - arch=SystemDescription.Arch.AARCH64, - paddr_top=0x70000000, - serial="soc@0/bus@30800000/spba-bus@30800000/serial@30890000", - timer="soc@0/bus@30000000/timer@302d0000", - ethernet="soc@0/bus@30800000/ethernet@30bf0000", - ), - Board( - name="imx8mp_iotgate", - arch=SystemDescription.Arch.AARCH64, - paddr_top=0x70000000, - serial="soc@0/bus@30800000/serial@30890000", - timer="soc@0/bus@30000000/timer@302d0000", - ethernet="soc@0/bus@30800000/ethernet@30bf0000", - ), - Board( - name="imx8mq_evk", - arch=SystemDescription.Arch.AARCH64, - paddr_top=0x70000000, - serial="soc@0/bus@30800000/serial@30860000", - timer="soc@0/bus@30000000/timer@302d0000", - ethernet="soc@0/bus@30800000/ethernet@30be0000", - ), - Board( - name="kria_k26", - arch=SystemDescription.Arch.AARCH64, - paddr_top=0x70000000, - timer="axi/timer@ff140000", - serial="axi/serial@ff010000", - ethernet="axi/ethernet@ff0e0000", - ), - Board( - name="maaxboard", - arch=SystemDescription.Arch.AARCH64, - paddr_top=0x70000000, - serial="soc@0/bus@30800000/serial@30860000", - timer="soc@0/bus@30000000/timer@302d0000", - ethernet="soc@0/bus@30800000/ethernet@30be0000", - blk="soc@0/bus@30800000/mmc@30b40000", - partition=2, - ), - Board( - name="odroidc2", - arch=SystemDescription.Arch.AARCH64, - paddr_top=0x60000000, - serial="soc/bus@c8100000/serial@4c0", - timer="soc/bus@c1100000/watchdog@98d0", - ethernet="soc/ethernet@c9410000", - ), - Board( - name="odroidc4", - arch=SystemDescription.Arch.AARCH64, - paddr_top=0x60000000, - i2c="soc/bus@ffd00000/i2c@1d000", - serial="soc/bus@ff800000/serial@3000", - timer="soc/bus@ffd00000/watchdog@f0d0", - ethernet="soc/ethernet@ff3f0000", - ), - Board( - name="qemu_virt_aarch64", - arch=SystemDescription.Arch.AARCH64, - paddr_top=0x6_0000_000, - serial="pl011@9000000", - timer="timer", - blk="virtio_mmio@a000200", - ethernet="virtio_mmio@a000000", - i2c=None, - ), - Board( - name="qemu_virt_riscv64", - arch=SystemDescription.Arch.RISCV64, - paddr_top=0xA_0000_000, - serial="soc/serial@10000000", - timer="soc/rtc@101000", - ethernet="soc/virtio_mmio@10001000", - blk="soc/virtio_mmio@10002000", - partition=0, - i2c=None, - ), - Board( - name="rock3b", - arch=SystemDescription.Arch.AARCH64, - paddr_top=0xEC000000, - serial="serial@fe660000", - timer="rktimer@fe5f0000", - ethernet="ethernet@fe2a0000", - baud_rate=1500000, - ), - Board( - name="rpi4b_1gb", - arch=SystemDescription.Arch.AARCH64, - paddr_top=0x2_000_000, - serial="soc/serial@7e215040", - timer="soc/timer@7e003000", - ethernet="scb/ethernet@7d580000", - ), - Board( - name="serengeti", - arch=SystemDescription.Arch.RISCV64, - paddr_top=0x90000000, - serial="soc/serial@3002000", - timer="soc/timer@300B000", - i2c="soc/i2c@3003000", - ), - Board( - name="star64", - arch=SystemDescription.Arch.RISCV64, - paddr_top=0x100000000, - serial="soc/serial@10000000", - timer="soc/timer@13050000", - ethernet="soc/ethernet@16030000", - ), - Board( - name="zcu102", - arch=SystemDescription.Arch.AARCH64, - paddr_top=0x80000000, - timer="axi/timer@ff140000", - serial="axi/serial@ff000000", - ethernet="axi/ethernet@ff0e0000", - ), - Board( - name="x86_64_generic", - arch=SystemDescription.Arch.X86_64, - paddr_top=0x70000000, - timer=None, - serial=None, - ), - Board( - name="x86_64_generic_vtx", - arch=SystemDescription.Arch.X86_64, - paddr_top=0x7FFDF000, - timer=None, - serial=None, - ), -] From a55c47c5dd5b9396a2359b0aa8be96b14b7b24d5 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Fri, 3 Jul 2026 09:47:21 +1000 Subject: [PATCH 02/15] Updated ina219 and bus scan i2c examples to use acacia Signed-off-by: Lesley Rossouw --- examples/i2c_bus_scan/i2cscan.mk | 16 ++--- examples/i2c_bus_scan/meta.py | 101 +++++++++++-------------------- examples/ina219/ina219.mk | 15 ++--- examples/ina219/meta.py | 95 ++++++++++++----------------- 4 files changed, 92 insertions(+), 135 deletions(-) diff --git a/examples/i2c_bus_scan/i2cscan.mk b/examples/i2c_bus_scan/i2cscan.mk index 4c5f44fa8..b3056d7a2 100644 --- a/examples/i2c_bus_scan/i2cscan.mk +++ b/examples/i2c_bus_scan/i2cscan.mk @@ -20,6 +20,7 @@ export PYTHONPATH SUPPORTED_BOARDS := \ odroidc4 \ + maaxboard \ serengeti include ${SDDF}/tools/make/board/common.mk @@ -75,14 +76,15 @@ $(SYSTEM_FILE): $(METAPROGRAM) $(IMAGES) $(DTB) $(PYTHON) $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) --dtb $(DTB) --output . --sdf $(SYSTEM_FILE) $(OBJCOPY) --update-section .device_resources=timer_driver_device_resources.data timer_driver.elf $(OBJCOPY) --update-section .device_resources=i2c_driver_device_resources.data i2c_driver.elf - $(OBJCOPY) --update-section .i2c_driver_config=i2c_driver.data i2c_driver.elf - $(OBJCOPY) --update-section .i2c_virt_config=i2c_virt.data i2c_virt.elf - $(OBJCOPY) --update-section .i2c_client_config=i2c_client_client_scan.data client_scan.elf - $(OBJCOPY) --update-section .timer_client_config=timer_client_client_scan.data client_scan.elf $(OBJCOPY) --update-section .device_resources=serial_driver_device_resources.data serial_driver.elf - $(OBJCOPY) --update-section .serial_driver_config=serial_driver_config.data serial_driver.elf - $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx.data serial_virt_tx.elf - $(OBJCOPY) --update-section .serial_client_config=serial_client_client_scan.data client_scan.elf + $(OBJCOPY) --update-section .i2c_driver_config=i2c_driver_i2c_driver_config.data i2c_driver.elf + $(OBJCOPY) --update-section .i2c_virt_config=i2c_virt_i2c_virt_config.data i2c_virt.elf + $(OBJCOPY) --update-section .i2c_client_config=client_scan_i2c_client_config.data client_scan.elf + $(OBJCOPY) --update-section .timer_client_config=client_scan_timer_client_config.data client_scan.elf + $(OBJCOPY) --update-section .serial_driver_config=serial_driver_serial_driver_config.data serial_driver.elf + $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx_serial_virt_tx_config.data serial_virt_tx.elf + $(OBJCOPY) --update-section .serial_client_config=client_scan_serial_client_config.data client_scan.elf + touch $@ $(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) diff --git a/examples/i2c_bus_scan/meta.py b/examples/i2c_bus_scan/meta.py index 74c4a95ea..b4bfe4554 100644 --- a/examples/i2c_bus_scan/meta.py +++ b/examples/i2c_bus_scan/meta.py @@ -4,78 +4,52 @@ import argparse from typing import List from dataclasses import dataclass -from sdfgen import SystemDescription, Sddf, DeviceTree +from acacia import System, MemoryRegion, Map, Channel, DeviceTreeBlob, ProtectionDomain sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../tools/meta") + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../") ) -from board import BOARDS -ProtectionDomain = SystemDescription.ProtectionDomain -MemoryRegion = SystemDescription.MemoryRegion -Map = SystemDescription.Map +from acacia_sddf import BOARDS, sDDFI2C, sDDFSerial, sDDFTimer +def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): + client_scan = ProtectionDomain("client_scan", "client_scan.elf", priority=1) -def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): - serial_driver = ProtectionDomain("serial_driver", "serial_driver.elf", priority=200) - # Increase the stack size as running with UBSAN uses more stack space than normal. - serial_virt_tx = ProtectionDomain( - "serial_virt_tx", "serial_virt_tx.elf", priority=199, stack_size=0x2000 + i2c = sDDFI2C(board.i2c.compatible, board.i2c.node_path, sdf, driver_prio=200, virt_prio=199) + i2c.add_client(client_scan) + sdf.add_subsystem(i2c) + + timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) + timer.add_client(client_scan) + sdf.add_subsystem(timer) + + serial = sDDFSerial( + board.serial.compatible, + board.serial.node_path, + sdf, + driver_prio=201, + virt_tx_prio=200, + allow_rx=False, + enable_color=False, + baud_rate=board.baud_rate if board.baud_rate else 115200 ) - - timer_driver = ProtectionDomain("timer_driver", "timer_driver.elf", priority=4) - i2c_driver = ProtectionDomain("i2c_driver", "i2c_driver.elf", priority=3) - i2c_virt = ProtectionDomain("i2c_virt", "i2c_virt.elf", priority=2) - client_scan = ProtectionDomain("client_scan", "client_scan.elf", priority=1) + serial.add_client(client_scan) + sdf.add_subsystem(serial) if board.name == "odroidc4": # Odroid-C4 I2C requires clocks/GPIO setup, for now we give the I2C driver # direct access. - clk_mr = MemoryRegion(sdf, "clk", 0x1000, paddr=0xFF63C000) - gpio_mr = MemoryRegion(sdf, "gpio", 0x1000, paddr=0xFF634000) - sdf.add_mr(clk_mr) - sdf.add_mr(gpio_mr) - i2c_driver.add_map(Map(clk_mr, 0x30_000_000, "rw", cached=False)) - i2c_driver.add_map(Map(gpio_mr, 0x30_100_000, "rw", cached=False)) + clk_mr = MemoryRegion("clk", 0x1000, paddr=0xFF63C000, cached=False) + gpio_mr = MemoryRegion("gpio", 0x1000, paddr=0xFF634000, cached=False) + sdf.add_memory_region(clk_mr) + sdf.add_memory_region(gpio_mr) + i2c.driver.add_map(Map(clk_mr, 0x30_000_000, "rw")) + i2c.driver.add_map(Map(gpio_mr, 0x30_100_000, "rw")) - i2c_node = dtb.node(board.i2c) - assert i2c_node is not None - timer_node = dtb.node(board.timer) - assert timer_node is not None - serial_node = dtb.node(board.serial) - assert serial_node is not None - - i2c_system = Sddf.I2c(sdf, i2c_node, i2c_driver, i2c_virt) - i2c_system.add_client(client_scan) - - timer_system = Sddf.Timer(sdf, timer_node, timer_driver) - timer_system.add_client(client_scan) - - serial_system = Sddf.Serial( - sdf, serial_node, serial_driver, serial_virt_tx, enable_color=False - ) - serial_system.add_client(client_scan) - - pds = [ - serial_driver, - serial_virt_tx, - timer_driver, - i2c_driver, - i2c_virt, - client_scan, - ] - for pd in pds: - sdf.add_pd(pd) - - assert i2c_system.connect() - assert i2c_system.serialise_config(output_dir) - assert serial_system.connect() - assert serial_system.serialise_config(output_dir) - assert timer_system.connect() - assert timer_system.serialise_config(output_dir) - - with open(f"{output_dir}/{sdf_file}", "w+") as f: - f.write(sdf.render()) + out_file = f"{output_dir}/{sdf_file}" + sdf.make_config_structs() + print(f"Saving to {out_file}") + sdf.write_xml_file(out_file) if __name__ == "__main__": @@ -90,10 +64,7 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): board = next(filter(lambda b: b.name == args.board, BOARDS)) - sdf = SystemDescription(board.arch, board.paddr_top) - sddf = Sddf(args.sddf) - - with open(args.dtb, "rb") as f: - dtb = DeviceTree(f.read()) + dtb = DeviceTreeBlob(args.dtb) + sdf = System(board.arch, board.paddr_top, dtb) generate(args.sdf, args.output, dtb) diff --git a/examples/ina219/ina219.mk b/examples/ina219/ina219.mk index ce8deb4a0..43e98809a 100644 --- a/examples/ina219/ina219.mk +++ b/examples/ina219/ina219.mk @@ -74,14 +74,15 @@ $(SYSTEM_FILE): $(METAPROGRAM) $(IMAGES) $(DTB) $(PYTHON) $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) --dtb $(DTB) --output . --sdf $(SYSTEM_FILE) $(OBJCOPY) --update-section .device_resources=timer_driver_device_resources.data timer_driver.elf $(OBJCOPY) --update-section .device_resources=i2c_driver_device_resources.data i2c_driver.elf - $(OBJCOPY) --update-section .i2c_driver_config=i2c_driver.data i2c_driver.elf - $(OBJCOPY) --update-section .i2c_virt_config=i2c_virt.data i2c_virt.elf - $(OBJCOPY) --update-section .i2c_client_config=i2c_client_client_ina.data client_ina.elf - $(OBJCOPY) --update-section .timer_client_config=timer_client_client_ina.data client_ina.elf $(OBJCOPY) --update-section .device_resources=serial_driver_device_resources.data serial_driver.elf - $(OBJCOPY) --update-section .serial_driver_config=serial_driver_config.data serial_driver.elf - $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx.data serial_virt_tx.elf - $(OBJCOPY) --update-section .serial_client_config=serial_client_client_ina.data client_ina.elf + $(OBJCOPY) --update-section .i2c_driver_config=i2c_driver_i2c_driver_config.data i2c_driver.elf + $(OBJCOPY) --update-section .i2c_virt_config=i2c_virt_i2c_virt_config.data i2c_virt.elf + $(OBJCOPY) --update-section .i2c_client_config=client_ina_i2c_client_config.data client_ina.elf + $(OBJCOPY) --update-section .timer_client_config=client_ina_timer_client_config.data client_ina.elf + $(OBJCOPY) --update-section .serial_driver_config=serial_driver_serial_driver_config.data serial_driver.elf + $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx_serial_virt_tx_config.data serial_virt_tx.elf + $(OBJCOPY) --update-section .serial_client_config=client_ina_serial_client_config.data client_ina.elf + touch $@ $(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) diff --git a/examples/ina219/meta.py b/examples/ina219/meta.py index f0b3b9c73..bd1d5127d 100644 --- a/examples/ina219/meta.py +++ b/examples/ina219/meta.py @@ -4,68 +4,54 @@ import argparse from typing import List from dataclasses import dataclass -from sdfgen import SystemDescription, Sddf, DeviceTree from importlib.metadata import version +from acacia import System, ProtectionDomain, MemoryRegion, Channel, DeviceTreeBlob, Map + sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../tools/meta") + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../") ) -from board import BOARDS - -ProtectionDomain = SystemDescription.ProtectionDomain -MemoryRegion = SystemDescription.MemoryRegion -Map = SystemDescription.Map +from acacia_sddf import BOARDS, sDDFI2C, sDDFSerial, sDDFTimer -def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): - serial_driver = ProtectionDomain("serial_driver", "serial_driver.elf", priority=200) - serial_virt_tx = ProtectionDomain( - "serial_virt_tx", "serial_virt_tx.elf", priority=199 - ) - - timer_driver = ProtectionDomain("timer_driver", "timer_driver.elf", priority=4) - i2c_driver = ProtectionDomain("i2c_driver", "i2c_driver.elf", priority=3) - i2c_virt = ProtectionDomain("i2c_virt", "i2c_virt.elf", priority=2) +def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): client_ina = ProtectionDomain("client_ina", "client_ina.elf", priority=1) - i2c_node = dtb.node(board.i2c) - assert i2c_node is not None - timer_node = dtb.node(board.timer) - assert timer_node is not None - serial_node = dtb.node(board.serial) - assert serial_node is not None - - i2c_system = Sddf.I2c(sdf, i2c_node, i2c_driver, i2c_virt) - i2c_system.add_client(client_ina) - - timer_system = Sddf.Timer(sdf, timer_node, timer_driver) - timer_system.add_client(client_ina) - - serial_system = Sddf.Serial( - sdf, serial_node, serial_driver, serial_virt_tx, enable_color=False + i2c = sDDFI2C(board.i2c.compatible, board.i2c.node_path, sdf, driver_prio=200, virt_prio=199) + i2c.add_client(client_ina) + sdf.add_subsystem(i2c) + + timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) + timer.add_client(client_ina) + sdf.add_subsystem(timer) + + serial = sDDFSerial( + board.serial.compatible, + board.serial.node_path, + sdf, + driver_prio=201, + virt_tx_prio=200, + allow_rx=False, + enable_color=False, + baud_rate=board.baud_rate if board.baud_rate else 115200 ) - serial_system.add_client(client_ina) + serial.add_client(client_ina) + sdf.add_subsystem(serial) - pds = [ - serial_driver, - serial_virt_tx, - timer_driver, - i2c_driver, - i2c_virt, - client_ina, - ] - for pd in pds: - sdf.add_pd(pd) + if board.name == "odroidc4": + # Odroid-C4 I2C requires clocks/GPIO setup, for now we give the I2C driver + # direct access. + clk_mr = MemoryRegion("clk", 0x1000, paddr=0xFF63C000, cached=False) + gpio_mr = MemoryRegion("gpio", 0x1000, paddr=0xFF634000, cached=False) + sdf.add_memory_region(clk_mr) + sdf.add_memory_region(gpio_mr) + i2c.driver.add_map(Map(clk_mr, 0x30_000_000, "rw")) + i2c.driver.add_map(Map(gpio_mr, 0x30_100_000, "rw")) - assert i2c_system.connect() - assert i2c_system.serialise_config(output_dir) - assert serial_system.connect() - assert serial_system.serialise_config(output_dir) - assert timer_system.connect() - assert timer_system.serialise_config(output_dir) - - with open(f"{output_dir}/{sdf_file}", "w+") as f: - f.write(sdf.render()) + out_file = f"{output_dir}/{sdf_file}" + sdf.make_config_structs() + print(f"Saving to {out_file}") + sdf.write_xml_file(out_file) if __name__ == "__main__": @@ -80,10 +66,7 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): board = next(filter(lambda b: b.name == args.board, BOARDS)) - sdf = SystemDescription(board.arch, board.paddr_top) - sddf = Sddf(args.sddf) - - with open(args.dtb, "rb") as f: - dtb = DeviceTree(f.read()) + dtb = DeviceTreeBlob(args.dtb) + sdf = System(board.arch, board.paddr_top, dtb) generate(args.sdf, args.output, dtb) From 013449b84c7d4bc18f5f683509f95313d7d2609f Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Fri, 3 Jul 2026 10:07:40 +1000 Subject: [PATCH 03/15] Lint Python with Acacia additions Signed-off-by: Lesley Rossouw --- acacia_sddf/__init__.py | 3 + acacia_sddf/board.py | 14 +- acacia_sddf/driver_manifest.py | 35 ++-- acacia_sddf/i2c.py | 218 ++++++++++++++++++------- acacia_sddf/sddf.py | 133 +++++++++------ acacia_sddf/serial.py | 288 ++++++++++++++++++++------------- acacia_sddf/timer.py | 125 +++++++------- examples/i2c/meta.py | 41 ++--- examples/i2c_bus_scan/meta.py | 34 ++-- examples/ina219/meta.py | 30 ++-- examples/serial/meta.py | 28 ++-- examples/timer/meta.py | 11 +- 12 files changed, 589 insertions(+), 371 deletions(-) diff --git a/acacia_sddf/__init__.py b/acacia_sddf/__init__.py index eae88ef7a..0aee53165 100644 --- a/acacia_sddf/__init__.py +++ b/acacia_sddf/__init__.py @@ -1,3 +1,6 @@ +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause + from .i2c import sDDFI2C from .timer import sDDFTimer from .serial import sDDFSerial diff --git a/acacia_sddf/board.py b/acacia_sddf/board.py index f9814bde4..3ae74958b 100644 --- a/acacia_sddf/board.py +++ b/acacia_sddf/board.py @@ -5,11 +5,13 @@ from acacia import System, ProtectionDomain, aarch64, riscv64, x86_64, Arch from importlib.metadata import version + @dataclass(frozen=True) class DriverDouble: compatible: str node_path: str + @dataclass class Board: name: str @@ -45,7 +47,9 @@ class Board: name="imx8mm_evk", arch=aarch64, paddr_top=0x70000000, - serial=DriverDouble("fsl,imx8mm-uart", "soc@0/bus@30800000/spba-bus@30800000/serial@30890000"), + serial=DriverDouble( + "fsl,imx8mm-uart", "soc@0/bus@30800000/spba-bus@30800000/serial@30890000" + ), timer=DriverDouble("fsl,imx8mm-gpt", "soc@0/bus@30000000/timer@302d0000"), ethernet=DriverDouble("", "soc@0/bus@30800000/ethernet@30be0000"), ), @@ -53,7 +57,9 @@ class Board: name="imx8mp_evk", arch=aarch64, paddr_top=0x70000000, - serial=DriverDouble("fsl,imx8mp-uart", "soc@0/bus@30800000/spba-bus@30800000/serial@30890000"), + serial=DriverDouble( + "fsl,imx8mp-uart", "soc@0/bus@30800000/spba-bus@30800000/serial@30890000" + ), timer=DriverDouble("fsl,imx8mp-gpt", "soc@0/bus@30000000/timer@302d0000"), ethernet=DriverDouble("", "soc@0/bus@30800000/ethernet@30bf0000"), ), @@ -97,7 +103,7 @@ class Board: serial=DriverDouble("amlogic,meson-gx-uart", "soc/bus@c8100000/serial@4c0"), timer=DriverDouble("amlogic,meson-gxbb-wdt", "soc/bus@c1100000/watchdog@98d0"), ethernet=DriverDouble("", "soc/ethernet@c9410000"), - baud_rate=115200 + baud_rate=115200, ), Board( name="odroidc4", @@ -107,7 +113,7 @@ class Board: serial=DriverDouble("amlogic,meson-gx-uart", "soc/bus@ff800000/serial@3000"), timer=DriverDouble("amlogic,meson-gxbb-wdt", "soc/bus@ffd00000/watchdog@f0d0"), ethernet=DriverDouble("amlogic,meson-gx-uart", "soc/ethernet@ff3f0000"), - baud_rate=115200 + baud_rate=115200, ), Board( name="qemu_virt_aarch64", diff --git a/acacia_sddf/driver_manifest.py b/acacia_sddf/driver_manifest.py index ee900612e..aecab9e30 100644 --- a/acacia_sddf/driver_manifest.py +++ b/acacia_sddf/driver_manifest.py @@ -5,6 +5,7 @@ from typing import List, Dict, Type, Union, Optional from collections import defaultdict + @dataclass class DTSRegion: name: str @@ -12,10 +13,12 @@ class DTSRegion: size: int = None dt_idx: int = None + @dataclass class DTSIRQ: dt_index: int + @dataclass class sDDFDriverConfig: """ @@ -28,9 +31,11 @@ class sDDFDriverConfig: TODO: make this better in future """ + compatible: Union[List[str], str] regions: List[DTSRegion] irqs: List[DTSIRQ] + def __post_init__(self): if type(self.compatible) is str: self.compatible = [self.compatible] @@ -51,29 +56,37 @@ class __sDDFDriverManifest: You should NOT make a new instance of this class! Use the `sDDFDriverManifest()` function to get the global instance. """ - def __init__(self): - self.map: Dict[Type[sDDFDeviceClass], Dict[str, sDDFDriverConfig]] = defaultdict(dict) - def add_driver_config(self, - subsystem_type: Type[sDDFDriverConfig], - driver_name: str, - config: sDDFDriverConfig): + def __init__(self): + self.map: Dict[Type[sDDFDeviceClass], Dict[str, sDDFDriverConfig]] = ( + defaultdict(dict) + ) + + def add_driver_config( + self, + subsystem_type: Type[sDDFDriverConfig], + driver_name: str, + config: sDDFDriverConfig, + ): # Refuse namespace collisions if driver_name in self.map[subsystem_type]: - raise ValueError(f"Driver named {driver_name} already exists for " - f"{subsystem_type}!") + raise ValueError( + f"Driver named {driver_name} already exists for " f"{subsystem_type}!" + ) self.map[subsystem_type][driver_name] = config def __getitem__(self, item): # Allow array syntax for indexing into dict of driver names per class type return self.map[item] - def get_configs_matching_compatible(self, subsystem_type: Type[sDDFDriverConfig], compat: str) -> List[sDDFDriverConfig]: + def get_configs_matching_compatible( + self, subsystem_type: Type[sDDFDriverConfig], compat: str + ) -> List[sDDFDriverConfig]: return [c for c in self.map[subsystem_type].values() if compat in c.compatible] + module_manifest = __sDDFDriverManifest() + def sDDFDriverManifest(): return module_manifest - - diff --git a/acacia_sddf/i2c.py b/acacia_sddf/i2c.py index 9e7d174ab..4df2f23d4 100644 --- a/acacia_sddf/i2c.py +++ b/acacia_sddf/i2c.py @@ -1,7 +1,18 @@ # Copyright 2026, UNSW # SPDX-License-Identifier: BSD-2-Clause -from acacia import System, Subsystem, ProtectionDomain, Channel, Map, MemoryRegion, DTBNode, DeviceTreeBlob, SchedulingProperties, ConfigStruct +from acacia import ( + System, + Subsystem, + ProtectionDomain, + Channel, + Map, + MemoryRegion, + DTBNode, + DeviceTreeBlob, + SchedulingProperties, + ConfigStruct, +) import sys, os from .driver_manifest import sDDFDriverManifest, sDDFDriverConfig, DTSIRQ, DTSRegion from .sddf import sDDFDriverClass, DeviceResourcesFactory, RegionResourceFactory @@ -12,19 +23,23 @@ I2C_NUM_BUFS = 128 # TODO: add support for dynamically sized queues I2C_PROTOCOL_MAGIC = "sDDF" + chr(0x4) + class sDDFI2C(sDDFDriverClass): - def __init__(self, - dev_compatible: str, - dev_dt_path: str, - sdf: System, - driver_prio: int, - virt_prio: int, - cpu:Optional[int]=None, - virt_elf: str="i2c_virt.elf", - driver_elf: str="i2c_driver.elf" - ): - super().__init__("i2c", dev_compatible, dev_dt_path, sdf, magic="sDDF"+chr(0x1)) - # assert driver_prio > virt_prio > 0 + def __init__( + self, + dev_compatible: str, + dev_dt_path: str, + sdf: System, + driver_prio: int, + virt_prio: int, + cpu: Optional[int] = None, + virt_elf: str = "i2c_virt.elf", + driver_elf: str = "i2c_driver.elf", + ): + super().__init__( + "i2c", dev_compatible, dev_dt_path, sdf, magic="sDDF" + chr(0x1) + ) + self.sdf = sdf self.cpu = cpu # Internal bookkeeping @@ -36,7 +51,13 @@ def __init__(self, # compiled objects itself. This will not be required once we start using # the sDDF with an SDK model, but that is for the future. self.virt_elf = virt_elf - self.driver = ProtectionDomain("i2c_driver", driver_elf, scheduling=SchedulingProperties(driver_prio), cpu=self.cpu) + self.driver = ProtectionDomain( + "i2c_driver", + driver_elf, + self.sdf, + scheduling=SchedulingProperties(driver_prio), + cpu=self.cpu, + ) # We must make the driver BEFORE we get here self.driver_dev_resources = self.create_dtb_resources(self.driver) @@ -46,6 +67,7 @@ def __init__(self, self.driver_config = None self.virt_driver_config = None self.client_configs = [] + self.channels = [] # We create queues etc. AFTER setting up the device resources to ensure that IRQ channels # have a lower value than any other channels. This is necessary because Microkit will @@ -54,35 +76,54 @@ def __init__(self, self.construct_infrastructure(virt_prio) def construct_infrastructure(self, virt_prio): - self.virt = ProtectionDomain("i2c_virt", self.virt_elf, scheduling=SchedulingProperties(virt_prio), cpu=self.cpu) - self.pds.extend([self.driver, self.virt]) + self.virt = ProtectionDomain( + "i2c_virt", + self.virt_elf, + self.sdf, + scheduling=SchedulingProperties(virt_prio), + cpu=self.cpu, + ) # Make queues - driver_req_q_mr = MemoryRegion("i2c_driver_request", 0x1000) - driver_resp_q_mr = MemoryRegion("i2c_driver_response", 0x1000) - driver_req_map = self.driver.create_automap(driver_req_q_mr, Map.Permissions(r=True, w=True)) - driver_resp_map = self.driver.create_automap(driver_resp_q_mr, Map.Permissions(r=True, w=True)) - virt_req_map = self.virt.create_automap(driver_req_q_mr, Map.Permissions(r=True, w=True)) - virt_resp_map = self.virt.create_automap(driver_resp_q_mr, Map.Permissions(r=True, w=True)) - - # Need to keep a reference to MRs to serialise them! - self.mrs.extend([driver_req_q_mr, driver_resp_q_mr]) + driver_req_q_mr = MemoryRegion("i2c_driver_request", 0x1000, self.sdf) + driver_resp_q_mr = MemoryRegion("i2c_driver_response", 0x1000, self.sdf) + driver_req_map = self.driver.create_automap( + driver_req_q_mr, Map.Permissions(r=True, w=True) + ) + driver_resp_map = self.driver.create_automap( + driver_resp_q_mr, Map.Permissions(r=True, w=True) + ) + virt_req_map = self.virt.create_automap( + driver_req_q_mr, Map.Permissions(r=True, w=True) + ) + virt_resp_map = self.virt.create_automap( + driver_resp_q_mr, Map.Permissions(r=True, w=True) + ) # Create channels driver_virt_ch = Channel( - Channel.End(self.driver, can_notify=True, can_pp=False), - Channel.End(self.virt, can_notify=True, can_pp=False) + Channel.End(self.driver, can_notify=True, can_pp=False), + Channel.End(self.virt, can_notify=True, can_pp=False), + self.sdf ) self.channels.append(driver_virt_ch) # Create config structs self.virt_driver_config = self.i2c_connection_resource_factory( - virt_req_map, virt_resp_map, I2C_NUM_BUFS, driver_virt_ch.id_for_pd(self.virt) + virt_req_map, + virt_resp_map, + I2C_NUM_BUFS, + driver_virt_ch.id_for_pd(self.virt), ) driver_virt_connection = self.i2c_connection_resource_factory( - driver_req_map, driver_resp_map, I2C_NUM_BUFS, driver_virt_ch.id_for_pd(self.driver) + driver_req_map, + driver_resp_map, + I2C_NUM_BUFS, + driver_virt_ch.id_for_pd(self.driver), + ) + self.driver_config = self.i2c_driver_config_factory( + self.driver, I2C_PROTOCOL_MAGIC, driver_virt_connection ) - self.driver_config = self.i2c_driver_config_factory(self.driver, I2C_PROTOCOL_MAGIC, driver_virt_connection) def connect_clients(self): assert self.virt is not None @@ -96,20 +137,22 @@ def connect_clients(self): virt_client_configs = [] for c in self.clients: if c.priority >= self.virt.priority: - raise SubsystemBuildError(f"Client {c} has a priority higher " - f"than virt's ({self.driver.priority})!") + raise SubsystemBuildError( + f"Client {c} has a priority higher " + f"than virt's ({self.driver.priority})!" + ) # Make channel ch = Channel( - Channel.End(c, can_notify=True, can_pp=True), - Channel.End(self.virt, can_notify=True, can_pp=False) + Channel.End(c, can_notify=True, can_pp=True), + Channel.End(self.virt, can_notify=True, can_pp=False), + self.sdf ) self.channels.append(ch) # Add request and response queue - c_req_q_mr = MemoryRegion(f"i2c_client_request_{c.name}", 0x1000) - c_resp_q_mr = MemoryRegion(f"i2c_client_response_{c.name}", 0x1000) - c_data_mr = MemoryRegion(f"i2c_client_data_{c.name}", I2C_DATA_SZ) - self.mrs.extend([c_req_q_mr, c_resp_q_mr, c_data_mr]) + c_req_q_mr = MemoryRegion(f"i2c_client_request_{c.name}", 0x1000, self.sdf) + c_resp_q_mr = MemoryRegion(f"i2c_client_response_{c.name}", 0x1000, self.sdf) + c_data_mr = MemoryRegion(f"i2c_client_data_{c.name}", I2C_DATA_SZ, self.sdf) # Create maps for clients c_req_map = c.create_automap(c_req_q_mr, Map.Permissions(r=True, w=True)) @@ -117,13 +160,23 @@ def connect_clients(self): c_data_map = c.create_automap(c_data_mr, Map.Permissions(r=True, w=True)) # Maps for virt / driver - req_map = self.virt.create_automap(c_req_q_mr, Map.Permissions(r=True, w=True)) - resp_map = self.virt.create_automap(c_resp_q_mr, Map.Permissions(r=True, w=True)) - data_map = self.driver.create_automap(c_data_mr, Map.Permissions(r=True, w=True)) + req_map = self.virt.create_automap( + c_req_q_mr, Map.Permissions(r=True, w=True) + ) + resp_map = self.virt.create_automap( + c_resp_q_mr, Map.Permissions(r=True, w=True) + ) + data_map = self.driver.create_automap( + c_data_mr, Map.Permissions(r=True, w=True) + ) # Prep config structs - virt_connection = self.i2c_connection_resource_factory(req_map, resp_map, I2C_NUM_BUFS, ch.id_for_pd(self.virt)) - client_connection = self.i2c_connection_resource_factory(c_req_map, c_resp_map, I2C_NUM_BUFS, ch.id_for_pd(c)) + virt_connection = self.i2c_connection_resource_factory( + req_map, resp_map, I2C_NUM_BUFS, ch.id_for_pd(self.virt) + ) + client_connection = self.i2c_connection_resource_factory( + c_req_map, c_resp_map, I2C_NUM_BUFS, ch.id_for_pd(c) + ) client_data = RegionResourceFactory(c_data_map) self.client_configs.append( @@ -136,10 +189,13 @@ def connect_clients(self): ) # Clients added. Finally, create virt config self.virt_config = self.i2c_virt_config_factory( - self.virt, I2C_PROTOCOL_MAGIC, len(self.clients), self.virt_driver_config, virt_client_configs + self.virt, + I2C_PROTOCOL_MAGIC, + len(self.clients), + self.virt_driver_config, + virt_client_configs, ) - def generate_config_structs(self): # We've already made our structs, just return them as a list for the serialiser driver_resources = [self.driver_dev_resources, self.driver_config] @@ -147,16 +203,23 @@ def generate_config_structs(self): return driver_resources + virt_resources + self.client_configs # ### connection config struct factory functions ### - def i2c_connection_resource_factory(self, req_q: Map, resp_q: Map, num_bufs: int, id: int) -> ConfigStruct: + def i2c_connection_resource_factory( + self, req_q: Map, resp_q: Map, num_bufs: int, id: int + ) -> ConfigStruct: fields = { "req_queue": RegionResourceFactory(req_q), "resp_queue": RegionResourceFactory(resp_q), "num_buffers": num_bufs, - "id": id + "id": id, } return ConfigStruct("i2c_connection_resource_t", fields=fields) - def i2c_client_config_factory(self, client_pd: ProtectionDomain, virt_connection: ConfigStruct, data_region: ConfigStruct) -> ConfigStruct: + def i2c_client_config_factory( + self, + client_pd: ProtectionDomain, + virt_connection: ConfigStruct, + data_region: ConfigStruct, + ) -> ConfigStruct: """ Create i2c_client_config for client_pd with serial id n """ @@ -166,11 +229,22 @@ def i2c_client_config_factory(self, client_pd: ProtectionDomain, virt_connection fields = { "magic": I2C_PROTOCOL_MAGIC, "virt": virt_connection, - "data": data_region + "data": data_region, } - return ConfigStruct("i2c_client_config_t", target_file=client_pd.prog_image, section_name="i2c_client_config", fields=fields) + return ConfigStruct( + "i2c_client_config_t", + target_file=client_pd.prog_image, + section_name="i2c_client_config", + fields=fields, + ) - def i2c_virt_client_config_factory(self, client_connection: ConfigStruct, data_size: int, driver_d_vaddr: int, client_d_vaddr: int) -> ConfigStruct: + def i2c_virt_client_config_factory( + self, + client_connection: ConfigStruct, + data_size: int, + driver_d_vaddr: int, + client_d_vaddr: int, + ) -> ConfigStruct: """ Create a i2c_virt_client_config for some client. """ @@ -178,40 +252,62 @@ def i2c_virt_client_config_factory(self, client_connection: ConfigStruct, data_s "conn": client_connection, "data_size": data_size, "driver_data_vaddr": driver_d_vaddr, - "client_data_vaddr": client_d_vaddr + "client_data_vaddr": client_d_vaddr, } return ConfigStruct("i2c_virt_client_config_t", fields=fields) - def i2c_virt_config_factory(self, virt_pd: ProtectionDomain, magic: str, num_clients: int, driver_connection: ConfigStruct, client_connections: List[ConfigStruct]) -> ConfigStruct: + def i2c_virt_config_factory( + self, + virt_pd: ProtectionDomain, + magic: str, + num_clients: int, + driver_connection: ConfigStruct, + client_connections: List[ConfigStruct], + ) -> ConfigStruct: fields = { "magic": magic, "num_clients": num_clients, "driver": driver_connection, - "clients": client_connections + "clients": client_connections, } - return ConfigStruct("i2c_virt_config_t", target_file=virt_pd.prog_image, section_name="i2c_virt_config", fields=fields) + return ConfigStruct( + "i2c_virt_config_t", + target_file=virt_pd.prog_image, + section_name="i2c_virt_config", + fields=fields, + ) - def i2c_driver_config_factory(self, driver_pd: ProtectionDomain, magic: str, virt_connection: ConfigStruct) -> ConfigStruct: + def i2c_driver_config_factory( + self, driver_pd: ProtectionDomain, magic: str, virt_connection: ConfigStruct + ) -> ConfigStruct: fields = { "magic": magic, "virt": virt_connection, } - return ConfigStruct("i2c_driver_config_t", target_file=driver_pd.prog_image, section_name="i2c_driver_config", fields=fields) + return ConfigStruct( + "i2c_driver_config_t", + target_file=driver_pd.prog_image, + section_name="i2c_driver_config", + fields=fields, + ) + # Driver configs i2c_driver_configs: Dict[str, List[sDDFDriverConfig]] = defaultdict(list) + def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverManifest().add_driver_config(sDDFI2C, driver_name, config) + # meson add_driver_config( "meson", sDDFDriverConfig( compatible="amlogic,meson-axg-i2c", regions=[DTSRegion("regs", "rw", 4096, 0)], - irqs=[DTSIRQ(0), DTSIRQ(1)] - ) + irqs=[DTSIRQ(0), DTSIRQ(1)], + ), ) # opentitan @@ -220,6 +316,6 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverConfig( compatible="eth,i2c", regions=[DTSRegion("regs", "rw", 4096, 0)], - irqs=[DTSIRQ(4), DTSIRQ(0), DTSIRQ(1), DTSIRQ(7), DTSIRQ(9)] - ) + irqs=[DTSIRQ(4), DTSIRQ(0), DTSIRQ(1), DTSIRQ(7), DTSIRQ(9)], + ), ) diff --git a/acacia_sddf/sddf.py b/acacia_sddf/sddf.py index f5068296f..c6350e766 100644 --- a/acacia_sddf/sddf.py +++ b/acacia_sddf/sddf.py @@ -2,9 +2,22 @@ # SPDX-License-Identifier: BSD-2-Clause import sys, os from typing import List, Optional, Tuple -from acacia import Subsystem, ProtectionDomain, Channel, Map, MemoryRegion, DTBNode, DeviceTreeBlob, SchedulingProperties, ConfigStruct, IRQ, System +from acacia import ( + Subsystem, + ProtectionDomain, + Channel, + Map, + MemoryRegion, + DTBNode, + DeviceTreeBlob, + SchedulingProperties, + ConfigStruct, + IRQ, + System, +) from .driver_manifest import sDDFDriverManifest, sDDFDriverConfig, DTSIRQ, DTSRegion + class sDDFDriverClass(Subsystem): """ This abstract class is inherited by all sDDF driver class implementations. @@ -13,29 +26,38 @@ class sDDFDriverClass(Subsystem): b) Parsing the device tree to set up device resources c) Providing some common utility functions for generating config structs, etc. """ - def __init__(self, - class_name: str, - dev_compatible: str, - dev_dt_path: str, - system: System, - magic: str): - super().__init__(self, class_name) + + def __init__( + self, + class_name: str, + dev_compatible: str, + dev_dt_path: str, + system: System, + magic: str, + ): + super().__init__(class_name, system) self.sdf = system self.dtb = system.dtb self.driver_magic = magic if system.dtb is None: - print(f"Initialising {class_name} driver with no DTB. Assuming this is x86 and no DTB is needed") + print( + f"Initialising {class_name} driver with no DTB. Assuming this is x86 and no DTB is needed" + ) return # Find real DTB node - print(f"Finding {class_name} compatible for {dev_compatible} -- {dev_dt_path} from {self.dtb.file_path}") + print( + f"Finding {class_name} compatible for {dev_compatible} -- {dev_dt_path} from {self.dtb.file_path}" + ) target_node = self.dtb.get_node_by_path(dev_dt_path) # make sure compatible matches! if dev_compatible not in (a_c := self.dtb.get_compatible(target_node)): - raise IOError(f"Target node {dev_dt_path} has compatible {a_c}... " - f"doesn't match expected {dev_compatible}!") + raise IOError( + f"Target node {dev_dt_path} has compatible {a_c}... " + f"doesn't match expected {dev_compatible}!" + ) # check if DTB node is "okay" if it has a status ok = self.dtb.get_node_prop(target_node, "status") @@ -45,13 +67,19 @@ def __init__(self, self.dtb_node = target_node # Find sDDF driver matching this node - matching_configs = sDDFDriverManifest().get_configs_matching_compatible(type(self), dev_compatible) + matching_configs = sDDFDriverManifest().get_configs_matching_compatible( + type(self), dev_compatible + ) if len(matching_configs) == 0: - raise RuntimeError(f"No driver config matches {dev_compatible} -> {dev_dt_path}!") + raise RuntimeError( + f"No driver config matches {dev_compatible} -> {dev_dt_path}!" + ) elif len(matching_configs) != 1: - raise RuntimeError(f"Multiple sDDF drivers satisfy {dev_compatible}! "\ - f"There whould be only one.\n{matching_configs}") + raise RuntimeError( + f"Multiple sDDF drivers satisfy {dev_compatible}! " + f"There whould be only one.\n{matching_configs}" + ) self.driver_config = matching_configs[0] def create_dtb_resources(self, driver_pd: ProtectionDomain) -> ConfigStruct: @@ -78,14 +106,13 @@ def create_dtb_resources(self, driver_pd: ProtectionDomain) -> ConfigStruct: # We generate an empty deviceresources despite it being useless, as our build system expects it. # TODO: fix that? self.__device_resources = DeviceResourcesFactory( - self.driver_magic, - [], - [], - target_file=driver_pd.prog_image + self.driver_magic, [], [], target_file=driver_pd.prog_image ) return self.__device_resources - region_maps = [] # track fields to store in DeviceResources. tuples of vaddr, offset + region_maps = ( + [] + ) # track fields to store in DeviceResources. tuples of vaddr, offset for region in self.driver_config.regions: mr = None # We name regions as [region_name]_[node_path] to avoid collisions with @@ -102,11 +129,13 @@ def create_dtb_resources(self, driver_pd: ProtectionDomain) -> ConfigStruct: # Check we can turn this into a region if region.size is not None: if r_sz < region.size: - raise RuntimeError() # todo + raise RuntimeError() # todo - if (region.size & (self.sdf.arch.default_page_size()-1)) != 0: - raise RuntimeError(f"Region {region} with size={region.size} is not aligned to"\ - f"system page size!") + if (region.size & (self.sdf.arch.default_page_size() - 1)) != 0: + raise RuntimeError( + f"Region {region} with size={region.size} is not aligned to" + f"system page size!" + ) mr_sz = region.size if region.size is not None else r_sz d_paddr = self.dtb.get_reg_paddr(self.sdf.arch, self.dtb_node, r_addr) d_reg_offset = r_addr % self.sdf.arch.default_page_size() @@ -118,10 +147,12 @@ def create_dtb_resources(self, driver_pd: ProtectionDomain) -> ConfigStruct: if len(existing_mr) == 1: mr = existing_mr[0] elif len(existing_mr) > 1: - raise RuntimeError(f"Multiple MRs with paddr={d_paddr}! -> {existing_mr}") + raise RuntimeError( + f"Multiple MRs with paddr={d_paddr}! -> {existing_mr}" + ) else: # This is new (or overlapping with a different start) - mr = MemoryRegion(region_name, mr_sz, paddr=d_paddr, cached=False) + mr = MemoryRegion(region_name, mr_sz, self.sdf, paddr=d_paddr, cached=False) else: # This is a MR that doesn't correspond to physical memory # mr = MemoryRegion(region_name, region.size) @@ -134,12 +165,13 @@ def create_dtb_resources(self, driver_pd: ProtectionDomain) -> ConfigStruct: # Assumes permission string is correctly formatted. Non r/w/x chars are ignored d_map = driver_pd.create_automap(mr, region.perms if region.perms else "rw") region_maps.append((d_map, d_reg_offset)) - self.sdf.add_memory_region(mr) # Next: set up IRQs irqs_from_prop = self.dtb.get_parsed_irqs(self.dtb_node, self.sdf.arch) - if len(irqs_from_prop) == 0 and (t:= len(self.driver_config.irqs)) != 0: - raise RuntimeError(f"Driver config expects {t} irqs but none found in node!") + if len(irqs_from_prop) == 0 and (t := len(self.driver_config.irqs)) != 0: + raise RuntimeError( + f"Driver config expects {t} irqs but none found in node!" + ) irq_ids = [] for irq in self.driver_config.irqs: @@ -148,35 +180,33 @@ def create_dtb_resources(self, driver_pd: ProtectionDomain) -> ConfigStruct: # Finally: make config struct self.__device_resources = DeviceResourcesFactory( - self.driver_magic, - region_maps, - irq_ids, - target_file=driver_pd.prog_image + self.driver_magic, region_maps, irq_ids, target_file=driver_pd.prog_image ) return self.__device_resources -def RegionResourceFactory(map: Map, section_name: Optional[str] = None, offset = 0): - fields = { - "vaddr": map.vaddr + offset, - "size": map.mr.size - } +def RegionResourceFactory(map: Map, section_name: Optional[str] = None, offset=0): + fields = {"vaddr": map.vaddr + offset, "size": map.mr.size} return ConfigStruct("region_resource_t", section_name=section_name, fields=fields) + def DeviceRegionResourceFactory(region: ConfigStruct, io_addr: int): - fields = { - "region": region, - "io_addr": io_addr - } + fields = {"region": region, "io_addr": io_addr} return ConfigStruct("device_region_resource_t", fields=fields) + def DeviceIRQResourceFactory(id: int): - fields = { - "id": id - } + fields = {"id": id} return ConfigStruct("device_irq_resource_t", fields=fields) -def DeviceResourcesFactory(magic_str: str, maps_offsets: List[Tuple[Map,int]], irq_ids: List[int], target_file: str, section_name = "device_resources"): + +def DeviceResourcesFactory( + magic_str: str, + maps_offsets: List[Tuple[Map, int]], + irq_ids: List[int], + target_file: str, + section_name="device_resources", +): region_structs = [ DeviceRegionResourceFactory(RegionResourceFactory(m, offset=o), m.mr.paddr) for m, o in maps_offsets @@ -187,6 +217,11 @@ def DeviceResourcesFactory(magic_str: str, maps_offsets: List[Tuple[Map,int]], i "num_regions": len(region_structs), "num_irqs": len(irq_structs), "regions": region_structs, - "irqs": irq_structs + "irqs": irq_structs, } - return ConfigStruct("device_resources_t", section_name=section_name, fields=fields, target_file=target_file) + return ConfigStruct( + "device_resources_t", + section_name=section_name, + fields=fields, + target_file=target_file, + ) diff --git a/acacia_sddf/serial.py b/acacia_sddf/serial.py index 3632ce8cd..eb0ef36af 100644 --- a/acacia_sddf/serial.py +++ b/acacia_sddf/serial.py @@ -1,7 +1,19 @@ # Copyright 2026, UNSW # SPDX-License-Identifier: BSD-2-Clause -from acacia import System, Subsystem, ProtectionDomain, Channel, Map, MemoryRegion, DTBNode, DeviceTreeBlob, SchedulingProperties, ConfigStruct, SubsystemBuildError +from acacia import ( + System, + Subsystem, + ProtectionDomain, + Channel, + Map, + MemoryRegion, + DTBNode, + DeviceTreeBlob, + SchedulingProperties, + ConfigStruct, + SubsystemBuildError, +) from acacia.x86 import IOPort from acacia.irq import IrqIoapic import sys, os @@ -14,27 +26,31 @@ SERIAL_MAX_BEGIN_STR_LEN = 128 SERIAL_PROTOCOL_MAGIC = "sDDF" + chr(0x3) + class sDDFSerial(sDDFDriverClass): - def __init__(self, - dev_compatible: str, - dev_dt_path: str, - sdf: System, - driver_prio: int, - virt_tx_prio: int, - allow_rx: bool = False, - virt_rx_prio: Optional[int] = None, - cpu: Optional[int] = None, - enable_color: bool = True, - baud_rate: int = 115200, - begin_str: str = SERIAL_DEFAULT_BEGIN_STR, - # We leave this as configurable just in case... - data_size: int = 0x10000, - queue_size: int = 0x1000, - virt_rx_elf: str = "serial_virt_rx.elf", - virt_tx_elf: str = "serial_virt_tx.elf", - driver_elf: str = "serial_driver.elf" - ): - super().__init__("serial", dev_compatible, dev_dt_path, sdf, magic="sDDF"+chr(0x1)) + def __init__( + self, + dev_compatible: str, + dev_dt_path: str, + sdf: System, + driver_prio: int, + virt_tx_prio: int, + allow_rx: bool = False, + virt_rx_prio: Optional[int] = None, + cpu: Optional[int] = None, + enable_color: bool = True, + baud_rate: int = 115200, + begin_str: str = SERIAL_DEFAULT_BEGIN_STR, + # We leave this as configurable just in case... + data_size: int = 0x10000, + queue_size: int = 0x1000, + virt_rx_elf: str = "serial_virt_rx.elf", + virt_tx_elf: str = "serial_virt_tx.elf", + driver_elf: str = "serial_driver.elf", + ): + super().__init__( + "serial", dev_compatible, dev_dt_path, sdf, magic="sDDF" + chr(0x1) + ) assert driver_prio > virt_tx_prio > 0 if allow_rx: # Default RX prio == TX prio @@ -59,8 +75,11 @@ def __init__(self, self.virt_rx_elf = virt_rx_elf self.virt_tx_elf = virt_tx_elf self.driver = ProtectionDomain( - "serial_driver", driver_elf, - scheduling=SchedulingProperties(driver_prio), cpu=self.cpu + "serial_driver", + driver_elf, + self.sdf, + scheduling=SchedulingProperties(driver_prio), + cpu=self.cpu, ) # Do x86 stuff if self.sdf.dtb is None: @@ -76,30 +95,34 @@ def __init__(self, self.virt_tx_driver_conn = None self.virt_rx_driver_conn = None self.client_configs = [] - self.construct_infrastructure(virt_rx_prio if virt_rx_prio else -1, virt_tx_prio) + self.construct_infrastructure( + virt_rx_prio if virt_rx_prio else -1, virt_tx_prio + ) def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): self.virt_tx = ProtectionDomain( - "serial_virt_tx", self.virt_tx_elf, - scheduling=SchedulingProperties(virt_tx_prio), cpu=self.cpu + "serial_virt_tx", + self.virt_tx_elf, + self.sdf, + scheduling=SchedulingProperties(virt_tx_prio), + cpu=self.cpu, ) - self.pds.append(self.driver) - self.pds.append(self.virt_tx) - if self.allow_rx and virt_rx_prio > 0: self.virt_rx = ProtectionDomain( - "serial_virt_rx", self.virt_rx_elf, - scheduling=SchedulingProperties(virt_rx_prio), cpu=self.cpu + "serial_virt_rx", + self.virt_rx_elf, + self.sdf, + scheduling=SchedulingProperties(virt_rx_prio), + cpu=self.cpu, ) - self.pds.append(self.virt_rx) - driver_tx_queue_mr = MemoryRegion("serial_driver_tx_queue", self.queue_size) + driver_tx_queue_mr = MemoryRegion("serial_driver_tx_queue", self.queue_size, self.sdf) driver_tx_data_mr = MemoryRegion( "serial_driver_tx_data", self.data_size * 2 if self.enable_color else self.data_size, - cached=True + self.sdf, + cached=True, ) - self.mrs.extend([driver_tx_queue_mr, driver_tx_data_mr]) driver_tx_queue_map = self.driver.create_automap( driver_tx_queue_mr, Map.Permissions(r=True, w=True) @@ -116,28 +139,25 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): driver_virt_tx_ch = Channel( Channel.End(self.driver, can_notify=True, can_pp=False), - Channel.End(self.virt_tx, can_notify=True, can_pp=False) + Channel.End(self.virt_tx, can_notify=True, can_pp=False), + self.sdf ) - self.channels.append(driver_virt_tx_ch) driver_tx_conn = self.serial_connection_resource_factory( - driver_tx_queue_map, driver_tx_data_map, - driver_virt_tx_ch.id_for_pd(self.driver) + driver_tx_queue_map, + driver_tx_data_map, + driver_virt_tx_ch.id_for_pd(self.driver), ) self.virt_tx_driver_conn = self.serial_connection_resource_factory( - virt_tx_queue_map, virt_tx_data_map, - driver_virt_tx_ch.id_for_pd(self.virt_tx) + virt_tx_queue_map, + virt_tx_data_map, + driver_virt_tx_ch.id_for_pd(self.virt_tx), ) driver_rx_conn = None if self.virt_rx: - driver_rx_queue_mr = MemoryRegion( - "serial_driver_rx_queue", self.queue_size - ) - driver_rx_data_mr = MemoryRegion( - "serial_driver_rx_data", self.data_size - ) - self.mrs.extend([driver_rx_queue_mr, driver_rx_data_mr]) + driver_rx_queue_mr = MemoryRegion("serial_driver_rx_queue", self.queue_size, self.sdf) + driver_rx_data_mr = MemoryRegion("serial_driver_rx_data", self.data_size, self.sdf) driver_rx_queue_map = self.driver.create_automap( driver_rx_queue_mr, Map.Permissions(r=True, w=True) @@ -154,22 +174,28 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): driver_virt_rx_ch = Channel( Channel.End(self.driver, can_notify=True, can_pp=False), - Channel.End(self.virt_rx, can_notify=True, can_pp=False) + Channel.End(self.virt_rx, can_notify=True, can_pp=False), + self.sdf ) - self.channels.append(driver_virt_rx_ch) driver_rx_conn = self.serial_connection_resource_factory( - driver_rx_queue_map, driver_rx_data_map, - driver_virt_rx_ch.id_for_pd(self.driver) + driver_rx_queue_map, + driver_rx_data_map, + driver_virt_rx_ch.id_for_pd(self.driver), ) self.virt_rx_driver_conn = self.serial_connection_resource_factory( - virt_rx_queue_map, virt_rx_data_map, - driver_virt_rx_ch.id_for_pd(self.virt_rx) + virt_rx_queue_map, + virt_rx_data_map, + driver_virt_rx_ch.id_for_pd(self.virt_rx), ) self.driver_config = self.serial_driver_config_factory( - self.driver, SERIAL_PROTOCOL_MAGIC, self.baud_rate, 1 if self.virt_rx else 0, - driver_tx_conn, driver_rx_conn + self.driver, + SERIAL_PROTOCOL_MAGIC, + self.baud_rate, + 1 if self.virt_rx else 0, + driver_tx_conn, + driver_rx_conn, ) def connect_clients(self): @@ -193,9 +219,8 @@ def connect_clients(self): ) # TX connection: virt_tx -> client - tx_queue_mr = MemoryRegion(f"serial_tx_queue_{c.name}", self.queue_size) - tx_data_mr = MemoryRegion(f"serial_tx_data_{c.name}", self.data_size) - self.mrs.extend([tx_queue_mr, tx_data_mr]) + tx_queue_mr = MemoryRegion(f"serial_tx_queue_{c.name}", self.queue_size, self.sdf) + tx_data_mr = MemoryRegion(f"serial_tx_data_{c.name}", self.data_size, self.sdf) virt_tx_tx_queue_map = self.virt_tx.create_automap( tx_queue_mr, Map.Permissions(r=True, w=True) @@ -212,13 +237,12 @@ def connect_clients(self): tx_ch = Channel( Channel.End(self.virt_tx, can_notify=True, can_pp=False), - Channel.End(c, can_notify=True, can_pp=False) + Channel.End(c, can_notify=True, can_pp=False), + self.sdf ) - self.channels.append(tx_ch) virt_tx_conn = self.serial_connection_resource_factory( - virt_tx_tx_queue_map, virt_tx_tx_data_map, - tx_ch.id_for_pd(self.virt_tx) + virt_tx_tx_queue_map, virt_tx_tx_data_map, tx_ch.id_for_pd(self.virt_tx) ) client_tx_conn = self.serial_connection_resource_factory( c_tx_queue_map, c_tx_data_map, tx_ch.id_for_pd(c) @@ -231,9 +255,8 @@ def connect_clients(self): # RX connection (if enabled): virt_rx -> client client_rx_conn = None if self.virt_rx: - rx_queue_mr = MemoryRegion(f"serial_rx_queue_{c.name}", self.queue_size) - rx_data_mr = MemoryRegion(f"serial_rx_data_{c.name}", self.data_size) - self.mrs.extend([rx_queue_mr, rx_data_mr]) + rx_queue_mr = MemoryRegion(f"serial_rx_queue_{c.name}", self.queue_size, self.sdf) + rx_data_mr = MemoryRegion(f"serial_rx_data_{c.name}", self.data_size, self.sdf) virt_rx_rx_queue_map = self.virt_rx.create_automap( rx_queue_mr, Map.Permissions(r=True, w=True) @@ -250,13 +273,14 @@ def connect_clients(self): rx_ch = Channel( Channel.End(self.virt_rx, can_notify=True, can_pp=False), - Channel.End(c, can_notify=True, can_pp=False) + Channel.End(c, can_notify=True, can_pp=False), + self.sdf ) - self.channels.append(rx_ch) virt_rx_conn = self.serial_connection_resource_factory( - virt_rx_rx_queue_map, virt_rx_rx_data_map, - rx_ch.id_for_pd(self.virt_rx) + virt_rx_rx_queue_map, + virt_rx_rx_data_map, + rx_ch.id_for_pd(self.virt_rx), ) client_rx_conn = self.serial_connection_resource_factory( c_rx_queue_map, c_rx_data_map, rx_ch.id_for_pd(c) @@ -264,19 +288,29 @@ def connect_clients(self): virt_rx_client_conns.append(virt_rx_conn) client_configs.append( - self.serial_client_config_factory(c, SERIAL_PROTOCOL_MAGIC, client_tx_conn, client_rx_conn) + self.serial_client_config_factory( + c, SERIAL_PROTOCOL_MAGIC, client_tx_conn, client_rx_conn + ) ) self.virt_tx_config = self.serial_virt_tx_config_factory( - self.virt_tx, SERIAL_PROTOCOL_MAGIC, len(self.clients), self.virt_tx_driver_conn, - virt_tx_client_structs, 1 if self.enable_color else 0, - 1 if self.virt_rx else 0, self.begin_str + self.virt_tx, + SERIAL_PROTOCOL_MAGIC, + len(self.clients), + self.virt_tx_driver_conn, + virt_tx_client_structs, + 1 if self.enable_color else 0, + 1 if self.virt_rx else 0, + self.begin_str, ) if self.virt_rx: self.virt_rx_config = self.serial_virt_rx_config_factory( - self.virt_rx, SERIAL_PROTOCOL_MAGIC, len(self.clients), self.virt_rx_driver_conn, - virt_rx_client_conns + self.virt_rx, + SERIAL_PROTOCOL_MAGIC, + len(self.clients), + self.virt_rx_driver_conn, + virt_rx_client_conns, ) self.client_configs = client_configs @@ -304,26 +338,35 @@ def serial_connection_resource_factory( return ConfigStruct("serial_connection_resource_t", fields=fields) def serial_driver_config_factory( - self, driver_pd: ProtectionDomain, magic: str, baud_rate: int, rx_enabled: int, + self, + driver_pd: ProtectionDomain, + magic: str, + baud_rate: int, + rx_enabled: int, tx_connection: ConfigStruct, - rx_connection: Optional[ConfigStruct] = None + rx_connection: Optional[ConfigStruct] = None, ) -> ConfigStruct: fields = { "magic": magic, "default_baud": baud_rate, "rx_enabled": rx_enabled, "tx": tx_connection, - "rx": rx_connection if rx_connection else 0 + "rx": rx_connection if rx_connection else 0, } return ConfigStruct( - "serial_driver_config_t", target_file=driver_pd.prog_image, - section_name="serial_driver_config", fields=fields + "serial_driver_config_t", + target_file=driver_pd.prog_image, + section_name="serial_driver_config", + fields=fields, ) def serial_virt_rx_config_factory( - self, virt_rx_pd: ProtectionDomain, magic:str, num_clients: int, + self, + virt_rx_pd: ProtectionDomain, + magic: str, + num_clients: int, driver_connection: ConfigStruct, - client_connections: List[ConfigStruct] + client_connections: List[ConfigStruct], ) -> ConfigStruct: fields = { "magic": magic, @@ -331,11 +374,13 @@ def serial_virt_rx_config_factory( "driver": driver_connection, "clients": client_connections, "switch_char": chr(28), - "terminate_num_char": '\r', + "terminate_num_char": "\r", } return ConfigStruct( - "serial_virt_rx_config_t", target_file=virt_rx_pd.prog_image, - section_name="serial_virt_rx_config", fields=fields + "serial_virt_rx_config_t", + target_file=virt_rx_pd.prog_image, + section_name="serial_virt_rx_config", + fields=fields, ) def serial_virt_tx_client_config_factory( @@ -348,10 +393,15 @@ def serial_virt_tx_client_config_factory( return ConfigStruct("serial_virt_tx_client_t", fields=fields) def serial_virt_tx_config_factory( - self, virt_tx_pd: ProtectionDomain, magic:str, num_clients: int, + self, + virt_tx_pd: ProtectionDomain, + magic: str, + num_clients: int, driver_connection: ConfigStruct, - client_connections: List[ConfigStruct], enable_colour: int, - enable_rx: int, begin_str: str + client_connections: List[ConfigStruct], + enable_colour: int, + enable_rx: int, + begin_str: str, ) -> ConfigStruct: fields = { "magic": magic, @@ -363,22 +413,29 @@ def serial_virt_tx_config_factory( "enable_rx": enable_rx, } return ConfigStruct( - "serial_virt_tx_config_t", target_file=virt_tx_pd.prog_image, - section_name="serial_virt_tx_config", fields=fields + "serial_virt_tx_config_t", + target_file=virt_tx_pd.prog_image, + section_name="serial_virt_tx_config", + fields=fields, ) def serial_client_config_factory( - self, client_pd: ProtectionDomain, magic, tx_connection: ConfigStruct, - rx_connection: Optional[ConfigStruct] = None + self, + client_pd: ProtectionDomain, + magic, + tx_connection: ConfigStruct, + rx_connection: Optional[ConfigStruct] = None, ) -> ConfigStruct: fields = { "magic": magic, "tx": tx_connection, - "rx": rx_connection if rx_connection else 0 + "rx": rx_connection if rx_connection else 0, } return ConfigStruct( - "serial_client_config_t", target_file=client_pd.prog_image, - section_name="serial_client_config", fields=fields + "serial_client_config_t", + target_file=client_pd.prog_image, + section_name="serial_client_config", + fields=fields, ) # x86 Util @@ -386,22 +443,25 @@ def add_x86_serial_port(self): # The serial device does not located on PCIe and the interrupts are # conventionally configured by BIOS. The IRQ number can be read from # Linux or APCI tables. - self.driver.add_ioport(IOPort(0x3f8, 8, 0)) + self.driver.add_ioport(IOPort(0x3F8, 8, 0)) self.driver.add_irq(IrqIoapic(0, 4, 0, id=1)) + # Driver configs serial_driver_configs: Dict[str, List[sDDFDriverConfig]] = defaultdict(list) + def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverManifest().add_driver_config(sDDFSerial, driver_name, config) + add_driver_config( "meson", sDDFDriverConfig( ["amlogic,meson-gx-uart", "amlogic,meson-ao-uart"], [DTSRegion("regs", "rw", 4096, 0)], - [DTSIRQ(0)] - ) + [DTSIRQ(0)], + ), ) add_driver_config( @@ -409,8 +469,8 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverConfig( compatible="arm,pl011", regions=[DTSRegion("regs", "rw", 4096, 0)], - irqs=[DTSIRQ(0)] - ) + irqs=[DTSIRQ(0)], + ), ) add_driver_config( @@ -418,18 +478,23 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverConfig( compatible=["fsl,imx8mq-uart", "fsl,imx8mm-uart", "fsl,imx8mp-uart"], regions=[DTSRegion("regs", "rw", 4096, 0)], - irqs=[DTSIRQ(0)] - ) + irqs=[DTSIRQ(0)], + ), ) # ns16550a add_driver_config( "ns16550a", sDDFDriverConfig( - compatible=["starfive,jh7110-uart", "ns16550a", "brcm,bcm2835-aux-uart", "snps,dw-apb-uart"], + compatible=[ + "starfive,jh7110-uart", + "ns16550a", + "brcm,bcm2835-aux-uart", + "snps,dw-apb-uart", + ], regions=[DTSRegion("regs", "rw", 4096, 0)], - irqs=[DTSIRQ(0)] - ) + irqs=[DTSIRQ(0)], + ), ) # virtio @@ -441,10 +506,10 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): DTSRegion("regs", "rw", 4096, 0), DTSRegion("hw_ring_buffer", size=65536), DTSRegion("virtio_rx_buf", size=4096), - DTSRegion("virtio_tx_buf", size=4096) + DTSRegion("virtio_tx_buf", size=4096), ], - irqs=[DTSIRQ(0)] - ) + irqs=[DTSIRQ(0)], + ), ) # xlnx @@ -453,7 +518,6 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverConfig( compatible="xlnx,zynqmp-uart", regions=[DTSRegion("regs", dt_idx=0)], - irqs=[DTSIRQ(0)] - ) + irqs=[DTSIRQ(0)], + ), ) - diff --git a/acacia_sddf/timer.py b/acacia_sddf/timer.py index 26c0e641a..dead29d4d 100644 --- a/acacia_sddf/timer.py +++ b/acacia_sddf/timer.py @@ -1,25 +1,46 @@ # Copyright 2026, UNSW # SPDX-License-Identifier: BSD-2-Clause -from acacia import System, Subsystem, ProtectionDomain, Channel, Map, MemoryRegion, DTBNode, DeviceTreeBlob, SchedulingProperties, ConfigStruct, IRQ, SubsystemBuildError +from acacia import ( + System, + Subsystem, + ProtectionDomain, + Channel, + Map, + MemoryRegion, + DTBNode, + DeviceTreeBlob, + SchedulingProperties, + ConfigStruct, + IRQ, + SubsystemBuildError, +) import sys, os from .driver_manifest import sDDFDriverManifest, sDDFDriverConfig, DTSIRQ, DTSRegion from .sddf import sDDFDriverClass, DeviceResourcesFactory, RegionResourceFactory from collections import defaultdict from typing import List, Dict, Type, Union, Optional + class sDDFTimer(sDDFDriverClass): - def __init__(self, - dev_compatible: str, - dev_dt_path: str, - sdf: System, - driver_prio: int=254, - cpu:Optional[int]=None, - driver_elf: str="timer_driver.elf" - ): - super().__init__("timer", dev_compatible, dev_dt_path, sdf, magic="sDDF"+chr(1)) - self.driver = ProtectionDomain("timer_driver", driver_elf, scheduling=SchedulingProperties(driver_prio, passive=True)) - self.pds.append(self.driver) + def __init__( + self, + dev_compatible: str, + dev_dt_path: str, + sdf: System, + driver_prio: int = 254, + cpu: Optional[int] = None, + driver_elf: str = "timer_driver.elf", + ): + super().__init__( + "timer", dev_compatible, dev_dt_path, sdf, magic="sDDF" + chr(1) + ) + self.driver = ProtectionDomain( + "timer_driver", + driver_elf, + self.sdf, + scheduling=SchedulingProperties(driver_prio, passive=True), + ) self.cpu = cpu # Create driver resources before doing anything else @@ -32,12 +53,14 @@ def connect_clients(self): # ... that's it! for c in self.clients: if c.priority > self.driver.priority: - raise SubsystemBuildError(f"Client {c} has higher priority than timer driver!") + raise SubsystemBuildError( + f"Client {c} has higher priority than timer driver!" + ) ch = Channel( - Channel.End(c, can_notify=False, can_pp=True), - Channel.End(self.driver, can_notify=True, can_pp=False) + Channel.End(c, can_notify=False, can_pp=True), + Channel.End(self.driver, can_notify=True, can_pp=False), + self.sdf ) - self.channels.append(ch) self.client_configs.append( self.timer_client_config_factory(c, ch.id_for_pd(c)) ) @@ -46,17 +69,20 @@ def generate_config_structs(self): # We've already made our structs return [self.driver_dev_resources] + self.client_configs - def timer_client_config_factory(self, client_pd: ProtectionDomain, driver_id: int) -> ConfigStruct: + def timer_client_config_factory( + self, client_pd: ProtectionDomain, driver_id: int + ) -> ConfigStruct: """ create timer_client_config for client_pd with serial id n """ # invariant: this PD only is a client to timer one time. - fields = { - "magic": "sDDF"+chr(6), - "driver_id": driver_id - } - return ConfigStruct("timer_client_config_t", target_file=client_pd.prog_image, section_name="timer_client_config", fields=fields) - + fields = {"magic": "sDDF" + chr(6), "driver_id": driver_id} + return ConfigStruct( + "timer_client_config_t", + target_file=client_pd.prog_image, + section_name="timer_client_config", + fields=fields, + ) # x86 utility # NOTE: is this safe to call automatically? I currently am assuming we want manual @@ -67,43 +93,35 @@ def add_x86_hpet(self, sdf: System): # Since our HPET driver uses legacy IRQ routing, comparator 0's IRQ will always arrives at # I/O APIC 0's pin 2. from acacia.irq import IrqIoapic + hpet_irq = IrqIoapic( - ioapic_id=0, - pin=2, - vector=107, - id=0, - trigger=IRQ.Trigger.EDGE + ioapic_id=0, pin=2, vector=107, id=0, trigger=IRQ.Trigger.EDGE ) self.driver.add_irq(hpet_irq) # paddr=0xFED00000 is a x86 convention for HPET, though it may be different on some machines depending on their BIOS. - hpet_regs = MemoryRegion( - "hpet_regs", 0x1000, paddr=0xFED00000 - ) + hpet_regs = MemoryRegion("hpet_regs", 0x1000, paddr=0xFED00000) hpet_regs_map = Map(hpet_regs, 0x5000_0000, "rw") self.driver.add_map(hpet_regs_map) sdf.add_memory_region(hpet_regs) + # Driver configs def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverManifest().add_driver_config(sDDFTimer, driver_name, config) + # pulp add_driver_config( "apb_timer", sDDFDriverConfig( compatible="pulp,apb_timer", regions=[DTSRegion("regs", "rw", 4096, 0)], - irqs=[DTSIRQ(0), DTSIRQ(1), DTSIRQ(2), DTSIRQ(3)] - ) + irqs=[DTSIRQ(0), DTSIRQ(1), DTSIRQ(2), DTSIRQ(3)], + ), ) # armv8 add_driver_config( - "arm", - sDDFDriverConfig( - compatible="arm,armv8-timer", - regions=[], - irqs=[DTSIRQ(1)] - ) + "arm", sDDFDriverConfig(compatible="arm,armv8-timer", regions=[], irqs=[DTSIRQ(1)]) ) # bcm2835 @@ -112,8 +130,8 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverConfig( compatible="brcm,bcm2835-system-timer", regions=[DTSRegion("regs", dt_idx=0)], - irqs=[DTSIRQ(1)] - ) + irqs=[DTSIRQ(1)], + ), ) # cdns @@ -122,8 +140,8 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverConfig( compatible="cdns,ttc", regions=[DTSRegion("regs", dt_idx=0)], - irqs=[DTSIRQ(0), DTSIRQ(1)] - ) + irqs=[DTSIRQ(0), DTSIRQ(1)], + ), ) # goldfish @@ -132,8 +150,8 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverConfig( compatible="google,goldfish-rtc", regions=[DTSRegion("regs", dt_idx=0)], - irqs=[DTSIRQ(0)] - ) + irqs=[DTSIRQ(0)], + ), ) # imx8 @@ -142,8 +160,8 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverConfig( compatible=["fsl,imx8mm-gpt", "fsl,imx8mq-gpt", "fsl,imx8mp-gpt"], regions=[DTSRegion("regs", "rw", 65536, 0)], - irqs=[DTSIRQ(0)] - ) + irqs=[DTSIRQ(0)], + ), ) # jh7110 @@ -152,8 +170,8 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverConfig( compatible="starfive,jh7110-timer", regions=[DTSRegion("regs", "rw", 4096, 0)], - irqs=[DTSIRQ(0), DTSIRQ(1)] - ) + irqs=[DTSIRQ(0), DTSIRQ(1)], + ), ) # meson_gxbb @@ -162,8 +180,8 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverConfig( compatible="amlogic,meson-gxbb-wdt", regions=[DTSRegion("regs", "rw", 4096, 0)], - irqs=[DTSIRQ(0)] - ) + irqs=[DTSIRQ(0)], + ), ) # rk3568 @@ -172,7 +190,6 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverConfig( compatible="rockchip,rk3568-timer", regions=[DTSRegion("regs", dt_idx=0)], - irqs=[DTSIRQ(0), DTSIRQ(1)] - ) + irqs=[DTSIRQ(0), DTSIRQ(1)], + ), ) - diff --git a/examples/i2c/meta.py b/examples/i2c/meta.py index 6d57aa80f..6d8532755 100644 --- a/examples/i2c/meta.py +++ b/examples/i2c/meta.py @@ -6,52 +6,45 @@ from dataclasses import dataclass from acacia import System, ProtectionDomain, MemoryRegion, Channel, DeviceTreeBlob, Map -sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../") -) +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) from acacia_sddf import BOARDS, sDDFI2C, sDDFSerial, sDDFTimer + def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): - client_pn532 = ProtectionDomain("client_pn532", "client_pn532.elf", priority=1) - client_ds3231 = ProtectionDomain("client_ds3231", "client_ds3231.elf", priority=1) + client_pn532 = ProtectionDomain("client_pn532", "client_pn532.elf", sdf, priority=1) + client_ds3231 = ProtectionDomain("client_ds3231", "client_ds3231.elf", sdf, priority=1) - i2c = sDDFI2C(board.i2c.compatible, board.i2c.node_path, sdf, driver_prio=200, virt_prio=199) + i2c = sDDFI2C( + board.i2c.compatible, board.i2c.node_path, sdf, driver_prio=200, virt_prio=199 + ) i2c.add_client(client_ds3231) i2c.add_client(client_pn532) - sdf.add_subsystem(i2c) timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) timer.add_client(client_ds3231) timer.add_client(client_pn532) - sdf.add_subsystem(timer) serial = sDDFSerial( - board.serial.compatible, - board.serial.node_path, - sdf, - driver_prio=201, - virt_tx_prio=200, - allow_rx=False, - enable_color=False, - baud_rate=board.baud_rate if board.baud_rate else 115200 + board.serial.compatible, + board.serial.node_path, + sdf, + driver_prio=201, + virt_tx_prio=200, + allow_rx=False, + enable_color=False, + baud_rate=board.baud_rate if board.baud_rate else 115200, ) serial.add_client(client_ds3231) serial.add_client(client_pn532) - sdf.add_subsystem(serial) if board.name == "odroidc4": # Odroid-C4 I2C requires clocks/GPIO setup, for now we give the I2C driver # direct access. - clk_mr = MemoryRegion("clk", 0x1000, paddr=0xFF63C000, cached=False) - gpio_mr = MemoryRegion("gpio", 0x1000, paddr=0xFF634000, cached=False) - sdf.add_memory_region(clk_mr) - sdf.add_memory_region(gpio_mr) + clk_mr = MemoryRegion("clk", 0x1000, sdf, paddr=0xFF63C000, cached=False) + gpio_mr = MemoryRegion("gpio", 0x1000, sdf, paddr=0xFF634000, cached=False) i2c.driver.add_map(Map(clk_mr, 0x30_000_000, "rw")) i2c.driver.add_map(Map(gpio_mr, 0x30_100_000, "rw")) - for pd in [client_ds3231, client_pn532]: - sdf.add_pd(pd) - out_file = f"{output_dir}/{sdf_file}" sdf.make_config_structs() print(f"Saving to {out_file}") diff --git a/examples/i2c_bus_scan/meta.py b/examples/i2c_bus_scan/meta.py index b4bfe4554..c26ba3dcc 100644 --- a/examples/i2c_bus_scan/meta.py +++ b/examples/i2c_bus_scan/meta.py @@ -6,41 +6,39 @@ from dataclasses import dataclass from acacia import System, MemoryRegion, Map, Channel, DeviceTreeBlob, ProtectionDomain -sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../") -) +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) from acacia_sddf import BOARDS, sDDFI2C, sDDFSerial, sDDFTimer + def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): - client_scan = ProtectionDomain("client_scan", "client_scan.elf", priority=1) + client_scan = ProtectionDomain("client_scan", "client_scan.elf", sdf, priority=1) - i2c = sDDFI2C(board.i2c.compatible, board.i2c.node_path, sdf, driver_prio=200, virt_prio=199) + i2c = sDDFI2C( + board.i2c.compatible, board.i2c.node_path, sdf, driver_prio=200, virt_prio=199 + ) i2c.add_client(client_scan) - sdf.add_subsystem(i2c) timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) timer.add_client(client_scan) - sdf.add_subsystem(timer) serial = sDDFSerial( - board.serial.compatible, - board.serial.node_path, - sdf, - driver_prio=201, - virt_tx_prio=200, - allow_rx=False, - enable_color=False, - baud_rate=board.baud_rate if board.baud_rate else 115200 + board.serial.compatible, + board.serial.node_path, + sdf, + driver_prio=201, + virt_tx_prio=200, + allow_rx=False, + enable_color=False, + baud_rate=board.baud_rate if board.baud_rate else 115200, ) serial.add_client(client_scan) - sdf.add_subsystem(serial) if board.name == "odroidc4": # Odroid-C4 I2C requires clocks/GPIO setup, for now we give the I2C driver # direct access. - clk_mr = MemoryRegion("clk", 0x1000, paddr=0xFF63C000, cached=False) - gpio_mr = MemoryRegion("gpio", 0x1000, paddr=0xFF634000, cached=False) + clk_mr = MemoryRegion("clk", 0x1000, sdf, paddr=0xFF63C000, cached=False) + gpio_mr = MemoryRegion("gpio", 0x1000, sdf, paddr=0xFF634000, cached=False) sdf.add_memory_region(clk_mr) sdf.add_memory_region(gpio_mr) i2c.driver.add_map(Map(clk_mr, 0x30_000_000, "rw")) diff --git a/examples/ina219/meta.py b/examples/ina219/meta.py index bd1d5127d..b4b434476 100644 --- a/examples/ina219/meta.py +++ b/examples/ina219/meta.py @@ -8,35 +8,33 @@ from acacia import System, ProtectionDomain, MemoryRegion, Channel, DeviceTreeBlob, Map -sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../") -) +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) from acacia_sddf import BOARDS, sDDFI2C, sDDFSerial, sDDFTimer + def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): - client_ina = ProtectionDomain("client_ina", "client_ina.elf", priority=1) + client_ina = ProtectionDomain("client_ina", "client_ina.elf", sdf, priority=1) - i2c = sDDFI2C(board.i2c.compatible, board.i2c.node_path, sdf, driver_prio=200, virt_prio=199) + i2c = sDDFI2C( + board.i2c.compatible, board.i2c.node_path, sdf, driver_prio=200, virt_prio=199 + ) i2c.add_client(client_ina) - sdf.add_subsystem(i2c) timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) timer.add_client(client_ina) - sdf.add_subsystem(timer) serial = sDDFSerial( - board.serial.compatible, - board.serial.node_path, - sdf, - driver_prio=201, - virt_tx_prio=200, - allow_rx=False, - enable_color=False, - baud_rate=board.baud_rate if board.baud_rate else 115200 + board.serial.compatible, + board.serial.node_path, + sdf, + driver_prio=201, + virt_tx_prio=200, + allow_rx=False, + enable_color=False, + baud_rate=board.baud_rate if board.baud_rate else 115200, ) serial.add_client(client_ina) - sdf.add_subsystem(serial) if board.name == "odroidc4": # Odroid-C4 I2C requires clocks/GPIO setup, for now we give the I2C driver diff --git a/examples/serial/meta.py b/examples/serial/meta.py index 21a0e0dc5..441ecec1b 100644 --- a/examples/serial/meta.py +++ b/examples/serial/meta.py @@ -7,36 +7,34 @@ from acacia import System, ProtectionDomain, MemoryRegion, Channel, DeviceTreeBlob from acacia.arch import x86_64 -sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../") -) +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) from acacia_sddf import sDDFSerial, BOARDS + def generate(sdf_file: str, output_dir: str): - client0 = ProtectionDomain("client0", "client0.elf", priority=1) - client1 = ProtectionDomain("client1", "client1.elf", priority=1) + client0 = ProtectionDomain("client0", "client0.elf", sdf, priority=1) + client1 = ProtectionDomain("client1", "client1.elf", sdf, priority=1) serial = sDDFSerial( - board.serial.compatible, - board.serial.node_path, - sdf, - driver_prio=200, - virt_tx_prio=199, - allow_rx=True, - enable_color=True, - baud_rate=board.baud_rate if board.baud_rate else 115200 + board.serial.compatible, + board.serial.node_path, + sdf, + driver_prio=200, + virt_tx_prio=199, + allow_rx=True, + enable_color=True, + baud_rate=board.baud_rate if board.baud_rate else 115200, ) for pd in [client0, client1]: serial.add_client(pd) - sdf.add_pd(pd) - sdf.add_subsystem(serial) sdf.make_config_structs() out_file = f"{output_dir}/{sdf_file}" print(f"Saving to {out_file}") sdf.write_xml_file(out_file) + if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--dtb", required=False) diff --git a/examples/timer/meta.py b/examples/timer/meta.py index 825a0323f..589541fcd 100644 --- a/examples/timer/meta.py +++ b/examples/timer/meta.py @@ -9,19 +9,15 @@ # Use importlib to dynamically load. Using `from` import below other code is bad style. # board_module = importlib.import_module("board") -sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..") -) +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..")) from acacia_sddf import BOARDS, sDDFTimer -def generate(sdf_file: str, output_dir: str): - client = ProtectionDomain("client", "client.elf", priority=1) +def generate(sdf_file: str, output_dir: str): + client = ProtectionDomain("client", "client.elf", sdf, priority=1) timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) timer.add_client(client) - sdf.add_subsystem(timer) - sdf.add_pd(client) # Add HPET if x86 if board.arch == x86_64: @@ -32,6 +28,7 @@ def generate(sdf_file: str, output_dir: str): print(f"Saving to {out_file}") sdf.write_xml_file(out_file) + if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--dtb", required=False) From 6d06dfb2f014da3236138bf05b3e67bfb95222bf Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Mon, 13 Jul 2026 09:29:15 +1000 Subject: [PATCH 04/15] Update signatures to put SDF first for Acacia Signed-off-by: Lesley Rossouw --- acacia_sddf/i2c.py | 24 ++++++++++-------- acacia_sddf/sddf.py | 8 +++--- acacia_sddf/serial.py | 48 ++++++++++++++++++++++------------- acacia_sddf/timer.py | 8 +++--- examples/i2c/meta.py | 16 +++++++----- examples/i2c_bus_scan/meta.py | 14 +++++----- examples/ina219/meta.py | 14 +++++----- examples/serial/meta.py | 6 ++--- examples/timer/meta.py | 4 +-- 9 files changed, 79 insertions(+), 63 deletions(-) diff --git a/acacia_sddf/i2c.py b/acacia_sddf/i2c.py index 4df2f23d4..6b6f560f7 100644 --- a/acacia_sddf/i2c.py +++ b/acacia_sddf/i2c.py @@ -27,9 +27,9 @@ class sDDFI2C(sDDFDriverClass): def __init__( self, + sdf: System, dev_compatible: str, dev_dt_path: str, - sdf: System, driver_prio: int, virt_prio: int, cpu: Optional[int] = None, @@ -37,7 +37,7 @@ def __init__( driver_elf: str = "i2c_driver.elf", ): super().__init__( - "i2c", dev_compatible, dev_dt_path, sdf, magic="sDDF" + chr(0x1) + sdf, "i2c", dev_compatible, dev_dt_path, magic="sDDF" + chr(0x1) ) self.sdf = sdf self.cpu = cpu @@ -52,9 +52,9 @@ def __init__( # the sDDF with an SDK model, but that is for the future. self.virt_elf = virt_elf self.driver = ProtectionDomain( + self.sdf, "i2c_driver", driver_elf, - self.sdf, scheduling=SchedulingProperties(driver_prio), cpu=self.cpu, ) @@ -77,16 +77,16 @@ def __init__( def construct_infrastructure(self, virt_prio): self.virt = ProtectionDomain( + self.sdf, "i2c_virt", self.virt_elf, - self.sdf, scheduling=SchedulingProperties(virt_prio), cpu=self.cpu, ) # Make queues - driver_req_q_mr = MemoryRegion("i2c_driver_request", 0x1000, self.sdf) - driver_resp_q_mr = MemoryRegion("i2c_driver_response", 0x1000, self.sdf) + driver_req_q_mr = MemoryRegion(self.sdf, "i2c_driver_request", 0x1000) + driver_resp_q_mr = MemoryRegion(self.sdf, "i2c_driver_response", 0x1000) driver_req_map = self.driver.create_automap( driver_req_q_mr, Map.Permissions(r=True, w=True) ) @@ -102,9 +102,9 @@ def construct_infrastructure(self, virt_prio): # Create channels driver_virt_ch = Channel( + self.sdf, Channel.End(self.driver, can_notify=True, can_pp=False), Channel.End(self.virt, can_notify=True, can_pp=False), - self.sdf ) self.channels.append(driver_virt_ch) @@ -143,16 +143,18 @@ def connect_clients(self): ) # Make channel ch = Channel( + self.sdf, Channel.End(c, can_notify=True, can_pp=True), Channel.End(self.virt, can_notify=True, can_pp=False), - self.sdf ) self.channels.append(ch) # Add request and response queue - c_req_q_mr = MemoryRegion(f"i2c_client_request_{c.name}", 0x1000, self.sdf) - c_resp_q_mr = MemoryRegion(f"i2c_client_response_{c.name}", 0x1000, self.sdf) - c_data_mr = MemoryRegion(f"i2c_client_data_{c.name}", I2C_DATA_SZ, self.sdf) + c_req_q_mr = MemoryRegion(self.sdf, f"i2c_client_request_{c.name}", 0x1000) + c_resp_q_mr = MemoryRegion( + self.sdf, f"i2c_client_response_{c.name}", 0x1000 + ) + c_data_mr = MemoryRegion(self.sdf, f"i2c_client_data_{c.name}", I2C_DATA_SZ) # Create maps for clients c_req_map = c.create_automap(c_req_q_mr, Map.Permissions(r=True, w=True)) diff --git a/acacia_sddf/sddf.py b/acacia_sddf/sddf.py index c6350e766..fca406e98 100644 --- a/acacia_sddf/sddf.py +++ b/acacia_sddf/sddf.py @@ -29,13 +29,13 @@ class sDDFDriverClass(Subsystem): def __init__( self, + system: System, class_name: str, dev_compatible: str, dev_dt_path: str, - system: System, magic: str, ): - super().__init__(class_name, system) + super().__init__(system, class_name) self.sdf = system self.dtb = system.dtb @@ -152,7 +152,9 @@ def create_dtb_resources(self, driver_pd: ProtectionDomain) -> ConfigStruct: ) else: # This is new (or overlapping with a different start) - mr = MemoryRegion(region_name, mr_sz, self.sdf, paddr=d_paddr, cached=False) + mr = MemoryRegion( + self.sdf, region_name, mr_sz, paddr=d_paddr, cached=False + ) else: # This is a MR that doesn't correspond to physical memory # mr = MemoryRegion(region_name, region.size) diff --git a/acacia_sddf/serial.py b/acacia_sddf/serial.py index eb0ef36af..7b0c06e1b 100644 --- a/acacia_sddf/serial.py +++ b/acacia_sddf/serial.py @@ -30,9 +30,9 @@ class sDDFSerial(sDDFDriverClass): def __init__( self, + sdf: System, dev_compatible: str, dev_dt_path: str, - sdf: System, driver_prio: int, virt_tx_prio: int, allow_rx: bool = False, @@ -49,7 +49,7 @@ def __init__( driver_elf: str = "serial_driver.elf", ): super().__init__( - "serial", dev_compatible, dev_dt_path, sdf, magic="sDDF" + chr(0x1) + sdf, "serial", dev_compatible, dev_dt_path, magic="sDDF" + chr(0x1) ) assert driver_prio > virt_tx_prio > 0 if allow_rx: @@ -75,9 +75,9 @@ def __init__( self.virt_rx_elf = virt_rx_elf self.virt_tx_elf = virt_tx_elf self.driver = ProtectionDomain( + self.sdf, "serial_driver", driver_elf, - self.sdf, scheduling=SchedulingProperties(driver_prio), cpu=self.cpu, ) @@ -101,26 +101,28 @@ def __init__( def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): self.virt_tx = ProtectionDomain( + self.sdf, "serial_virt_tx", self.virt_tx_elf, - self.sdf, scheduling=SchedulingProperties(virt_tx_prio), cpu=self.cpu, ) if self.allow_rx and virt_rx_prio > 0: self.virt_rx = ProtectionDomain( + self.sdf, "serial_virt_rx", self.virt_rx_elf, - self.sdf, scheduling=SchedulingProperties(virt_rx_prio), cpu=self.cpu, ) - driver_tx_queue_mr = MemoryRegion("serial_driver_tx_queue", self.queue_size, self.sdf) + driver_tx_queue_mr = MemoryRegion( + self.sdf, "serial_driver_tx_queue", self.queue_size + ) driver_tx_data_mr = MemoryRegion( + self.sdf, "serial_driver_tx_data", self.data_size * 2 if self.enable_color else self.data_size, - self.sdf, cached=True, ) @@ -138,9 +140,9 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): ) driver_virt_tx_ch = Channel( + self.sdf, Channel.End(self.driver, can_notify=True, can_pp=False), Channel.End(self.virt_tx, can_notify=True, can_pp=False), - self.sdf ) driver_tx_conn = self.serial_connection_resource_factory( @@ -156,8 +158,12 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): driver_rx_conn = None if self.virt_rx: - driver_rx_queue_mr = MemoryRegion("serial_driver_rx_queue", self.queue_size, self.sdf) - driver_rx_data_mr = MemoryRegion("serial_driver_rx_data", self.data_size, self.sdf) + driver_rx_queue_mr = MemoryRegion( + self.sdf, "serial_driver_rx_queue", self.queue_size + ) + driver_rx_data_mr = MemoryRegion( + self.sdf, "serial_driver_rx_data", self.data_size + ) driver_rx_queue_map = self.driver.create_automap( driver_rx_queue_mr, Map.Permissions(r=True, w=True) @@ -173,9 +179,9 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): ) driver_virt_rx_ch = Channel( + self.sdf, Channel.End(self.driver, can_notify=True, can_pp=False), Channel.End(self.virt_rx, can_notify=True, can_pp=False), - self.sdf ) driver_rx_conn = self.serial_connection_resource_factory( @@ -219,8 +225,12 @@ def connect_clients(self): ) # TX connection: virt_tx -> client - tx_queue_mr = MemoryRegion(f"serial_tx_queue_{c.name}", self.queue_size, self.sdf) - tx_data_mr = MemoryRegion(f"serial_tx_data_{c.name}", self.data_size, self.sdf) + tx_queue_mr = MemoryRegion( + self.sdf, f"serial_tx_queue_{c.name}", self.queue_size + ) + tx_data_mr = MemoryRegion( + self.sdf, f"serial_tx_data_{c.name}", self.data_size + ) virt_tx_tx_queue_map = self.virt_tx.create_automap( tx_queue_mr, Map.Permissions(r=True, w=True) @@ -236,9 +246,9 @@ def connect_clients(self): ) tx_ch = Channel( + self.sdf, Channel.End(self.virt_tx, can_notify=True, can_pp=False), Channel.End(c, can_notify=True, can_pp=False), - self.sdf ) virt_tx_conn = self.serial_connection_resource_factory( @@ -255,8 +265,12 @@ def connect_clients(self): # RX connection (if enabled): virt_rx -> client client_rx_conn = None if self.virt_rx: - rx_queue_mr = MemoryRegion(f"serial_rx_queue_{c.name}", self.queue_size, self.sdf) - rx_data_mr = MemoryRegion(f"serial_rx_data_{c.name}", self.data_size, self.sdf) + rx_queue_mr = MemoryRegion( + self.sdf, f"serial_rx_queue_{c.name}", self.queue_size + ) + rx_data_mr = MemoryRegion( + self.sdf, f"serial_rx_data_{c.name}", self.data_size + ) virt_rx_rx_queue_map = self.virt_rx.create_automap( rx_queue_mr, Map.Permissions(r=True, w=True) @@ -272,9 +286,9 @@ def connect_clients(self): ) rx_ch = Channel( + self.sdf, Channel.End(self.virt_rx, can_notify=True, can_pp=False), Channel.End(c, can_notify=True, can_pp=False), - self.sdf ) virt_rx_conn = self.serial_connection_resource_factory( diff --git a/acacia_sddf/timer.py b/acacia_sddf/timer.py index dead29d4d..65774493c 100644 --- a/acacia_sddf/timer.py +++ b/acacia_sddf/timer.py @@ -25,20 +25,20 @@ class sDDFTimer(sDDFDriverClass): def __init__( self, + sdf: System, dev_compatible: str, dev_dt_path: str, - sdf: System, driver_prio: int = 254, cpu: Optional[int] = None, driver_elf: str = "timer_driver.elf", ): super().__init__( - "timer", dev_compatible, dev_dt_path, sdf, magic="sDDF" + chr(1) + sdf, "timer", dev_compatible, dev_dt_path, magic="sDDF" + chr(1) ) self.driver = ProtectionDomain( + self.sdf, "timer_driver", driver_elf, - self.sdf, scheduling=SchedulingProperties(driver_prio, passive=True), ) self.cpu = cpu @@ -57,9 +57,9 @@ def connect_clients(self): f"Client {c} has higher priority than timer driver!" ) ch = Channel( + self.sdf, Channel.End(c, can_notify=False, can_pp=True), Channel.End(self.driver, can_notify=True, can_pp=False), - self.sdf ) self.client_configs.append( self.timer_client_config_factory(c, ch.id_for_pd(c)) diff --git a/examples/i2c/meta.py b/examples/i2c/meta.py index 6d8532755..d4a18f85a 100644 --- a/examples/i2c/meta.py +++ b/examples/i2c/meta.py @@ -11,23 +11,25 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): - client_pn532 = ProtectionDomain("client_pn532", "client_pn532.elf", sdf, priority=1) - client_ds3231 = ProtectionDomain("client_ds3231", "client_ds3231.elf", sdf, priority=1) + client_pn532 = ProtectionDomain(sdf, "client_pn532", "client_pn532.elf", priority=1) + client_ds3231 = ProtectionDomain( + sdf, "client_ds3231", "client_ds3231.elf", priority=1 + ) i2c = sDDFI2C( - board.i2c.compatible, board.i2c.node_path, sdf, driver_prio=200, virt_prio=199 + sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=199 ) i2c.add_client(client_ds3231) i2c.add_client(client_pn532) - timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) + timer = sDDFTimer(sdf, board.timer.compatible, board.timer.node_path) timer.add_client(client_ds3231) timer.add_client(client_pn532) serial = sDDFSerial( + sdf, board.serial.compatible, board.serial.node_path, - sdf, driver_prio=201, virt_tx_prio=200, allow_rx=False, @@ -40,8 +42,8 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): if board.name == "odroidc4": # Odroid-C4 I2C requires clocks/GPIO setup, for now we give the I2C driver # direct access. - clk_mr = MemoryRegion("clk", 0x1000, sdf, paddr=0xFF63C000, cached=False) - gpio_mr = MemoryRegion("gpio", 0x1000, sdf, paddr=0xFF634000, cached=False) + clk_mr = MemoryRegion(sdf, "clk", 0x1000, paddr=0xFF63C000, cached=False) + gpio_mr = MemoryRegion(sdf, "gpio", 0x1000, paddr=0xFF634000, cached=False) i2c.driver.add_map(Map(clk_mr, 0x30_000_000, "rw")) i2c.driver.add_map(Map(gpio_mr, 0x30_100_000, "rw")) diff --git a/examples/i2c_bus_scan/meta.py b/examples/i2c_bus_scan/meta.py index c26ba3dcc..f24cc0de0 100644 --- a/examples/i2c_bus_scan/meta.py +++ b/examples/i2c_bus_scan/meta.py @@ -12,20 +12,20 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): - client_scan = ProtectionDomain("client_scan", "client_scan.elf", sdf, priority=1) + client_scan = ProtectionDomain(sdf, "client_scan", "client_scan.elf", priority=1) i2c = sDDFI2C( - board.i2c.compatible, board.i2c.node_path, sdf, driver_prio=200, virt_prio=199 + sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=199 ) i2c.add_client(client_scan) - timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) + timer = sDDFTimer(sdf, board.timer.compatible, board.timer.node_path) timer.add_client(client_scan) serial = sDDFSerial( + sdf, board.serial.compatible, board.serial.node_path, - sdf, driver_prio=201, virt_tx_prio=200, allow_rx=False, @@ -37,10 +37,8 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): if board.name == "odroidc4": # Odroid-C4 I2C requires clocks/GPIO setup, for now we give the I2C driver # direct access. - clk_mr = MemoryRegion("clk", 0x1000, sdf, paddr=0xFF63C000, cached=False) - gpio_mr = MemoryRegion("gpio", 0x1000, sdf, paddr=0xFF634000, cached=False) - sdf.add_memory_region(clk_mr) - sdf.add_memory_region(gpio_mr) + clk_mr = MemoryRegion(sdf, "clk", 0x1000, paddr=0xFF63C000, cached=False) + gpio_mr = MemoryRegion(sdf, "gpio", 0x1000, paddr=0xFF634000, cached=False) i2c.driver.add_map(Map(clk_mr, 0x30_000_000, "rw")) i2c.driver.add_map(Map(gpio_mr, 0x30_100_000, "rw")) diff --git a/examples/ina219/meta.py b/examples/ina219/meta.py index b4b434476..f38876e90 100644 --- a/examples/ina219/meta.py +++ b/examples/ina219/meta.py @@ -14,20 +14,20 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): - client_ina = ProtectionDomain("client_ina", "client_ina.elf", sdf, priority=1) + client_ina = ProtectionDomain(sdf, "client_ina", "client_ina.elf", priority=1) i2c = sDDFI2C( - board.i2c.compatible, board.i2c.node_path, sdf, driver_prio=200, virt_prio=199 + sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=199 ) i2c.add_client(client_ina) - timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) + timer = sDDFTimer(sdf, board.timer.compatible, board.timer.node_path) timer.add_client(client_ina) serial = sDDFSerial( + sdf, board.serial.compatible, board.serial.node_path, - sdf, driver_prio=201, virt_tx_prio=200, allow_rx=False, @@ -39,10 +39,8 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): if board.name == "odroidc4": # Odroid-C4 I2C requires clocks/GPIO setup, for now we give the I2C driver # direct access. - clk_mr = MemoryRegion("clk", 0x1000, paddr=0xFF63C000, cached=False) - gpio_mr = MemoryRegion("gpio", 0x1000, paddr=0xFF634000, cached=False) - sdf.add_memory_region(clk_mr) - sdf.add_memory_region(gpio_mr) + clk_mr = MemoryRegion(sdf, "clk", 0x1000, paddr=0xFF63C000, cached=False) + gpio_mr = MemoryRegion(sdf, "gpio", 0x1000, paddr=0xFF634000, cached=False) i2c.driver.add_map(Map(clk_mr, 0x30_000_000, "rw")) i2c.driver.add_map(Map(gpio_mr, 0x30_100_000, "rw")) diff --git a/examples/serial/meta.py b/examples/serial/meta.py index 441ecec1b..93b89e152 100644 --- a/examples/serial/meta.py +++ b/examples/serial/meta.py @@ -12,13 +12,13 @@ def generate(sdf_file: str, output_dir: str): - client0 = ProtectionDomain("client0", "client0.elf", sdf, priority=1) - client1 = ProtectionDomain("client1", "client1.elf", sdf, priority=1) + client0 = ProtectionDomain(sdf, "client0", "client0.elf", priority=1) + client1 = ProtectionDomain(sdf, "client1", "client1.elf", priority=1) serial = sDDFSerial( + sdf, board.serial.compatible, board.serial.node_path, - sdf, driver_prio=200, virt_tx_prio=199, allow_rx=True, diff --git a/examples/timer/meta.py b/examples/timer/meta.py index 589541fcd..73880ac97 100644 --- a/examples/timer/meta.py +++ b/examples/timer/meta.py @@ -14,9 +14,9 @@ def generate(sdf_file: str, output_dir: str): - client = ProtectionDomain("client", "client.elf", sdf, priority=1) + client = ProtectionDomain(sdf, "client", "client.elf", priority=1) - timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) + timer = sDDFTimer(sdf, board.timer.compatible, board.timer.node_path) timer.add_client(client) # Add HPET if x86 From de0f706d75117a8bbfcbe16f604df8207ca1ad87 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Mon, 13 Jul 2026 10:09:37 +1000 Subject: [PATCH 05/15] Extract per-board / x86 metaprogram hacks to class.py Signed-off-by: Lesley Rossouw --- acacia_sddf/i2c.py | 14 ++++++++++++++ acacia_sddf/sddf.py | 14 +++++++++++++- acacia_sddf/serial.py | 6 +++--- acacia_sddf/timer.py | 5 ++++- examples/i2c/meta.py | 8 -------- examples/i2c_bus_scan/meta.py | 8 -------- examples/ina219/meta.py | 8 -------- 7 files changed, 34 insertions(+), 29 deletions(-) diff --git a/acacia_sddf/i2c.py b/acacia_sddf/i2c.py index 6b6f560f7..ad9d949de 100644 --- a/acacia_sddf/i2c.py +++ b/acacia_sddf/i2c.py @@ -69,6 +69,20 @@ def __init__( self.client_configs = [] self.channels = [] + # Special cases for boards. These should be removed once we add infrastructure to support this better. + # meson (odroidc4/5) + if "amlogic,meson" in dev_compatible: + # Odroid-C4 I2C requires clocks/GPIO setup, for now we give the I2C driver + # direct access. + clk_mr = MemoryRegion( + self.sdf, "clk", 0x1000, paddr=0xFF63C000, cached=False + ) + gpio_mr = MemoryRegion( + self.sdf, "gpio", 0x1000, paddr=0xFF634000, cached=False + ) + self.driver.add_map(Map(clk_mr, 0x30_000_000, "rw")) + self.driver.add_map(Map(gpio_mr, 0x30_100_000, "rw")) + # We create queues etc. AFTER setting up the device resources to ensure that IRQ channels # have a lower value than any other channels. This is necessary because Microkit will # deliver notifications in ascending channel_id order, which can end up mattering in certain diff --git a/acacia_sddf/sddf.py b/acacia_sddf/sddf.py index fca406e98..d0234d5d9 100644 --- a/acacia_sddf/sddf.py +++ b/acacia_sddf/sddf.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: BSD-2-Clause import sys, os from typing import List, Optional, Tuple +from abc import abstractmethod from acacia import ( Subsystem, ProtectionDomain, @@ -103,8 +104,9 @@ def create_dtb_resources(self, driver_pd: ProtectionDomain) -> ConfigStruct: if self.dtb is None: print(f"sddf.py: no DTB! Creating dummy device resources.") # x86 or otherwise no DTB! + print("sddf.py: no DTB! Assuming x86") + self.x86_resources() # We generate an empty deviceresources despite it being useless, as our build system expects it. - # TODO: fix that? self.__device_resources = DeviceResourcesFactory( self.driver_magic, [], [], target_file=driver_pd.prog_image ) @@ -186,6 +188,16 @@ def create_dtb_resources(self, driver_pd: ProtectionDomain) -> ConfigStruct: ) return self.__device_resources + def x86_resources(self): + """ + Create any resources needed if running on an x86 platform. Automatically + called in the event that no DTB is present. Subclasses should override this + method to do whatever they might need. + + By default nothing will happen. + """ + ... + def RegionResourceFactory(map: Map, section_name: Optional[str] = None, offset=0): fields = {"vaddr": map.vaddr + offset, "size": map.mr.size} diff --git a/acacia_sddf/serial.py b/acacia_sddf/serial.py index 7b0c06e1b..d63826717 100644 --- a/acacia_sddf/serial.py +++ b/acacia_sddf/serial.py @@ -81,9 +81,6 @@ def __init__( scheduling=SchedulingProperties(driver_prio), cpu=self.cpu, ) - # Do x86 stuff - if self.sdf.dtb is None: - self.add_x86_serial_port() # We must make the driver BEFORE we get here self.driver_dev_resources = self.create_dtb_resources(self.driver) @@ -329,6 +326,9 @@ def connect_clients(self): self.client_configs = client_configs + def x86_resources(self): + self.add_x86_serial_port() + def generate_config_structs(self): # We've already made our structs, just return them as a list for the serialiser driver_resources = [self.driver_dev_resources, self.driver_config] diff --git a/acacia_sddf/timer.py b/acacia_sddf/timer.py index 65774493c..f4c381bb6 100644 --- a/acacia_sddf/timer.py +++ b/acacia_sddf/timer.py @@ -65,6 +65,9 @@ def connect_clients(self): self.timer_client_config_factory(c, ch.id_for_pd(c)) ) + def x86_resources(self): + self.add_x86_hpet() + def generate_config_structs(self): # We've already made our structs return [self.driver_dev_resources] + self.client_configs @@ -87,7 +90,7 @@ def timer_client_config_factory( # x86 utility # NOTE: is this safe to call automatically? I currently am assuming we want manual # control over this since we didn't bake it into sdfgen before. - def add_x86_hpet(self, sdf: System): + def add_x86_hpet(self): # Timer IRQ must be the highest priority (highest vector) to ensure they are delivered # as close as possible to the timer expiry. The highest vector is defined by (irq_user_max - irq_user_min) in seL4 source # Since our HPET driver uses legacy IRQ routing, comparator 0's IRQ will always arrives at diff --git a/examples/i2c/meta.py b/examples/i2c/meta.py index d4a18f85a..ebb1d654f 100644 --- a/examples/i2c/meta.py +++ b/examples/i2c/meta.py @@ -39,14 +39,6 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): serial.add_client(client_ds3231) serial.add_client(client_pn532) - if board.name == "odroidc4": - # Odroid-C4 I2C requires clocks/GPIO setup, for now we give the I2C driver - # direct access. - clk_mr = MemoryRegion(sdf, "clk", 0x1000, paddr=0xFF63C000, cached=False) - gpio_mr = MemoryRegion(sdf, "gpio", 0x1000, paddr=0xFF634000, cached=False) - i2c.driver.add_map(Map(clk_mr, 0x30_000_000, "rw")) - i2c.driver.add_map(Map(gpio_mr, 0x30_100_000, "rw")) - out_file = f"{output_dir}/{sdf_file}" sdf.make_config_structs() print(f"Saving to {out_file}") diff --git a/examples/i2c_bus_scan/meta.py b/examples/i2c_bus_scan/meta.py index f24cc0de0..fc3231de6 100644 --- a/examples/i2c_bus_scan/meta.py +++ b/examples/i2c_bus_scan/meta.py @@ -34,14 +34,6 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): ) serial.add_client(client_scan) - if board.name == "odroidc4": - # Odroid-C4 I2C requires clocks/GPIO setup, for now we give the I2C driver - # direct access. - clk_mr = MemoryRegion(sdf, "clk", 0x1000, paddr=0xFF63C000, cached=False) - gpio_mr = MemoryRegion(sdf, "gpio", 0x1000, paddr=0xFF634000, cached=False) - i2c.driver.add_map(Map(clk_mr, 0x30_000_000, "rw")) - i2c.driver.add_map(Map(gpio_mr, 0x30_100_000, "rw")) - out_file = f"{output_dir}/{sdf_file}" sdf.make_config_structs() print(f"Saving to {out_file}") diff --git a/examples/ina219/meta.py b/examples/ina219/meta.py index f38876e90..ce1e25cc1 100644 --- a/examples/ina219/meta.py +++ b/examples/ina219/meta.py @@ -36,14 +36,6 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): ) serial.add_client(client_ina) - if board.name == "odroidc4": - # Odroid-C4 I2C requires clocks/GPIO setup, for now we give the I2C driver - # direct access. - clk_mr = MemoryRegion(sdf, "clk", 0x1000, paddr=0xFF63C000, cached=False) - gpio_mr = MemoryRegion(sdf, "gpio", 0x1000, paddr=0xFF634000, cached=False) - i2c.driver.add_map(Map(clk_mr, 0x30_000_000, "rw")) - i2c.driver.add_map(Map(gpio_mr, 0x30_100_000, "rw")) - out_file = f"{output_dir}/{sdf_file}" sdf.make_config_structs() print(f"Saving to {out_file}") From f8411a57fc19633ddd96830c86eeda3bd42575a6 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Fri, 3 Jul 2026 10:07:40 +1000 Subject: [PATCH 06/15] Lint Python with Acacia additions Signed-off-by: Lesley Rossouw --- examples/i2c_bus_scan/meta.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/i2c_bus_scan/meta.py b/examples/i2c_bus_scan/meta.py index fc3231de6..966cdc86a 100644 --- a/examples/i2c_bus_scan/meta.py +++ b/examples/i2c_bus_scan/meta.py @@ -10,10 +10,17 @@ from acacia_sddf import BOARDS, sDDFI2C, sDDFSerial, sDDFTimer +<<<<<<< HEAD def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): client_scan = ProtectionDomain(sdf, "client_scan", "client_scan.elf", priority=1) +======= + +def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): + client_scan = ProtectionDomain("client_scan", "client_scan.elf", priority=1) + +>>>>>>> 0cbc47d9 (Lint Python with Acacia additions) i2c = sDDFI2C( sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=199 ) From 971a03d22c4cbab7be555d0a51ab4991be721945 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Fri, 3 Jul 2026 10:07:40 +1000 Subject: [PATCH 07/15] Lint Python with Acacia additions Signed-off-by: Lesley Rossouw --- examples/i2c/meta.py | 8 ++++++++ examples/timer/meta.py | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/i2c/meta.py b/examples/i2c/meta.py index ebb1d654f..9965e54e8 100644 --- a/examples/i2c/meta.py +++ b/examples/i2c/meta.py @@ -9,6 +9,7 @@ sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) from acacia_sddf import BOARDS, sDDFI2C, sDDFSerial, sDDFTimer +<<<<<<< HEAD def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): client_pn532 = ProtectionDomain(sdf, "client_pn532", "client_pn532.elf", priority=1) @@ -16,6 +17,13 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): sdf, "client_ds3231", "client_ds3231.elf", priority=1 ) +======= + +def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): + client_pn532 = ProtectionDomain("client_pn532", "client_pn532.elf", priority=1) + client_ds3231 = ProtectionDomain("client_ds3231", "client_ds3231.elf", priority=1) + +>>>>>>> 4eaca1d8 (Lint Python with Acacia additions) i2c = sDDFI2C( sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=199 ) diff --git a/examples/timer/meta.py b/examples/timer/meta.py index 73880ac97..c7748b022 100644 --- a/examples/timer/meta.py +++ b/examples/timer/meta.py @@ -12,7 +12,6 @@ sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..")) from acacia_sddf import BOARDS, sDDFTimer - def generate(sdf_file: str, output_dir: str): client = ProtectionDomain(sdf, "client", "client.elf", priority=1) From c8c74447938ac863750066679436068c8e66f025 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Tue, 10 Mar 2026 16:31:06 +1100 Subject: [PATCH 08/15] Added TMU driver class and imx8mq implementation (WIP) Signed-off-by: Lesley Rossouw --- drivers/tmu/imx8mq/imx8mq-tmu.h | 186 +++++++++++++++++++++++++++++++ drivers/tmu/imx8mq/tmu.c | 95 ++++++++++++++++ drivers/tmu/imx8mq/tmu_driver.mk | 31 ++++++ examples/tmu/Makefile | 31 ++++++ examples/tmu/client.c | 110 ++++++++++++++++++ examples/tmu/meta.py | 89 +++++++++++++++ examples/tmu/tmu.mk | 99 ++++++++++++++++ include/sddf/tmu/client.h | 82 ++++++++++++++ include/sddf/tmu/driver.h | 96 ++++++++++++++++ include/sddf/tmu/protocol.h | 86 ++++++++++++++ 10 files changed, 905 insertions(+) create mode 100644 drivers/tmu/imx8mq/imx8mq-tmu.h create mode 100644 drivers/tmu/imx8mq/tmu.c create mode 100644 drivers/tmu/imx8mq/tmu_driver.mk create mode 100644 examples/tmu/Makefile create mode 100644 examples/tmu/client.c create mode 100644 examples/tmu/meta.py create mode 100644 examples/tmu/tmu.mk create mode 100644 include/sddf/tmu/client.h create mode 100644 include/sddf/tmu/driver.h create mode 100644 include/sddf/tmu/protocol.h diff --git a/drivers/tmu/imx8mq/imx8mq-tmu.h b/drivers/tmu/imx8mq/imx8mq-tmu.h new file mode 100644 index 000000000..eb9a393e8 --- /dev/null +++ b/drivers/tmu/imx8mq/imx8mq-tmu.h @@ -0,0 +1,186 @@ +/* + * Copyright 2026, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +typedef struct imx8mq_tmu_regs { + uint32_t tmr; /* 0x00 - TMU mode register */ + uint32_t tsr; /* 0x04 - TMU status register */ + uint32_t tmtmir; /* 0x08 - TMU monitor temperature measurement interval register */ + uint32_t reserved0[5]; /* 0x0C-0x1F - Reserved */ + uint32_t tier; /* 0x20 - TMU interrupt enable register */ + uint32_t tidr; /* 0x24 - TMU interrupt detect register */ + uint32_t tiscr; /* 0x28 - TMU interrupt site capture register */ + uint32_t ticscr; /* 0x2C - TMU interrupt critical site capture register */ + uint32_t reserved1[4]; /* 0x30-0x3F - Reserved */ + uint32_t tmhtcr; /* 0x40 - TMU monitor high temperature capture register */ + uint32_t tmltcr; /* 0x44 - TMU monitor low temperature capture register */ + uint32_t reserved2[2]; /* 0x48-0x4F - Reserved */ + uint32_t tmhtitr; /* 0x50 - TMU monitor high temperature immediate threshold register */ + uint32_t tmhtatr; /* 0x54 - TMU monitor high temperature average threshold register */ + uint32_t tmhtactr; /* 0x58 - TMU monitor high temperature average critical threshold register */ + uint32_t reserved3[9]; /* 0x5C-0x7F - Reserved */ + uint32_t ttcfgr; /* 0x80 - TMU temperature configuration register */ + uint32_t tscfgr; /* 0x84 - TMU sensor configuration register */ + uint32_t reserved4[30]; /* 0x88-0xFF - Reserved */ + uint32_t tritsr0; /* 0x100 - TMU report immediate temperature site register 0 */ + uint32_t tratsr0; /* 0x104 - TMU report average temperature site register 0 */ + uint32_t reserved5[2]; /* 0x108-0x10F - Reserved */ + uint32_t tritsr1; /* 0x110 - TMU report immediate temperature site register 1 */ + uint32_t tratsr1; /* 0x114 - TMU report average temperature site register 1 */ + uint32_t reserved6[2]; /* 0x118-0x11F - Reserved */ + uint32_t tritsr2; /* 0x120 - TMU report immediate temperature site register 2 */ + uint32_t tratsr2; /* 0x124 - TMU report average temperature site register 2 */ + uint32_t reserved7[996]; /* 0x128-0xF0F - Reserved */ + uint32_t ttr0cr; /* 0xF10 - TMU temperature range 0 control register */ + uint32_t ttr1cr; /* 0xF14 - TMU temperature range 1 control register */ + uint32_t ttr2cr; /* 0xF18 - TMU temperature range 2 control register */ + uint32_t ttr3cr; /* 0xF1C - TMU temperature range 3 control register */ +} imx8mq_tmu_regs_t; + +#define SENSOR_MAX_TEMP ((sddf_temp_celsius_t) 85) +#define SENSOR_MIN_TEMP ((sddf_temp_celsius_t) 0) + +/* + * Register fields. + * We define every bit field as follows: + * Offset: number of bits the mask is shifted from the LSB. + * Mask: mask of bits in the field, left-shifted by offset. + * Bit: mask of exact bit (for single-bit fields). + */ + +// TMR - TMU Mode Register +// Bit 31: ME, Bits 15-8: MSITE +#define TMU_TMR_ME_MASK (0x80000000) +#define TMU_TMR_ME_OFFSET (31) +#define TMU_TMR_ME_BIT (0x80000000) +#define TMU_TMR_MSITE_MASK (0x0000FF00) +#define TMU_TMR_MSITE_OFFSET (8) + +// TSR - TMU Status Register +// Bit 31: MIE, Bit 1: ORL, Bit 0: ORH +#define TMU_TSR_MIE_MASK (0x80000000) +#define TMU_TSR_MIE_OFFSET (31) +#define TMU_TSR_MIE_BIT (0x80000000) +#define TMU_TSR_ORL_MASK (0x00000002) +#define TMU_TSR_ORL_OFFSET (1) +#define TMU_TSR_ORL_BIT (0x00000002) +#define TMU_TSR_ORH_MASK (0x00000001) +#define TMU_TSR_ORH_OFFSET (0) +#define TMU_TSR_ORH_BIT (0x00000001) + +// TMTMIR - TMU Monitor Temperature Measurement Interval Register +// Bits 15-0: TMI +#define TMU_TMTMIR_TMI_MASK (0x0000FFFF) +#define TMU_TMTMIR_TMI_OFFSET (0) + +// TIER - TMU Interrupt Enable Register +// Bit 2: ITTEIE, Bit 1: ATTEIE, Bit 0: ATCTEIE +#define TMU_TIER_ITTEIE_MASK (0x00000004) +#define TMU_TIER_ITTEIE_OFFSET (2) +#define TMU_TIER_ITTEIE_BIT (0x00000004) +#define TMU_TIER_ATTEIE_MASK (0x00000002) +#define TMU_TIER_ATTEIE_OFFSET (1) +#define TMU_TIER_ATTEIE_BIT (0x00000002) +#define TMU_TIER_ATCTEIE_MASK (0x00000001) +#define TMU_TIER_ATCTEIE_OFFSET (0) +#define TMU_TIER_ATCTEIE_BIT (0x00000001) + +// TIDR - TMU Interrupt Detect Register +// Bit 2: ITTE, Bit 1: ATTE, Bit 0: ATCTE (W1C) +#define TMU_TIDR_ITTE_MASK (0x00000004) +#define TMU_TIDR_ITTE_OFFSET (2) +#define TMU_TIDR_ITTE_BIT (0x00000004) +#define TMU_TIDR_ATTE_MASK (0x00000002) +#define TMU_TIDR_ATTE_OFFSET (1) +#define TMU_TIDR_ATTE_BIT (0x00000002) +#define TMU_TIDR_ATCTE_MASK (0x00000001) +#define TMU_TIDR_ATCTE_OFFSET (0) +#define TMU_TIDR_ATCTE_BIT (0x00000001) + +// TISCR - TMU Interrupt Site Capture Register +// Bits 10-8: ISITE, Bits 2-0: ASITE +#define TMU_TISCR_ISITE_MASK (0x00000700) +#define TMU_TISCR_ISITE_OFFSET (8) +#define TMU_TISCR_ASITE_MASK (0x00000007) +#define TMU_TISCR_ASITE_OFFSET (0) + +// TICSCR - TMU Interrupt Critical Site Capture Register +// Bits 2-0: CASITE +#define TMU_TICSCR_CASITE_MASK (0x00000007) +#define TMU_TICSCR_CASITE_OFFSET (0) + +// TMHTCR - TMU Monitor High Temperature Capture Register +// Bit 31: V, Bits 11-0: TEMP +#define TMU_TMHTCR_V_MASK (0x80000000) +#define TMU_TMHTCR_V_OFFSET (31) +#define TMU_TMHTCR_V_BIT (0x80000000) +#define TMU_TMHTCR_TEMP_MASK (0x00000FFF) +#define TMU_TMHTCR_TEMP_OFFSET (0) + +// TMLTCR - TMU Monitor Low Temperature Capture Register +// Bit 31: V, Bits 11-0: TEMP +#define TMU_TMLTCR_V_MASK (0x80000000) +#define TMU_TMLTCR_V_OFFSET (31) +#define TMU_TMLTCR_V_BIT (0x80000000) +#define TMU_TMLTCR_TEMP_MASK (0x00000FFF) +#define TMU_TMLTCR_TEMP_OFFSET (0) + +// TMHTITR - TMU Monitor High Temperature Immediate Threshold Register +// Bit 31: EN, Bits 11-0: TEMP +#define TMU_TMHTITR_EN_MASK (0x80000000) +#define TMU_TMHTITR_EN_OFFSET (31) +#define TMU_TMHTITR_EN_BIT (0x80000000) +#define TMU_TMHTITR_TEMP_MASK (0x00000FFF) +#define TMU_TMHTITR_TEMP_OFFSET (0) + +// TMHTATR - TMU Monitor High Temperature Average Threshold Register +// Bit 31: EN, Bits 11-0: TEMP +#define TMU_TMHTATR_EN_MASK (0x80000000) +#define TMU_TMHTATR_EN_OFFSET (31) +#define TMU_TMHTATR_EN_BIT (0x80000000) +#define TMU_TMHTATR_TEMP_MASK (0x00000FFF) +#define TMU_TMHTATR_TEMP_OFFSET (0) + +// TMHTACTR - TMU Monitor High Temperature Average Critical Threshold Register +// Bit 31: EN, Bits 11-0: TEMP +#define TMU_TMHTACTR_EN_MASK (0x80000000) +#define TMU_TMHTACTR_EN_OFFSET (31) +#define TMU_TMHTACTR_EN_BIT (0x80000000) +#define TMU_TMHTACTR_TEMP_MASK (0x00000FFF) +#define TMU_TMHTACTR_TEMP_OFFSET (0) + +// TTCFGR - TMU Temperature Configuration Register +// Bits 31-0: DATA +#define TMU_TTCFGR_DATA_MASK (0xFFFFFFFF) +#define TMU_TTCFGR_DATA_OFFSET (0) + +// TSCFGR - TMU Sensor Configuration Register +// Bits 31-0: DATA +#define TMU_TSCFGR_DATA_MASK (0xFFFFFFFF) +#define TMU_TSCFGR_DATA_OFFSET (0) + +// TRITSRn - TMU Report Immediate Temperature Site Registers +// Bit 31: V, Bits 11-0: TEMP +#define TMU_TRITSR_V_MASK (0x80000000) +#define TMU_TRITSR_V_OFFSET (31) +#define TMU_TRITSR_V_BIT (0x80000000) +#define TMU_TRITSR_TEMP_MASK (0x00000FFF) +#define TMU_TRITSR_TEMP_OFFSET (0) + +// TRATSRn - TMU Report Average Temperature Site Registers +// Bit 31: V, Bits 11-0: TEMP +#define TMU_TRATSR_V_MASK (0x80000000) +#define TMU_TRATSR_V_OFFSET (31) +#define TMU_TRATSR_V_BIT (0x80000000) +#define TMU_TRATSR_TEMP_MASK (0x00000FFF) +#define TMU_TRATSR_TEMP_OFFSET (0) + +// TTRnCR - TMU Temperature Range Control Registers +// Bits 23-16: CAL_PTR, Bits 11-0: TEMP +#define TMU_TTRCR_CAL_PTR_MASK (0x00FF0000) +#define TMU_TTRCR_CAL_PTR_OFFSET (16) +#define TMU_TTRCR_TEMP_MASK (0x00000FFF) +#define TMU_TTRCR_TEMP_OFFSET (0) + diff --git a/drivers/tmu/imx8mq/tmu.c b/drivers/tmu/imx8mq/tmu.c new file mode 100644 index 000000000..249e69070 --- /dev/null +++ b/drivers/tmu/imx8mq/tmu.c @@ -0,0 +1,95 @@ +/* + * Copyright 2026, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// This driver has a notion of a controlling connection which will receive forwarded IRQs +// and has the capability to set thesholds for forwarded IRQs or enable the device. +// All other clients can only request the current temperature. +// Note: this driver does NOT work on imx8m mini devices! + +// TODO: add sdfgen support for device resources and driver config. +// TODO: add connection to clock driver to allow setting TMTMIR based on real clock speed rather +// than a compile-time guess. We cannot support adjusting the polling rate by clients without this. + +#include +#include +#include +#include +#include "imx8mq-tmu" + +// TODO: add sdfgen support for IRQ forward channel and critical temp +#define IRQ_FORWARD_CHANNEL (0) +#define IRQ_INST_CHANNEL (1) +#define IRQ_AVG_CHANNEL (2) +#define IRQ_CRITICAL_CHANNEL (3) +#define CRITICAL_TEMP ((sddf_temp_celsius_t )85) // Warnings will be printed if this is exceeded + +sddf_tmu_irq_modes_t current_irq_mode = SDDF_TMU_IRQ_MODE_DISABLED; + +void init(void) { + // Set critical temp + uint64_t critical_temp_quantised = 0; + sddf_tmu_err_t ret = degrees_to_quantised(CRITICAL_TEMP, SENSOR_MIN_TEMP, SENSOR_MAX_TEMP, + SENSOR_QUANTISATION, &critical_temp_quantised); + assert(!ret); +} + +void notified(microkit_channel ch) +{ + if (ch == IRQ_FORWARD_CHANNEL) { + LOG_TMU_DRIVER_ERR("IRQ forward channel should not notify driver!"); + } else if (ch == IRQ_INST_CHANNEL) { + LOG_TMU_DRIVER("Instantaneous value threshold reached!\n"); + if (current_irq_mode != SDDF_TMU_IRQ_MODE_INSTANTANEOUS) { + LOG_TMU_DRIVER_ERR("Received spurious instantaneous threshold IRQ!\n"); + } + } else if (ch == IRQ_AVG_CHANNEL) { + LOG_TMU_DRIVER("Average value threshold reached!\n"); + if (current_irq_mode != SDDF_TMU_IRQ_MODE_AVG) { + LOG_TMU_DRIVER_ERR("Received spurious average threshold IRQ!\n"); + } + } else if (ch == IRQ_CRITICAL_CHANNEL) { + LOG_TMU_DRIVER_ERR("WARNING: critical temperature of %f exceeded! System may be damaged!\n", + CRITICAL_TEMP); + // clear IRQ state + } else { + LOG_TMU_DRIVER_ERR("Unknown channel 0x%x!\n", ch); + } +} + +microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) +{ + sddf_pmic_err_t err = 0; + uint64_t ret_num = 0; + uint32_t argc = microkit_msginfo_get_count(msginfo); + switch (microkit_msginfo_get_label(msginfo)) { + case SDDF_TMU_SET_ENABLED: + if (ch != IRQ_FORWARD_CHANNEL) { + LOG_PMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_ENABLED!\n", ch); + err = SDDF_TMU_ERR_UNPERMITTED; + break; + } + case SDDF_TMU_SET_IRQ_MODE: + if (ch != IRQ_FORWARD_CHANNEL) { + LOG_PMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_IRQ_MODE!\n", ch); + err = SDDF_TMU_ERR_UNPERMITTED; + break; + } + case SDDF_TMU_SET_IRQ_THESHOLD: + if (ch != IRQ_FORWARD_CHANNEL) { + LOG_PMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_IRQ_THRESHOLD!\n", ch); + err = SDDF_TMU_ERR_UNPERMITTED; + break; + } + case SDDF_TMU_SET_GET_TEMP: + + default: + LOG_TMU_DRIVER_ERR("Unknown request %lu to TMU driver from channel %u\n", + microkit_msginfo_get_label(msginfo), ch); + err = SDDF_TMU_ERR_BAD_PPC_CALL; + } + + return microkit_msginfo_new(err, ret_num); +} diff --git a/drivers/tmu/imx8mq/tmu_driver.mk b/drivers/tmu/imx8mq/tmu_driver.mk new file mode 100644 index 000000000..d8b67d4e5 --- /dev/null +++ b/drivers/tmu/imx8mq/tmu_driver.mk @@ -0,0 +1,31 @@ +# +# Copyright 2026, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Include this snippet in your project Makefile to build +# the Meson tmu driver +# +# NOTES +# Generates tmu_driver.elf +# Requires libsddf_util_debug.a in ${LIBS} + +TMU_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +tmu_driver.elf: tmu/tmu_driver.o + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +tmu/tmu_driver.o: CFLAGS+=-I${TMU_DRIVER_DIR} +tmu/tmu_driver.o: ${TMU_DRIVER_DIR}/tmu.c |tmu $(SDDF_LIBC_INCLUDE) + ${CC} ${CFLAGS} -c -o $@ $< + +tmu: + mkdir -p $@ + +clean:: + rm -rf tmu + +clobber:: + rm -f tmu_driver.elf + +-include tmu_driver.d diff --git a/examples/tmu/Makefile b/examples/tmu/Makefile new file mode 100644 index 000000000..19369ba43 --- /dev/null +++ b/examples/tmu/Makefile @@ -0,0 +1,31 @@ +# +# Copyright 2026, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif +override MICROKIT_SDK := $(abspath ${MICROKIT_SDK}) + +BUILD_DIR ?= build +override BUILD_DIR := $(abspath ${BUILD_DIR}) +export BUILD_DIR +export MICROKIT_CONFIG ?= debug +export MICROKIT_BOARD ?= maaxboard + +export SDDF := $(abspath ../../) + +IMAGE_FILE := $(BUILD_DIR)/loader.img +REPORT_FILE := $(BUILD_DIR)/report.txt + +all: ${IMAGE_FILE} + +${IMAGE_FILE} ${REPORT_FILE} clean clobber: ${BUILD_DIR}/Makefile FORCE + ${MAKE} -C ${BUILD_DIR} MICROKIT_SDK=${MICROKIT_SDK} $(notdir $@) + +${BUILD_DIR}/Makefile: pmic.mk + mkdir -p ${BUILD_DIR} + cp pmic.mk $@ +FORCE: diff --git a/examples/tmu/client.c b/examples/tmu/client.c new file mode 100644 index 000000000..e51b76879 --- /dev/null +++ b/examples/tmu/client.c @@ -0,0 +1,110 @@ +/* + * Copyright 2026, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef CONFIG_PLAT_MAAXBOARD +#include +#define TARGET_REGULATOR (BD718XX_BUCK2) // VDD_ARM +#define VOLTAGE_A (900000) // 0.9V +#define VOLTAGE_B (1000000) // 1V +#else +#error "Unsupported board!" +#endif + +__attribute__((__section__(".timer_client_config"))) timer_client_config_t timer_config; +__attribute__((__section__(".serial_client_config"))) serial_client_config_t serial_config; + +cothread_t t_event; +cothread_t t_main; + +static serial_queue_handle_t serial_tx_queue_handle; + +// TODO: sdfgen for pmic client channel +#define PMIC_CHANNEL (0) + +#define STACK_SIZE (4096) +static char t_client_main_stack[STACK_SIZE]; + +#define DEBUG_CLIENT + +#ifdef DEBUG_CLIENT +#define LOG_CLIENT(...) do{ sddf_dprintf("SCAN|INFO: "); sddf_printf(__VA_ARGS__); }while(0) +#else +#define LOG_CLIENT(...) do{}while(0) +#endif +#define LOG_CLIENT_ERR(...) do{ sddf_printf("SCAN|ERROR: "); sddf_printf(__VA_ARGS__); }while(0) + +static inline bool delay_ms(size_t milliseconds) +{ + size_t time_ns = milliseconds * NS_IN_MS; + + /* Detect potential overflow */ + if (milliseconds != 0 && time_ns / milliseconds != NS_IN_MS) { + LOG_CLIENT_ERR("overflow detected in delay_ms"); + return false; + } + + sddf_timer_set_timeout(timer_config.driver_id, time_ns); + co_switch(t_event); + + return true; +} + +sddf_channel timer_channel; + +void notified(sddf_channel ch) +{ + if (ch == timer_config.driver_id) { + co_switch(t_main); + } else if (ch == serial_config.tx.id) { + // nothing to do + } else { + LOG_CLIENT_ERR("Unknown channel 0x%x!\n", ch); + } +} + +void client_main(void) { + LOG_CLIENT("Entered main loop.\n"); + for (uint32_t i = 0;; i++) { + // Alternate between setting voltage rail to 0.9 or 1V + if (i % 2) { + sddf_pmic_set_vout(PMIC_CHANNEL, TARGET_REGULATOR, VOLTAGE_A); + LOG_CLIENT("Set voltage of regulator %d to %zu\n", TARGET_REGULATOR, VOLTAGE_A); + } else { + sddf_pmic_set_vout(PMIC_CHANNEL, TARGET_REGULATOR, VOLTAGE_B); + LOG_CLIENT("Set voltage of regulator %d to %zu\n", TARGET_REGULATOR, VOLTAGE_B); + } + delay_ms(5000); + } +} + +void init(void) +{ + assert(serial_config_check_magic(&serial_config)); + serial_queue_init(&serial_tx_queue_handle, serial_config.tx.queue.vaddr, serial_config.tx.data.size, + serial_config.tx.data.vaddr); + serial_putchar_init(serial_config.tx.id, &serial_tx_queue_handle); + + assert(timer_config_check_magic(&timer_config)); + sddf_printf("CLIENT|INFO: starting\n"); + + timer_channel = timer_config.driver_id; + + /* Define the event loop/notified thread as the active co-routine */ + t_event = co_active(); + + /* derive main entry point */ + t_main = co_derive((void *)t_client_main_stack, STACK_SIZE, client_main); + + co_switch(t_main); +} diff --git a/examples/tmu/meta.py b/examples/tmu/meta.py new file mode 100644 index 000000000..68cc0c1cf --- /dev/null +++ b/examples/tmu/meta.py @@ -0,0 +1,89 @@ +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause +import os, sys +import argparse +from typing import List +from dataclasses import dataclass +from sdfgen import SystemDescription, Sddf, DeviceTree +from importlib.metadata import version + +sys.path.append( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../tools/meta") +) +from board import BOARDS + +assert version("sdfgen").split(".")[1] == "28", "Unexpected sdfgen version" + +ProtectionDomain = SystemDescription.ProtectionDomain +MemoryRegion = SystemDescription.MemoryRegion +Map = SystemDescription.Map +Channel = SystemDescription.Channel + + +def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): + serial_driver = ProtectionDomain("serial_driver", "serial_driver.elf", priority=200) + # Increase the stack size as running with UBSAN uses more stack space than normal. + serial_virt_tx = ProtectionDomain( + "serial_virt_tx", "serial_virt_tx.elf", priority=199, stack_size=0x2000 + ) + + timer_driver = ProtectionDomain("timer_driver", "timer_driver.elf", priority=7) + tmu_driver = ProtectionDomain("tmu_driver", "tmu_driver.elf", priority=4) + client = ProtectionDomain("client", "client.elf", priority=1) + + timer_node = dtb.node(board.timer) + assert timer_node is not None + serial_node = dtb.node(board.serial) + assert serial_node is not None + + timer_system = Sddf.Timer(sdf, timer_node, timer_driver) + timer_system.add_client(client) + + serial_system = Sddf.Serial( + sdf, serial_node, serial_driver, serial_virt_tx, enable_color=False + ) + serial_system.add_client(client) + + # Connect TMU client + # TODO: sdfgen for this + tmu_channel = Channel(tmu_driver, client, pp_b=True) + sdf.add_channel(tmu_channel) + + pds = [ + serial_driver, + serial_virt_tx, + timer_driver, + tmu_driver, + client, + ] + for pd in pds: + sdf.add_pd(pd) + + assert serial_system.connect() + assert serial_system.serialise_config(output_dir) + assert timer_system.connect() + assert timer_system.serialise_config(output_dir) + + with open(f"{output_dir}/{sdf_file}", "w+") as f: + f.write(sdf.render()) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--dtb", required=True) + parser.add_argument("--sddf", required=True) + parser.add_argument("--board", required=True, choices=[b.name for b in BOARDS]) + parser.add_argument("--output", required=True) + parser.add_argument("--sdf", required=True) + + args = parser.parse_args() + + board = next(filter(lambda b: b.name == args.board, BOARDS)) + + sdf = SystemDescription(board.arch, board.paddr_top) + sddf = Sddf(args.sddf) + + with open(args.dtb, "rb") as f: + dtb = DeviceTree(f.read()) + + generate(args.sdf, args.output, dtb) diff --git a/examples/tmu/tmu.mk b/examples/tmu/tmu.mk new file mode 100644 index 000000000..c5b3ae7b0 --- /dev/null +++ b/examples/tmu/tmu.mk @@ -0,0 +1,99 @@ +# +# Copyright 2026, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# +# This Makefile is copied into the build directory +# and operated on from there. +# + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(TOOLCHAIN)),) + TOOLCHAIN := clang +endif + +PYTHONPATH := ${SDDF}/tools/meta:${PYTHONPATH} +export PYTHONPATH + +SUPPORTED_BOARDS := \ + maaxboard + +include ${SDDF}/tools/make/board/common.mk + +SDDF_CUSTOM_LIBC := 1 +UTIL := $(SDDF)/util +LIBCO := $(SDDF)/libco +TOP := ${SDDF}/examples/tmu +SERIAL := $(SDDF)/serial +TIMER_DRIVER := $(SDDF)/drivers/timer/${TIMER_DRIV_DIR} +SERIAL_DRIVER := $(SDDF)/drivers/serial/${UART_DRIV_DIR} +TMU_DRIVER := $(SDDF)/drivers/tmu/${TMU_DRIV_DIR} + +IMAGES :=\ + client.elf \ + timer_driver.elf \ + serial_driver.elf \ + serial_virt_tx.elf \ + tmu_driver.elf + +LDFLAGS := -L$(BOARD_DIR)/lib +LIBS := --start-group -lmicrokit -Tmicrokit.ld libsddf_util_debug.a --end-group +CFLAGS += -Wno-unused-function -I${TOP} + +IMAGE_FILE = loader.img +REPORT_FILE = report.txt +SYSTEM_FILE = tmu.system + +DTS := $(SDDF)/dts/$(MICROKIT_BOARD).dts +DTB := $(MICROKIT_BOARD).dtb +METAPROGRAM := $(TOP)/meta.py + +CFLAGS += -I$(BOARD_DIR)/include \ + -I$(SDDF)/include \ + -I$(SDDF)/include/microkit \ + -I$(LIBCO) \ + -MD \ + -MP + +CLIENT_OBJS := client.o + +VPATH := ${TOP} +all: $(IMAGE_FILE) + +client.o: client.c + +client.elf: $(CLIENT_OBJS) libco.a libsddf_util.a + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +$(SYSTEM_FILE): $(METAPROGRAM) $(IMAGES) $(DTB) + $(PYTHON) $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) --dtb $(DTB) --output . --sdf $(SYSTEM_FILE) + $(OBJCOPY) --update-section .device_resources=timer_driver_device_resources.data timer_driver.elf + $(OBJCOPY) --update-section .timer_client_config=timer_client_client.data client.elf + $(OBJCOPY) --update-section .device_resources=serial_driver_device_resources.data serial_driver.elf + $(OBJCOPY) --update-section .serial_driver_config=serial_driver_config.data serial_driver.elf + $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx.data serial_virt_tx.elf + $(OBJCOPY) --update-section .serial_client_config=serial_client_client.data client.elf + touch $@ + +$(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) + $(MICROKIT_TOOL) $(SYSTEM_FILE) --search-path $(BUILD_DIR) --board $(MICROKIT_BOARD) --config $(MICROKIT_CONFIG) -o $(IMAGE_FILE) -r $(REPORT_FILE) + +${IMAGES}: libsddf_util_debug.a +.PHONY: all compile clean + +clean:: + rm -f *.elf + find . -name '*.[do]' |xargs --no-run-if-empty rm + +clobber:: clean + rm -f ${REPORT_FILE} ${IMAGE_FILE} *.a .*cflags* + +include ${SDDF}/util/util.mk +include ${SERIAL}/components/serial_components.mk +include ${SERIAL_DRIVER}/serial_driver.mk +include ${TIMER_DRIVER}/timer_driver.mk +include ${LIBCO}/libco.mk +include ${TMU_DRIVER}/tmu_driver.mk diff --git a/include/sddf/tmu/client.h b/include/sddf/tmu/client.h new file mode 100644 index 000000000..20a6bc45d --- /dev/null +++ b/include/sddf/tmu/client.h @@ -0,0 +1,82 @@ +/* + * Copyright 2026, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +/** + * Enable or disable the TMU via PPC to the passive TMU driver. + * @param channel of TMU driver. + * @param enable true to enable, false to disable. + * @return 0 on success, 1 on failure. + */ +static inline int sddf_tmu_set_enabled(microkit_channel channel, bool enable) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_TMU_SET_ENABLED, 1); + microkit_mr_set(SDDF_TMU_SET_ENABLED_ENABLE, enable ? 1 : 0); + + msginfo = microkit_ppcall(channel, msginfo); + + return (int)microkit_msginfo_get_label(msginfo); +} + +/** + * Set the IRQ forwarding mode via PPC to the passive TMU driver. + * @param channel of TMU driver. + * @param mode IRQ mode (disabled, instantaneous, or average). + * @return 0 on success, 1 on failure. + */ +static inline int sddf_tmu_set_irq_mode(microkit_channel channel, sddf_tmu_irq_modes mode) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_TMU_SET_IRQ_MODE, 1); + microkit_mr_set(SDDF_TMU_SET_IRQ_MODE_MODE, mode); + + msginfo = microkit_ppcall(channel, msginfo); + + return (int)microkit_msginfo_get_label(msginfo); +} + +/** + * Set the high temperature threshold for IRQ delivery via PPC to the passive TMU driver. + * @param channel of TMU driver. + * @param threshold high temperature threshold in degrees celsius. + * @return 0 on success, 1 on failure. + */ +static inline int sddf_tmu_set_irq_threshold(microkit_channel channel, int64_t threshold) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_TMU_SET_IRQ_THRESHOLD, 1); + microkit_mr_set(SDDF_TMU_SET_IRQ_THRESHOLD_THRESHOLD, threshold); + + msginfo = microkit_ppcall(channel, msginfo); + + return (int)microkit_msginfo_get_label(msginfo); +} + +/** + * Get temperature readings from the TMU via PPC to the passive TMU driver. + * @param channel of TMU driver. + * @param info pointer to structure to populate with temperature data. + * @return 0 on success, 1 on failure. + */ +static inline int sddf_tmu_get_temp(microkit_channel channel, sddf_tmu_temp_info_t *info) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_TMU_GET_TEMP, 0); + + msginfo = microkit_ppcall(channel, msginfo); + + int ret = (int)microkit_msginfo_get_label(msginfo); + if (ret == SDDF_TMU_GET_TEMP_SUCCESS) { + info->valid = microkit_mr_get(SDDF_TMU_GET_TEMP_VALIDITY); + info->temp_inst = microkit_mr_get(SDDF_TMU_GET_TEMP_INST); + info->temp_avg = microkit_mr_get(SDDF_TMU_GET_TEMP_AVG); + } + + return ret; +} + diff --git a/include/sddf/tmu/driver.h b/include/sddf/tmu/driver.h new file mode 100644 index 000000000..59a314d39 --- /dev/null +++ b/include/sddf/tmu/driver.h @@ -0,0 +1,96 @@ +/* + * Copyright 2026, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once +#include + +#define DEBUG_TMU_DRIVER +#ifdef DEBUG_TMU_DRIVER +#define LOG_TMU_DRIVER(...) do{ sddf_dprintf("TMU DRIVER|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) +#else +#define LOG_TMU_DRIVER(...) do{}while(0) +#endif + +#define LOG_TMU_DRIVER_ERR(...) do{ sddf_dprintf("TMU DRIVER|ERROR: "); sddf_dprintf(__VA_ARGS__); }while(0) + + +// TODO: we should extract this quantisation logic to a library. This is really similar +// to what is currently done in timers, i2c and more. The only difference here is that we +// use float, but that just means we need a float and non-float variant. +static inline sddf_temp_celsius_t find_quantised_unit(sddf_temp_celsius_t min_temp, + sddf_temp_celsius_t max_temp, + uint32_t quantisation) { + assert(min_temp < max_temp); + + // calculate value of a unit in this quantisation + // invariant: range is positive + sddf_temp_celsius_t range = max_temp - min_temp; + sddf_temp_celsius_t unit = range / (1 << quantisation); + assert(unit != 0); + return unit; +} + + +/** + * Given a temperature in degrees, return a quantised value to put in a device register. + * Args: + * val_degrees: temperature in degrees + * min_temp: minimum temperature represented in register + * max_temp: maximum temperature represented in register + * quantisation: number of bits used to represent value in hardware + * quantised_val: pointer to output + * + * Returns: + * sddf_tmu_err_t OK if fine, otherwise positive error value. + */ +sddf_tmu_err_t degrees_to_quantised(sddf_temp_celsius_t val_degrees, sddf_temp_celsius_t min_temp, + sddf_temp_celsius_t max_temp, uint32_t quantisation, + uint64_t *quantised_val) { + // Sanity: reject values that are invalid + if (val_degrees < min_temp || val_degrees >= max_temp) { + return SDDF_TMU_ERR_EINVAL; + } + + sddf_temp_celsius_t unit = find_quantised_unit(min_temp, max_temp, quantisation); + + // convert input temperature to unit + uint64_t val_in_units = (uint64_t)(val_degrees / unit); + + // if this doesn't fit in the register, we've made a mistake + assert(val_in_units <= (1 << quantisation)); + + *quantised_val = val_in_units; + return SDDF_TMU_ERR_OK; +} + +/** + * Given a temperature in quantised units, return a value in degrees celsius. + * Args: + * val_quantised: temperature in device register units + * min_temp: minimum temperature represented in register + * max_temp: maximum temperature represented in register + * quantisation: number of bits used to represent value in hardware + * degrees_celsius: pointer to output + * + * Returns: + * sddf_tmu_err_t OK if fine, otherwise positive error value. + */ +sddf_tmu_err_t degrees_to_quantised(uint64_t val_quantised, sddf_temp_celsius_t min_temp, + sddf_temp_celsius_t max_temp, uint32_t quantisation, + sddf_temp_celsius_t *degrees_celsius) { + + sddf_temp_celsius_t unit = find_quantised_unit(min_temp, max_temp, quantisation); + + // convert input quantisation to celsius + sddf_temp_celsius_t temp = ((sddf_temp_celsius_t)val_quantised * unit); + + // Sanity: if this temperature is outside of the valid temp range we have failed horribly. + assert(temp > min_temp && val_degrees <= max_temp); + + // Finally: return. + *quantised_val = val_in_units; + return SDDF_TMU_ERR_OK; +} diff --git a/include/sddf/tmu/protocol.h b/include/sddf/tmu/protocol.h new file mode 100644 index 000000000..7a64dda71 --- /dev/null +++ b/include/sddf/tmu/protocol.h @@ -0,0 +1,86 @@ +/* + * Copyright 2026, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once +#include +#include + +// PPC interface for interacting with TMU driver +// NOTE: this driver class currently only supports a SINGLE temperature source, +// ideally measuring the CPU or SoC temp. + +typedef double sddf_temp_celsius_t; +typedef enum { + SDDF_TMU_SET_ENABLED, + SDDF_TMU_SET_IRQ_MODE, + SDDF_TMU_SET_IRQ_THRESHOLD, + SDDF_TMU_GET_TEMP +} sddf_tmu_ppc_codes_t; + +typedef enum { + SDDF_TMU_IRQ_MODE_DISABLED, + SDDF_TMU_IRQ_MODE_INSTANTANEOUS, // IRQ on instant of threshold exceeding + SDDF_TMU_IRQ_MODE_AVG // IRQ when low-passed average exceeds theshold +} sddf_tmu_irq_modes_t; + +typedef enum { + SDDF_TMU_ERR_OK, + SDDF_TMU_ERR_UNPERMITTED, + SDDF_TMU_ERR_FAILED, + SDDF_TMU_ERR_EINVAL, + SDDF_TMU_NUM_ERRORS +} sddf_tmu_err_t; + +typedef struct tmu_temp_info { + sddf_temp_celsius_t temp_inst; + sddf_temp_celsius_t temp_avg; + bool valid; // Temp was outside of safe boundaries +} sddf_tmu_temp_info_t; + +// SDDF_TMU_SET_ENABLED +// Enable or disable the TMU. +// Args: +// MR0: 0 to disable, 1 to enable +// Returns: +// MR0: 0 on success, 1 on failure. +#define SDDF_TMU_SET_ENABLED_ENABLE (0) +#define SDDF_TMU_SET_ENABLED_SUCCESS (0) +#define SDDF_TMU_SET_ENABLED_FAIL (1) + +// SDDF_TMU_SET_IRQ_MODE +// Set the IRQ forwarding to disabled or active with a direct or low-pass average theshold. Forwarded interrupts are sent to +// Args: +// MR0: mode. 0 = disabled, 1 = instantaneous, 2 = average +// Returns: +// MR0: 0 on success, 1 on failure. +#define SDDF_TMU_SET_IRQ_MODE_MODE (0) +#define SDDF_TMU_SET_IRQ_MODE_SUCCESS (0) +#define SDDF_TMU_SET_IRQ_MODE_FAIL (1) + +// SDDF_TMU_SET_IRQ_THESHOLD +// Set the bounds for IRQ delivery +// Args: +// MR0: high theshold in degrees celsius +// Returns: +// MR0: 0 on success, 1 on failure. +#define SDDF_TMU_SET_IRQ_THRESHOLD_THRESHOLD (0) +#define SDDF_TMU_SET_IRQ_THRESHOLD_SUCCESS (0) +#define SDDF_TMU_SET_IRQ_THRESHOLD_FAIL (1) + +// SDDF_TMU_GET_TEMP +// Return temperature reading. +// Args: (none) +// Returns: +// MR0: 0 on success, 1 on failure. +// MR1: reading value. 0 if invalid, 1 if valid +// MR2: instantaneous temperature in celsius +// MR3: average temperature in celsius +#define SDDF_TMU_GET_TEMP_SUCCESS (0) +#define SDDF_TMU_GET_TEMP_FAIL (1) +#define SDDF_TMU_GET_TEMP_VALIDITY (1) +#define SDDF_TMU_GET_TEMP_INST (2) +#define SDDF_TMU_GET_TEMP_AVG (3) + From a9a1537e373c44a5dd44c7a644d5ee1413c5b798 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Wed, 11 Mar 2026 17:27:12 +1100 Subject: [PATCH 09/15] TMU driver and example complete Signed-off-by: Lesley Rossouw --- drivers/tmu/imx8mq/imx8mq-tmu.h | 136 +++++++-------------- drivers/tmu/imx8mq/tmu.c | 195 +++++++++++++++++++++++++------ drivers/tmu/imx8mq/tmu_driver.mk | 1 + examples/tmu/Makefile | 4 +- examples/tmu/client.c | 62 ++++++---- examples/tmu/meta.py | 15 +++ include/sddf/tmu/client.h | 26 +---- include/sddf/tmu/driver.h | 12 +- include/sddf/tmu/protocol.h | 40 ++----- tools/make/board/maaxboard.mk | 1 + 10 files changed, 284 insertions(+), 208 deletions(-) diff --git a/drivers/tmu/imx8mq/imx8mq-tmu.h b/drivers/tmu/imx8mq/imx8mq-tmu.h index eb9a393e8..de191b8b8 100644 --- a/drivers/tmu/imx8mq/imx8mq-tmu.h +++ b/drivers/tmu/imx8mq/imx8mq-tmu.h @@ -3,6 +3,8 @@ * * SPDX-License-Identifier: BSD-2-Clause */ +#pragma once +#include typedef struct imx8mq_tmu_regs { uint32_t tmr; /* 0x00 - TMU mode register */ @@ -41,6 +43,7 @@ typedef struct imx8mq_tmu_regs { #define SENSOR_MAX_TEMP ((sddf_temp_celsius_t) 85) #define SENSOR_MIN_TEMP ((sddf_temp_celsius_t) 0) +#define SENSOR_QUANTISATION (8) /* * Register fields. @@ -51,72 +54,49 @@ typedef struct imx8mq_tmu_regs { */ // TMR - TMU Mode Register -// Bit 31: ME, Bits 15-8: MSITE -#define TMU_TMR_ME_MASK (0x80000000) -#define TMU_TMR_ME_OFFSET (31) -#define TMU_TMR_ME_BIT (0x80000000) -#define TMU_TMR_MSITE_MASK (0x0000FF00) -#define TMU_TMR_MSITE_OFFSET (8) +#define TMU_TMR_ME_BIT (BIT(31)) +#define TMU_TMR_MSITE_MASK (0x7) +#define TMU_TMR_MSITE_OFFSET (13) +#define TMU_TMR_MSITE_ARM_BIT (BIT(13)) +#define TMU_TMR_MSITE_GPU_BIT (BIT(14)) +#define TMU_TMR_MSITE_VPU_BIT (BIT(15)) +#define TMU_TMR_ALPF_MASK (0x3) +#define TMU_TMR_ALPF_OFFSET (26) // TSR - TMU Status Register -// Bit 31: MIE, Bit 1: ORL, Bit 0: ORH -#define TMU_TSR_MIE_MASK (0x80000000) -#define TMU_TSR_MIE_OFFSET (31) -#define TMU_TSR_MIE_BIT (0x80000000) -#define TMU_TSR_ORL_MASK (0x00000002) -#define TMU_TSR_ORL_OFFSET (1) -#define TMU_TSR_ORL_BIT (0x00000002) -#define TMU_TSR_ORH_MASK (0x00000001) -#define TMU_TSR_ORH_OFFSET (0) -#define TMU_TSR_ORH_BIT (0x00000001) +#define TMU_TSR_MIE_BIT (BIT(30)) +#define TMU_TSR_ORL_BIT (BIT(29)) +#define TMU_TSR_ORH_BIT (BIT(28)) // TMTMIR - TMU Monitor Temperature Measurement Interval Register -// Bits 15-0: TMI -#define TMU_TMTMIR_TMI_MASK (0x0000FFFF) -#define TMU_TMTMIR_TMI_OFFSET (0) +#define TMU_TMTMIR_TMI_MASK (0xFFFF) // TIER - TMU Interrupt Enable Register -// Bit 2: ITTEIE, Bit 1: ATTEIE, Bit 0: ATCTEIE -#define TMU_TIER_ITTEIE_MASK (0x00000004) -#define TMU_TIER_ITTEIE_OFFSET (2) -#define TMU_TIER_ITTEIE_BIT (0x00000004) -#define TMU_TIER_ATTEIE_MASK (0x00000002) -#define TMU_TIER_ATTEIE_OFFSET (1) -#define TMU_TIER_ATTEIE_BIT (0x00000002) -#define TMU_TIER_ATCTEIE_MASK (0x00000001) -#define TMU_TIER_ATCTEIE_OFFSET (0) -#define TMU_TIER_ATCTEIE_BIT (0x00000001) +#define TMU_TIER_ITTEIE_BIT (BIT(31)) +#define TMU_TIER_ATTEIE_BIT (BIT(30)) +#define TMU_TIER_ATCTEIE_BIT (BIT(29)) // TIDR - TMU Interrupt Detect Register -// Bit 2: ITTE, Bit 1: ATTE, Bit 0: ATCTE (W1C) -#define TMU_TIDR_ITTE_MASK (0x00000004) -#define TMU_TIDR_ITTE_OFFSET (2) -#define TMU_TIDR_ITTE_BIT (0x00000004) -#define TMU_TIDR_ATTE_MASK (0x00000002) -#define TMU_TIDR_ATTE_OFFSET (1) -#define TMU_TIDR_ATTE_BIT (0x00000002) -#define TMU_TIDR_ATCTE_MASK (0x00000001) -#define TMU_TIDR_ATCTE_OFFSET (0) -#define TMU_TIDR_ATCTE_BIT (0x00000001) +#define TMU_TIDR_ITTE_BIT (BIT(31)) +#define TMU_TIDR_ATTE_BIT (BIT(30)) +#define TMU_TIDR_ATCTE_BIT (BIT(29)) // TISCR - TMU Interrupt Site Capture Register -// Bits 10-8: ISITE, Bits 2-0: ASITE -#define TMU_TISCR_ISITE_MASK (0x00000700) -#define TMU_TISCR_ISITE_OFFSET (8) -#define TMU_TISCR_ASITE_MASK (0x00000007) -#define TMU_TISCR_ASITE_OFFSET (0) +#define TMU_TISCR_ISITE_MASK (0x7) +#define TMU_TISCR_ISITE_OFFSET (29) +#define TMU_TISCR_ASITE_MASK (0x7) +#define TMU_TISCR_ASITE_OFFSET (13) // TICSCR - TMU Interrupt Critical Site Capture Register -// Bits 2-0: CASITE -#define TMU_TICSCR_CASITE_MASK (0x00000007) -#define TMU_TICSCR_CASITE_OFFSET (0) +#define TMU_TICSCR_CASITE_MASK (0x7) +#define TMU_TICSCR_CASITE_OFFSET (13) // TMHTCR - TMU Monitor High Temperature Capture Register // Bit 31: V, Bits 11-0: TEMP #define TMU_TMHTCR_V_MASK (0x80000000) #define TMU_TMHTCR_V_OFFSET (31) #define TMU_TMHTCR_V_BIT (0x80000000) -#define TMU_TMHTCR_TEMP_MASK (0x00000FFF) +#define TMU_TMHTCR_TEMP_MASK (0x0FFF) #define TMU_TMHTCR_TEMP_OFFSET (0) // TMLTCR - TMU Monitor Low Temperature Capture Register @@ -124,63 +104,25 @@ typedef struct imx8mq_tmu_regs { #define TMU_TMLTCR_V_MASK (0x80000000) #define TMU_TMLTCR_V_OFFSET (31) #define TMU_TMLTCR_V_BIT (0x80000000) -#define TMU_TMLTCR_TEMP_MASK (0x00000FFF) -#define TMU_TMLTCR_TEMP_OFFSET (0) +#define TMU_TMLTCR_TEMP_MASK (0x0FFF) // TMHTITR - TMU Monitor High Temperature Immediate Threshold Register -// Bit 31: EN, Bits 11-0: TEMP -#define TMU_TMHTITR_EN_MASK (0x80000000) -#define TMU_TMHTITR_EN_OFFSET (31) -#define TMU_TMHTITR_EN_BIT (0x80000000) -#define TMU_TMHTITR_TEMP_MASK (0x00000FFF) -#define TMU_TMHTITR_TEMP_OFFSET (0) +#define TMU_TMHTITR_EN_BIT (BIT(31)) +#define TMU_TMHTITR_TEMP_MASK (0xff) // TMHTATR - TMU Monitor High Temperature Average Threshold Register -// Bit 31: EN, Bits 11-0: TEMP -#define TMU_TMHTATR_EN_MASK (0x80000000) -#define TMU_TMHTATR_EN_OFFSET (31) -#define TMU_TMHTATR_EN_BIT (0x80000000) -#define TMU_TMHTATR_TEMP_MASK (0x00000FFF) -#define TMU_TMHTATR_TEMP_OFFSET (0) +#define TMU_TMHTATR_EN_BIT (BIT(31)) +#define TMU_TMHTATR_TEMP_MASK (0xFF) // TMHTACTR - TMU Monitor High Temperature Average Critical Threshold Register -// Bit 31: EN, Bits 11-0: TEMP -#define TMU_TMHTACTR_EN_MASK (0x80000000) -#define TMU_TMHTACTR_EN_OFFSET (31) -#define TMU_TMHTACTR_EN_BIT (0x80000000) -#define TMU_TMHTACTR_TEMP_MASK (0x00000FFF) -#define TMU_TMHTACTR_TEMP_OFFSET (0) - -// TTCFGR - TMU Temperature Configuration Register -// Bits 31-0: DATA -#define TMU_TTCFGR_DATA_MASK (0xFFFFFFFF) -#define TMU_TTCFGR_DATA_OFFSET (0) - -// TSCFGR - TMU Sensor Configuration Register -// Bits 31-0: DATA -#define TMU_TSCFGR_DATA_MASK (0xFFFFFFFF) -#define TMU_TSCFGR_DATA_OFFSET (0) +#define TMU_TMHTACTR_EN_BIT (BIT(31)) +#define TMU_TMHTACTR_TEMP_MASK (0xFF) // TRITSRn - TMU Report Immediate Temperature Site Registers -// Bit 31: V, Bits 11-0: TEMP -#define TMU_TRITSR_V_MASK (0x80000000) -#define TMU_TRITSR_V_OFFSET (31) -#define TMU_TRITSR_V_BIT (0x80000000) -#define TMU_TRITSR_TEMP_MASK (0x00000FFF) -#define TMU_TRITSR_TEMP_OFFSET (0) +#define TMU_TRITSR_V_BIT (BIT(31)) +#define TMU_TRITSR_TEMP_MASK (0xff) // TRATSRn - TMU Report Average Temperature Site Registers -// Bit 31: V, Bits 11-0: TEMP -#define TMU_TRATSR_V_MASK (0x80000000) -#define TMU_TRATSR_V_OFFSET (31) -#define TMU_TRATSR_V_BIT (0x80000000) -#define TMU_TRATSR_TEMP_MASK (0x00000FFF) -#define TMU_TRATSR_TEMP_OFFSET (0) - -// TTRnCR - TMU Temperature Range Control Registers -// Bits 23-16: CAL_PTR, Bits 11-0: TEMP -#define TMU_TTRCR_CAL_PTR_MASK (0x00FF0000) -#define TMU_TTRCR_CAL_PTR_OFFSET (16) -#define TMU_TTRCR_TEMP_MASK (0x00000FFF) -#define TMU_TTRCR_TEMP_OFFSET (0) +#define TMU_TRATSR_V_BIT (BIT(31)) +#define TMU_TRATSR_TEMP_MASK (0xFF) diff --git a/drivers/tmu/imx8mq/tmu.c b/drivers/tmu/imx8mq/tmu.c index 249e69070..6ad5b8639 100644 --- a/drivers/tmu/imx8mq/tmu.c +++ b/drivers/tmu/imx8mq/tmu.c @@ -17,73 +17,198 @@ #include #include #include -#include "imx8mq-tmu" +#include "imx8mq-tmu.h" // TODO: add sdfgen support for IRQ forward channel and critical temp -#define IRQ_FORWARD_CHANNEL (0) -#define IRQ_INST_CHANNEL (1) -#define IRQ_AVG_CHANNEL (2) -#define IRQ_CRITICAL_CHANNEL (3) +#define IRQ_CHANNEL (0) +#define IRQ_FORWARD_CHANNEL (1) #define CRITICAL_TEMP ((sddf_temp_celsius_t )85) // Warnings will be printed if this is exceeded +// TODO: sdfgen support for passing in this resource +#define TMU_REG_BASE (0x30260000) +volatile imx8mq_tmu_regs_t *regs; + sddf_tmu_irq_modes_t current_irq_mode = SDDF_TMU_IRQ_MODE_DISABLED; +sddf_temp_celsius_t current_irq_thresh = SENSOR_MAX_TEMP; + +static inline void enable_tmu(void) { + // disable monitoring to reconfigure + regs->tmr &= ~TMU_TMR_ME_BIT; + // clear irqs + regs->tidr |= (TMU_TIDR_ITTE_BIT | TMU_TIDR_ATTE_BIT | TMU_TIDR_ATCTE_BIT); + + #ifdef SDDF_PMU_ENABLE_IRQ + // clear interrupt site capture register + regs->tiscr &= ~(TMU_TISCR_ASITE_MASK << TMU_TISCR_ASITE_OFFSET); + regs->tiscr &= ~(TMU_TISCR_ISITE_MASK << TMU_TISCR_ISITE_OFFSET); + + // clear critical interrupt site capture register + regs->ticscr &= ~(TMU_TICSCR_CASITE_MASK << TMU_TICSCR_CASITE_OFFSET); + + // enable critical irq and suitable IRQ mode bits + regs->tier |= TMU_TIER_ATCTEIE_BIT; + if (current_irq_mode == SDDF_TMU_IRQ_MODE_INSTANTANEOUS) { + regs->tier |= TMU_TIER_ITTEIE_BIT; + } else if (current_irq_mode == SDDF_TMU_IRQ_MODE_AVG) { + regs->tier |= TMU_TIER_ATTEIE_BIT; + } + + // Set critical temp. We don't use this for the protocol, but it is a handy utility + // to print errors if the board is in the process of melting down. + // enable theshold monitoring + program critical temperature theshold + regs->tmhtactr |= ((TMU_TMHTACTR_EN_BIT) | (CRITICAL_TEMP & TMU_TMHTACTR_TEMP_MASK)); + + // repeat for IRQ mode threshold (must happen after enabling IRQ) + if (current_irq_mode == SDDF_TMU_IRQ_MODE_INSTANTANEOUS) { + regs->tmhtitr |= (TMU_TMHTITR_EN_BIT | ((uint64_t)current_irq_thresh & TMU_TMHTITR_TEMP_MASK)); + } else if (current_irq_mode == SDDF_TMU_IRQ_MODE_AVG) { + regs->tmhtatr |= (TMU_TMHTATR_EN_BIT | ((uint64_t)current_irq_thresh & TMU_TMHTATR_TEMP_MASK)); + } + #endif + // set low pass filter for average to 4 samples + regs->tmr |= (0b10 & TMU_TMR_ALPF_MASK) << TMU_TMR_ALPF_OFFSET; + // set a conservative polling rate that should be adequate for all input clock + // speeds. rate is 0.08 to 0.2 seconds depending on input clock. + regs->tmtmir |= (0b0011 & TMU_TMTMIR_TMI_MASK); + + // enable whole monitor and enable CPU monitoring site + regs->tmr |= (TMU_TMR_ME_BIT | TMU_TMR_MSITE_ARM_BIT); +} + +static inline void get_temp(sddf_tmu_temp_info_t *info) { + info->valid_inst = (regs->tritsr0 & TMU_TRITSR_V_BIT) != 0; + info->valid_avg = (regs->tratsr0 & TMU_TRATSR_V_BIT) != 0; + uint64_t inst = regs->tritsr0 & TMU_TRITSR_TEMP_MASK; + uint64_t avg = regs->tratsr0 & TMU_TRATSR_TEMP_MASK; + info->temp_inst = (sddf_temp_celsius_t) inst; + info->temp_avg = (sddf_temp_celsius_t) avg; + if (!info->valid_inst) { + LOG_TMU_DRIVER_ERR("Invalid instantaneous reading detected\n"); + } + if (!info->valid_avg) { + LOG_TMU_DRIVER_ERR("Invalid average reading detected\n"); + } +} void init(void) { - // Set critical temp - uint64_t critical_temp_quantised = 0; - sddf_tmu_err_t ret = degrees_to_quantised(CRITICAL_TEMP, SENSOR_MIN_TEMP, SENSOR_MAX_TEMP, - SENSOR_QUANTISATION, &critical_temp_quantised); - assert(!ret); + // TODO: replace with device resource + regs = (volatile imx8mq_tmu_regs_t *)TMU_REG_BASE; + enable_tmu(); } void notified(microkit_channel ch) { + + // Sanity: make sure we are still monitoring. If the monitoring interval is exceeded, + // the device will stop silently. + if (regs->tsr & TMU_TSR_MIE_BIT) { + LOG_TMU_DRIVER_ERR("Monitoring has failed due to polling timing out!\n"); + assert(false); // If you get this, increase the interval setting in init() + } + if (ch == IRQ_FORWARD_CHANNEL) { LOG_TMU_DRIVER_ERR("IRQ forward channel should not notify driver!"); - } else if (ch == IRQ_INST_CHANNEL) { - LOG_TMU_DRIVER("Instantaneous value threshold reached!\n"); - if (current_irq_mode != SDDF_TMU_IRQ_MODE_INSTANTANEOUS) { - LOG_TMU_DRIVER_ERR("Received spurious instantaneous threshold IRQ!\n"); + return; + } else if (ch == IRQ_CHANNEL) { + // check IRQ source + if (regs->tidr & TMU_TIDR_ITTE_BIT) { + LOG_TMU_DRIVER("Instantaneous value threshold reached!\n"); + if (current_irq_mode != SDDF_TMU_IRQ_MODE_INSTANTANEOUS) { + LOG_TMU_DRIVER_ERR("Received spurious instantaneous threshold IRQ!\n"); + } + microkit_notify(IRQ_FORWARD_CHANNEL); } - } else if (ch == IRQ_AVG_CHANNEL) { - LOG_TMU_DRIVER("Average value threshold reached!\n"); - if (current_irq_mode != SDDF_TMU_IRQ_MODE_AVG) { - LOG_TMU_DRIVER_ERR("Received spurious average threshold IRQ!\n"); + if (regs->tidr & TMU_TIDR_ATTE_BIT) { + LOG_TMU_DRIVER("Average value threshold reached!\n"); + if (current_irq_mode != SDDF_TMU_IRQ_MODE_AVG) { + LOG_TMU_DRIVER_ERR("Received spurious average threshold IRQ!\n"); + } + microkit_notify(IRQ_FORWARD_CHANNEL); } - } else if (ch == IRQ_CRITICAL_CHANNEL) { - LOG_TMU_DRIVER_ERR("WARNING: critical temperature of %f exceeded! System may be damaged!\n", - CRITICAL_TEMP); - // clear IRQ state + if (regs->tidr & TMU_TIDR_ATCTE_BIT) { + LOG_TMU_DRIVER_ERR("WARNING: critical temperature of %f exceeded! System may be damaged!\n", + CRITICAL_TEMP); + } } else { LOG_TMU_DRIVER_ERR("Unknown channel 0x%x!\n", ch); } + // Check if temperature exceeded range + bool invalid_low = regs->tsr & TMU_TSR_ORL_BIT; + bool invalid_high = regs->tsr & TMU_TSR_ORH_BIT; + if (invalid_low) { + LOG_TMU_DRIVER_ERR("Avg monitoring detected temperature <= %f (out of range)!\n", + SENSOR_MIN_TEMP); + } + if (invalid_high) { + LOG_TMU_DRIVER_ERR("Avg monitoring detected temperature >= %f (out of range)!\n", + SENSOR_MIN_TEMP); + } + if (invalid_high && invalid_low) { + LOG_TMU_DRIVER_ERR("Temperatures are out of high and low range! Something is wrong!\n"); + } + microkit_irq_ack(ch); + + // Re-enable the device to clear all IRQ flags. + // There are lots of conditions that cause the device to stop, requiring multiple bits. + // It's easier to simply reset than to handle all of these! + enable_tmu(); } microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) { - sddf_pmic_err_t err = 0; - uint64_t ret_num = 0; - uint32_t argc = microkit_msginfo_get_count(msginfo); + // Sanity: make sure we are still monitoring. If the monitoring interval is exceeded, + // the device will stop silently. + if (regs->tsr & TMU_TSR_MIE_BIT) { + LOG_TMU_DRIVER_ERR("Monitoring has failed due to polling timing out!\n"); + assert(false); // If you get this, increase the interval setting in init() + } + sddf_tmu_err_t err = SDDF_TMU_ERR_OK; + uint64_t ret_num = 1; + sddf_tmu_temp_info_t temp; switch (microkit_msginfo_get_label(msginfo)) { - case SDDF_TMU_SET_ENABLED: - if (ch != IRQ_FORWARD_CHANNEL) { - LOG_PMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_ENABLED!\n", ch); - err = SDDF_TMU_ERR_UNPERMITTED; - break; - } case SDDF_TMU_SET_IRQ_MODE: if (ch != IRQ_FORWARD_CHANNEL) { - LOG_PMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_IRQ_MODE!\n", ch); + LOG_TMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_IRQ_MODE!\n", ch); err = SDDF_TMU_ERR_UNPERMITTED; break; } - case SDDF_TMU_SET_IRQ_THESHOLD: + sddf_tmu_irq_modes_t new_mode = (sddf_tmu_irq_modes_t) + microkit_mr_get(SDDF_TMU_SET_IRQ_MODE_MODE); + + if (new_mode >= SDDF_TMU_IRQ_MODES_NUM) { + LOG_TMU_DRIVER_ERR("Invalid IRQ mode %d supplied!\n", new_mode); + err = SDDF_TMU_ERR_EINVAL; + } else { + current_irq_mode = new_mode; + enable_tmu(); + } + break; + + case SDDF_TMU_SET_IRQ_THRESHOLD: if (ch != IRQ_FORWARD_CHANNEL) { - LOG_PMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_IRQ_THRESHOLD!\n", ch); + LOG_TMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_IRQ_THRESHOLD!\n", ch); err = SDDF_TMU_ERR_UNPERMITTED; break; } - case SDDF_TMU_SET_GET_TEMP: + sddf_temp_celsius_t new_temp = (sddf_temp_celsius_t) microkit_mr_get(SDDF_TMU_SET_IRQ_THESHOLD_THESH); + // Check theshold is valid + if (new_temp > SENSOR_MAX_TEMP || new_temp < SENSOR_MIN_TEMP) { + LOG_TMU_DRIVER_ERR("Invalid temperature %f, must be in range [%f, %f]\n", + new_temp, SENSOR_MIN_TEMP, SENSOR_MAX_TEMP); + err = SDDF_TMU_ERR_EINVAL; + } else { + current_irq_thresh = new_temp; + LOG_TMU_DRIVER("IRQ thesh set to %f\n", new_temp); + enable_tmu(); + } + break; + case SDDF_TMU_GET_TEMP: + get_temp(&temp); + microkit_mr_set(SDDF_TMU_GET_TEMP_VALIDITY, (temp.valid_inst | (temp.valid_avg << 1))); + microkit_mr_set(SDDF_TMU_GET_TEMP_INST, temp.temp_inst); + microkit_mr_set(SDDF_TMU_GET_TEMP_AVG, temp.temp_avg); + ret_num = 3; + break; default: LOG_TMU_DRIVER_ERR("Unknown request %lu to TMU driver from channel %u\n", diff --git a/drivers/tmu/imx8mq/tmu_driver.mk b/drivers/tmu/imx8mq/tmu_driver.mk index d8b67d4e5..60abdbe55 100644 --- a/drivers/tmu/imx8mq/tmu_driver.mk +++ b/drivers/tmu/imx8mq/tmu_driver.mk @@ -8,6 +8,7 @@ # # NOTES # Generates tmu_driver.elf +# Requires floating point support # Requires libsddf_util_debug.a in ${LIBS} TMU_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) diff --git a/examples/tmu/Makefile b/examples/tmu/Makefile index 19369ba43..464c82719 100644 --- a/examples/tmu/Makefile +++ b/examples/tmu/Makefile @@ -25,7 +25,7 @@ all: ${IMAGE_FILE} ${IMAGE_FILE} ${REPORT_FILE} clean clobber: ${BUILD_DIR}/Makefile FORCE ${MAKE} -C ${BUILD_DIR} MICROKIT_SDK=${MICROKIT_SDK} $(notdir $@) -${BUILD_DIR}/Makefile: pmic.mk +${BUILD_DIR}/Makefile: tmu.mk mkdir -p ${BUILD_DIR} - cp pmic.mk $@ + cp tmu.mk $@ FORCE: diff --git a/examples/tmu/client.c b/examples/tmu/client.c index e51b76879..62870cb9b 100644 --- a/examples/tmu/client.c +++ b/examples/tmu/client.c @@ -11,15 +11,7 @@ #include #include #include -#include -#ifdef CONFIG_PLAT_MAAXBOARD -#include -#define TARGET_REGULATOR (BD718XX_BUCK2) // VDD_ARM -#define VOLTAGE_A (900000) // 0.9V -#define VOLTAGE_B (1000000) // 1V -#else -#error "Unsupported board!" -#endif +#include __attribute__((__section__(".timer_client_config"))) timer_client_config_t timer_config; __attribute__((__section__(".serial_client_config"))) serial_client_config_t serial_config; @@ -29,8 +21,8 @@ cothread_t t_main; static serial_queue_handle_t serial_tx_queue_handle; -// TODO: sdfgen for pmic client channel -#define PMIC_CHANNEL (0) +// TODO: sdfgen for tmu client channel +#define TMU_CHANNEL (0) #define STACK_SIZE (4096) static char t_client_main_stack[STACK_SIZE]; @@ -38,11 +30,11 @@ static char t_client_main_stack[STACK_SIZE]; #define DEBUG_CLIENT #ifdef DEBUG_CLIENT -#define LOG_CLIENT(...) do{ sddf_dprintf("SCAN|INFO: "); sddf_printf(__VA_ARGS__); }while(0) +#define LOG_CLIENT(...) do{ sddf_dprintf("TMU_CLIENT|INFO: "); sddf_printf(__VA_ARGS__); }while(0) #else #define LOG_CLIENT(...) do{}while(0) #endif -#define LOG_CLIENT_ERR(...) do{ sddf_printf("SCAN|ERROR: "); sddf_printf(__VA_ARGS__); }while(0) +#define LOG_CLIENT_ERR(...) do{ sddf_printf("TMU_CLIENT|ERROR: "); sddf_printf(__VA_ARGS__); }while(0) static inline bool delay_ms(size_t milliseconds) { @@ -68,23 +60,51 @@ void notified(sddf_channel ch) co_switch(t_main); } else if (ch == serial_config.tx.id) { // nothing to do + } else if (ch == TMU_CHANNEL) { + LOG_CLIENT_ERR("Warning: IRQ forwarded!\n"); } else { LOG_CLIENT_ERR("Unknown channel 0x%x!\n", ch); } } +static uint64_t busywork_magic = 0; + void client_main(void) { LOG_CLIENT("Entered main loop.\n"); - for (uint32_t i = 0;; i++) { - // Alternate between setting voltage rail to 0.9 or 1V - if (i % 2) { - sddf_pmic_set_vout(PMIC_CHANNEL, TARGET_REGULATOR, VOLTAGE_A); - LOG_CLIENT("Set voltage of regulator %d to %zu\n", TARGET_REGULATOR, VOLTAGE_A); + int ret; + #ifdef SDDF_PMU_ENABLE_IRQ + // Set an average temperature IRQ forward @ 45 deg C + int ret = sddf_tmu_set_irq_mode(TMU_CHANNEL, SDDF_TMU_IRQ_MODE_AVG); + assert(!ret); + ret = sddf_tmu_set_irq_threshold(TMU_CHANNEL, 45.0); + assert(!ret); + #endif + + sddf_tmu_temp_info_t temp_info; + for (;;) { + // Get temperature and print + ret = sddf_tmu_get_temp(TMU_CHANNEL, &temp_info); + if (ret) { + LOG_CLIENT_ERR("Failed to get temperature!\n"); } else { - sddf_pmic_set_vout(PMIC_CHANNEL, TARGET_REGULATOR, VOLTAGE_B); - LOG_CLIENT("Set voltage of regulator %d to %zu\n", TARGET_REGULATOR, VOLTAGE_B); + LOG_CLIENT("\n\nRead successfully!\n"); + LOG_CLIENT("\tAvg. valid: %d\n", temp_info.valid_avg); + LOG_CLIENT("\tAvg. temp: %f\n", temp_info.temp_avg); + LOG_CLIENT("\tInst. valid: %d\n", temp_info.valid_inst); + LOG_CLIENT("\tInst. temp: %f\n", temp_info.temp_inst); + } + // delay_ms(2000); + // Busy wait to make heat + for (uint64_t i = 0; i < 100000000; i++) { + busywork_magic++; + busywork_magic = ((busywork_magic / 2) << 3) - 5; + busywork_magic = (busywork_magic * busywork_magic) + 300; + if (busywork_magic < 500) { + busywork_magic = busywork_magic * 718; + } else { + busywork_magic = busywork_magic - (300*busywork_magic); + } } - delay_ms(5000); } } diff --git a/examples/tmu/meta.py b/examples/tmu/meta.py index 68cc0c1cf..888c61baf 100644 --- a/examples/tmu/meta.py +++ b/examples/tmu/meta.py @@ -18,6 +18,7 @@ MemoryRegion = SystemDescription.MemoryRegion Map = SystemDescription.Map Channel = SystemDescription.Channel +IrqConventional = SystemDescription.IrqConventional def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): @@ -44,6 +45,20 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): ) serial_system.add_client(client) + + # TODO: replace with sdfgen + if board.name == "maaxboard": + tmu_mr = MemoryRegion(sdf, "tmu_mr", 0x1000, paddr=0x30260000) + sdf.add_mr(tmu_mr) + + tmu_mr_map = Map(tmu_mr, 0x30260000, "rw", cached=False) + tmu_driver.add_map(tmu_mr_map) + tmu_driver.add_irq(IrqConventional(49 + 32, IrqConventional.Trigger.EDGE)) + + else: + print("Unsupported board!") + exit(-1) + # Connect TMU client # TODO: sdfgen for this tmu_channel = Channel(tmu_driver, client, pp_b=True) diff --git a/include/sddf/tmu/client.h b/include/sddf/tmu/client.h index 20a6bc45d..59df69d48 100644 --- a/include/sddf/tmu/client.h +++ b/include/sddf/tmu/client.h @@ -10,29 +10,13 @@ #include #include -/** - * Enable or disable the TMU via PPC to the passive TMU driver. - * @param channel of TMU driver. - * @param enable true to enable, false to disable. - * @return 0 on success, 1 on failure. - */ -static inline int sddf_tmu_set_enabled(microkit_channel channel, bool enable) -{ - microkit_msginfo msginfo = microkit_msginfo_new(SDDF_TMU_SET_ENABLED, 1); - microkit_mr_set(SDDF_TMU_SET_ENABLED_ENABLE, enable ? 1 : 0); - - msginfo = microkit_ppcall(channel, msginfo); - - return (int)microkit_msginfo_get_label(msginfo); -} - /** * Set the IRQ forwarding mode via PPC to the passive TMU driver. * @param channel of TMU driver. * @param mode IRQ mode (disabled, instantaneous, or average). * @return 0 on success, 1 on failure. */ -static inline int sddf_tmu_set_irq_mode(microkit_channel channel, sddf_tmu_irq_modes mode) +static inline int sddf_tmu_set_irq_mode(microkit_channel channel, sddf_tmu_irq_modes_t mode) { microkit_msginfo msginfo = microkit_msginfo_new(SDDF_TMU_SET_IRQ_MODE, 1); microkit_mr_set(SDDF_TMU_SET_IRQ_MODE_MODE, mode); @@ -51,7 +35,7 @@ static inline int sddf_tmu_set_irq_mode(microkit_channel channel, sddf_tmu_irq_m static inline int sddf_tmu_set_irq_threshold(microkit_channel channel, int64_t threshold) { microkit_msginfo msginfo = microkit_msginfo_new(SDDF_TMU_SET_IRQ_THRESHOLD, 1); - microkit_mr_set(SDDF_TMU_SET_IRQ_THRESHOLD_THRESHOLD, threshold); + microkit_mr_set(0, threshold); msginfo = microkit_ppcall(channel, msginfo); @@ -71,8 +55,10 @@ static inline int sddf_tmu_get_temp(microkit_channel channel, sddf_tmu_temp_info msginfo = microkit_ppcall(channel, msginfo); int ret = (int)microkit_msginfo_get_label(msginfo); - if (ret == SDDF_TMU_GET_TEMP_SUCCESS) { - info->valid = microkit_mr_get(SDDF_TMU_GET_TEMP_VALIDITY); + if (ret == SDDF_TMU_ERR_OK) { + uint64_t valid = microkit_mr_get(SDDF_TMU_GET_TEMP_VALIDITY); + info->valid_inst = valid & 0b1; + info->valid_inst = (valid & 0b10) >> 1; info->temp_inst = microkit_mr_get(SDDF_TMU_GET_TEMP_INST); info->temp_avg = microkit_mr_get(SDDF_TMU_GET_TEMP_AVG); } diff --git a/include/sddf/tmu/driver.h b/include/sddf/tmu/driver.h index 59a314d39..a2867a6e7 100644 --- a/include/sddf/tmu/driver.h +++ b/include/sddf/tmu/driver.h @@ -5,7 +5,9 @@ */ #pragma once -#include +#include +#include +#include #define DEBUG_TMU_DRIVER #ifdef DEBUG_TMU_DRIVER @@ -50,7 +52,7 @@ sddf_tmu_err_t degrees_to_quantised(sddf_temp_celsius_t val_degrees, sddf_temp_c sddf_temp_celsius_t max_temp, uint32_t quantisation, uint64_t *quantised_val) { // Sanity: reject values that are invalid - if (val_degrees < min_temp || val_degrees >= max_temp) { + if (val_degrees < min_temp || val_degrees > max_temp) { return SDDF_TMU_ERR_EINVAL; } @@ -78,7 +80,7 @@ sddf_tmu_err_t degrees_to_quantised(sddf_temp_celsius_t val_degrees, sddf_temp_c * Returns: * sddf_tmu_err_t OK if fine, otherwise positive error value. */ -sddf_tmu_err_t degrees_to_quantised(uint64_t val_quantised, sddf_temp_celsius_t min_temp, +sddf_tmu_err_t quantised_to_degrees(uint64_t val_quantised, sddf_temp_celsius_t min_temp, sddf_temp_celsius_t max_temp, uint32_t quantisation, sddf_temp_celsius_t *degrees_celsius) { @@ -88,9 +90,9 @@ sddf_tmu_err_t degrees_to_quantised(uint64_t val_quantised, sddf_temp_celsius_t sddf_temp_celsius_t temp = ((sddf_temp_celsius_t)val_quantised * unit); // Sanity: if this temperature is outside of the valid temp range we have failed horribly. - assert(temp > min_temp && val_degrees <= max_temp); + assert(temp > min_temp && temp <= max_temp); // Finally: return. - *quantised_val = val_in_units; + *degrees_celsius = temp; return SDDF_TMU_ERR_OK; } diff --git a/include/sddf/tmu/protocol.h b/include/sddf/tmu/protocol.h index 7a64dda71..aadac88b7 100644 --- a/include/sddf/tmu/protocol.h +++ b/include/sddf/tmu/protocol.h @@ -23,7 +23,8 @@ typedef enum { typedef enum { SDDF_TMU_IRQ_MODE_DISABLED, SDDF_TMU_IRQ_MODE_INSTANTANEOUS, // IRQ on instant of threshold exceeding - SDDF_TMU_IRQ_MODE_AVG // IRQ when low-passed average exceeds theshold + SDDF_TMU_IRQ_MODE_AVG, // IRQ when low-passed average exceeds theshold + SDDF_TMU_IRQ_MODES_NUM } sddf_tmu_irq_modes_t; typedef enum { @@ -31,56 +32,39 @@ typedef enum { SDDF_TMU_ERR_UNPERMITTED, SDDF_TMU_ERR_FAILED, SDDF_TMU_ERR_EINVAL, + SDDF_TMU_ERR_BAD_PPC_CALL, SDDF_TMU_NUM_ERRORS } sddf_tmu_err_t; typedef struct tmu_temp_info { sddf_temp_celsius_t temp_inst; sddf_temp_celsius_t temp_avg; - bool valid; // Temp was outside of safe boundaries + bool valid_inst; // Temp was outside of safe boundaries + bool valid_avg; } sddf_tmu_temp_info_t; -// SDDF_TMU_SET_ENABLED -// Enable or disable the TMU. -// Args: -// MR0: 0 to disable, 1 to enable -// Returns: -// MR0: 0 on success, 1 on failure. -#define SDDF_TMU_SET_ENABLED_ENABLE (0) -#define SDDF_TMU_SET_ENABLED_SUCCESS (0) -#define SDDF_TMU_SET_ENABLED_FAIL (1) - // SDDF_TMU_SET_IRQ_MODE // Set the IRQ forwarding to disabled or active with a direct or low-pass average theshold. Forwarded interrupts are sent to // Args: // MR0: mode. 0 = disabled, 1 = instantaneous, 2 = average // Returns: -// MR0: 0 on success, 1 on failure. #define SDDF_TMU_SET_IRQ_MODE_MODE (0) -#define SDDF_TMU_SET_IRQ_MODE_SUCCESS (0) -#define SDDF_TMU_SET_IRQ_MODE_FAIL (1) // SDDF_TMU_SET_IRQ_THESHOLD // Set the bounds for IRQ delivery // Args: // MR0: high theshold in degrees celsius // Returns: -// MR0: 0 on success, 1 on failure. -#define SDDF_TMU_SET_IRQ_THRESHOLD_THRESHOLD (0) -#define SDDF_TMU_SET_IRQ_THRESHOLD_SUCCESS (0) -#define SDDF_TMU_SET_IRQ_THRESHOLD_FAIL (1) +#define SDDF_TMU_SET_IRQ_THESHOLD_THESH (0) // SDDF_TMU_GET_TEMP // Return temperature reading. // Args: (none) // Returns: -// MR0: 0 on success, 1 on failure. -// MR1: reading value. 0 if invalid, 1 if valid -// MR2: instantaneous temperature in celsius -// MR3: average temperature in celsius -#define SDDF_TMU_GET_TEMP_SUCCESS (0) -#define SDDF_TMU_GET_TEMP_FAIL (1) -#define SDDF_TMU_GET_TEMP_VALIDITY (1) -#define SDDF_TMU_GET_TEMP_INST (2) -#define SDDF_TMU_GET_TEMP_AVG (3) +// MR0: validity mask. bit 0 = inst valid, bit 1 = avg valid +// MR1: instantaneous temperature in celsius +// MR2: average temperature in celsius +#define SDDF_TMU_GET_TEMP_VALIDITY (0) +#define SDDF_TMU_GET_TEMP_INST (1) +#define SDDF_TMU_GET_TEMP_AVG (2) diff --git a/tools/make/board/maaxboard.mk b/tools/make/board/maaxboard.mk index 5c9372dfb..2e3687b55 100644 --- a/tools/make/board/maaxboard.mk +++ b/tools/make/board/maaxboard.mk @@ -13,5 +13,6 @@ NET_DRIV_DIR := ${PLATFORM} ETH_DRIV := eth_driver_${PLATFORM}.elf TIMER_DRIV_DIR := ${PLATFORM} UART_DRIV_DIR := ${PLATFORM} +TMU_DRIV_DIR := imx8mq CPU := cortex-a53 From 6f042cd4973d2cb6b4e8dec7d896c05b3f4359b5 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Fri, 3 Jul 2026 12:14:45 +1000 Subject: [PATCH 10/15] Make TMU driver use acacia, add proper config structs Signed-off-by: Lesley Rossouw --- acacia_sddf/__init__.py | 1 + acacia_sddf/board.py | 2 + acacia_sddf/tmu.py | 133 +++++++++++++++++++++++ drivers/tmu/imx8mq/imx8mq-tmu.h | 4 +- drivers/tmu/imx8mq/tmu.c | 183 +++++++++++++++++--------------- examples/tmu/client.c | 15 +-- examples/tmu/meta.py | 109 +++++++------------ examples/tmu/tmu.mk | 11 +- include/sddf/tmu/client.h | 1 - include/sddf/tmu/driver.h | 18 ++-- include/sddf/tmu/protocol.h | 1 - 11 files changed, 293 insertions(+), 185 deletions(-) create mode 100644 acacia_sddf/tmu.py diff --git a/acacia_sddf/__init__.py b/acacia_sddf/__init__.py index 0aee53165..42e475bdc 100644 --- a/acacia_sddf/__init__.py +++ b/acacia_sddf/__init__.py @@ -6,3 +6,4 @@ from .serial import sDDFSerial from .sddf import sDDFDriverClass, sDDFDriverConfig, sDDFDriverManifest from .board import BOARDS, Board +from .tmu import sDDFTMU diff --git a/acacia_sddf/board.py b/acacia_sddf/board.py index 3ae74958b..098a4e486 100644 --- a/acacia_sddf/board.py +++ b/acacia_sddf/board.py @@ -23,6 +23,7 @@ class Board: timer: Optional[DriverDouble] = DriverDouble(None, None) i2c: Optional[DriverDouble] = DriverDouble(None, None) blk: Optional[DriverDouble] = DriverDouble(None, None) + tmu: Optional[DriverDouble] = DriverDouble(None, None) partition: int = 0 baud_rate: Optional[int] = None @@ -94,6 +95,7 @@ class Board: timer=DriverDouble("fsl,imx8mq-gpt", "soc@0/bus@30000000/timer@302d0000"), ethernet=DriverDouble("", "soc@0/bus@30800000/ethernet@30be0000"), blk=DriverDouble("", "soc@0/bus@30800000/mmc@30b40000"), + tmu=DriverDouble("fsl,imx8mq-tmu", "soc@0/bus@30000000/tmu@30260000"), partition=2, ), Board( diff --git a/acacia_sddf/tmu.py b/acacia_sddf/tmu.py new file mode 100644 index 000000000..078e76459 --- /dev/null +++ b/acacia_sddf/tmu.py @@ -0,0 +1,133 @@ +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +from acacia import ( + System, + Subsystem, + ProtectionDomain, + Channel, + Map, + MemoryRegion, + DTBNode, + DeviceTreeBlob, + SchedulingProperties, + ConfigStruct, + IRQ, + SubsystemBuildError, +) +import sys, os +from .driver_manifest import sDDFDriverManifest, sDDFDriverConfig, DTSIRQ, DTSRegion +from .sddf import sDDFDriverClass, DeviceResourcesFactory, RegionResourceFactory +from collections import defaultdict +from typing import List, Dict, Type, Union, Optional + +TMU_PROTOCOL_MAGIC = "TMU" + chr(2) + + +class sDDFTMU(sDDFDriverClass): + def __init__( + self, + dev_compatible: str, + dev_dt_path: str, + sdf: System, + driver_prio: int, + cpu: Optional[int] = None, + driver_elf: str = "tmu_driver.elf", + ): + super().__init__("tmu", dev_compatible, dev_dt_path, sdf, magic="sDDF" + chr(1)) + self.driver = ProtectionDomain( + "tmu_driver", + driver_elf, + scheduling=SchedulingProperties(driver_prio, passive=True), + cpu=cpu, + ) + self.pds.append(self.driver) + self.cpu = cpu + + # Create driver resources before doing anything else + self.driver_dev_resources = self.create_dtb_resources(self.driver) + self.driver_config = None + self.client_configs = [] + self.irq_fwd_client = None # One client may receive forwarded IRQs + + def connect_clients(self): + # Clients are connected with: + # a. channel allowing PPCs -> driver, notifications -> clienet + # ... that's it! + fwd_channel = None + for c in self.clients: + if c.priority > self.driver.priority: + raise SubsystemBuildError( + f"Client {c} has higher priority than tmu driver!" + ) + do_fwd = c is self.irq_fwd_client + ch = Channel( + Channel.End(c, can_notify=do_fwd, can_pp=True), + Channel.End(self.driver, can_notify=True, can_pp=False), + ) + if do_fwd: + fwd_channel = ch + self.channels.append(ch) + self.client_configs.append( + self.tmu_client_config_factory(c, ch.id_for_pd(c)) + ) + # Make driver config + self.driver_config = self.tmu_driver_config_factory(fwd_channel) + + def add_client(self, client: ProtectionDomain, rcv_forwarded_irq=False): + if rcv_forwarded_irq: + if self.irq_fwd_client is not None: + raise RuntimeError("TMU only supports forwarding IRQs to one client!") + self.irq_fwd_client = client + if client not in self.clients: + self.clients.append(client) + + def generate_config_structs(self): + # We've already made our structs + return [self.driver_dev_resources, self.driver_config] + self.client_configs + + def tmu_driver_config_factory(self, irq_fwd_channel: Channel) -> ConfigStruct: + """ + Create driver config + """ + # invariant: this PD only is a client to tmu one time. + fields = {"magic": TMU_PROTOCOL_MAGIC} + if self.irq_fwd_client: + fields["irq_fwd_channel"] = irq_fwd_channel.ch_for_pd(self.driver) + fields["do_irq_fwd"] = True + else: + fields["irq_fwd_channel"] = 0 + fields["do_irq_fwd"] = False + return ConfigStruct( + "tmu_driver_config_t", + target_file=self.driver.prog_image, + section_name="tmu_driver_config", + fields=fields, + ) + + def tmu_client_config_factory( + self, client_pd: ProtectionDomain, driver_id: int + ) -> ConfigStruct: + """ + create tmu_client_config for client_pd + """ + # invariant: this PD only is a client to tmu one time. + fields = {"magic": TMU_PROTOCOL_MAGIC, "driver_id": driver_id} + return ConfigStruct( + "tmu_client_config_t", + target_file=client_pd.prog_image, + section_name="tmu_client_config", + fields=fields, + ) + + +# Driver configs +def add_driver_config(driver_name: str, config: sDDFDriverConfig): + sDDFDriverManifest().add_driver_config(sDDFTMU, driver_name, config) + + +# imx +add_driver_config( + "imx", + sDDFDriverConfig("fsl,imx8mq-tmu", [DTSRegion("regs", dt_idx=0)], [DTSIRQ(0)]), +) diff --git a/drivers/tmu/imx8mq/imx8mq-tmu.h b/drivers/tmu/imx8mq/imx8mq-tmu.h index de191b8b8..32c10cad7 100644 --- a/drivers/tmu/imx8mq/imx8mq-tmu.h +++ b/drivers/tmu/imx8mq/imx8mq-tmu.h @@ -3,6 +3,7 @@ * * SPDX-License-Identifier: BSD-2-Clause */ + #pragma once #include @@ -10,7 +11,7 @@ typedef struct imx8mq_tmu_regs { uint32_t tmr; /* 0x00 - TMU mode register */ uint32_t tsr; /* 0x04 - TMU status register */ uint32_t tmtmir; /* 0x08 - TMU monitor temperature measurement interval register */ - uint32_t reserved0[5]; /* 0x0C-0x1F - Reserved */ + uint32_t reserved0[5]; /* 0x0C-0x1F - Reserved */ uint32_t tier; /* 0x20 - TMU interrupt enable register */ uint32_t tidr; /* 0x24 - TMU interrupt detect register */ uint32_t tiscr; /* 0x28 - TMU interrupt site capture register */ @@ -125,4 +126,3 @@ typedef struct imx8mq_tmu_regs { // TRATSRn - TMU Report Average Temperature Site Registers #define TMU_TRATSR_V_BIT (BIT(31)) #define TMU_TRATSR_TEMP_MASK (0xFF) - diff --git a/drivers/tmu/imx8mq/tmu.c b/drivers/tmu/imx8mq/tmu.c index 6ad5b8639..c5a7ed915 100644 --- a/drivers/tmu/imx8mq/tmu.c +++ b/drivers/tmu/imx8mq/tmu.c @@ -9,62 +9,64 @@ // All other clients can only request the current temperature. // Note: this driver does NOT work on imx8m mini devices! -// TODO: add sdfgen support for device resources and driver config. // TODO: add connection to clock driver to allow setting TMTMIR based on real clock speed rather // than a compile-time guess. We cannot support adjusting the polling rate by clients without this. +// WARNING: IRQ forwarding is untested as the Maaxboard uses this IRQ channel for system management. #include #include +#include #include #include +#include #include "imx8mq-tmu.h" -// TODO: add sdfgen support for IRQ forward channel and critical temp -#define IRQ_CHANNEL (0) +__attribute__((__section__(".device_resources"))) device_resources_t device_resources; +__attribute__((__section__(".tmu_driver_config"))) tmu_driver_config_t driver_config; +#define IRQ_CHANNEL (device_resources.irqs[0].id) #define IRQ_FORWARD_CHANNEL (1) -#define CRITICAL_TEMP ((sddf_temp_celsius_t )85) // Warnings will be printed if this is exceeded +#define CRITICAL_TEMP ((uint32_t) 85) // Warnings will be printed if this is exceeded -// TODO: sdfgen support for passing in this resource -#define TMU_REG_BASE (0x30260000) volatile imx8mq_tmu_regs_t *regs; sddf_tmu_irq_modes_t current_irq_mode = SDDF_TMU_IRQ_MODE_DISABLED; sddf_temp_celsius_t current_irq_thresh = SENSOR_MAX_TEMP; -static inline void enable_tmu(void) { +static inline void enable_tmu(void) +{ // disable monitoring to reconfigure regs->tmr &= ~TMU_TMR_ME_BIT; // clear irqs regs->tidr |= (TMU_TIDR_ITTE_BIT | TMU_TIDR_ATTE_BIT | TMU_TIDR_ATCTE_BIT); - #ifdef SDDF_PMU_ENABLE_IRQ - // clear interrupt site capture register - regs->tiscr &= ~(TMU_TISCR_ASITE_MASK << TMU_TISCR_ASITE_OFFSET); - regs->tiscr &= ~(TMU_TISCR_ISITE_MASK << TMU_TISCR_ISITE_OFFSET); + if (driver_config.do_irq_fwd) { + // clear interrupt site capture register + regs->tiscr &= ~(TMU_TISCR_ASITE_MASK << TMU_TISCR_ASITE_OFFSET); + regs->tiscr &= ~(TMU_TISCR_ISITE_MASK << TMU_TISCR_ISITE_OFFSET); - // clear critical interrupt site capture register - regs->ticscr &= ~(TMU_TICSCR_CASITE_MASK << TMU_TICSCR_CASITE_OFFSET); + // clear critical interrupt site capture register + regs->ticscr &= ~(TMU_TICSCR_CASITE_MASK << TMU_TICSCR_CASITE_OFFSET); - // enable critical irq and suitable IRQ mode bits - regs->tier |= TMU_TIER_ATCTEIE_BIT; - if (current_irq_mode == SDDF_TMU_IRQ_MODE_INSTANTANEOUS) { - regs->tier |= TMU_TIER_ITTEIE_BIT; - } else if (current_irq_mode == SDDF_TMU_IRQ_MODE_AVG) { - regs->tier |= TMU_TIER_ATTEIE_BIT; - } + // enable critical irq and suitable IRQ mode bits + regs->tier |= TMU_TIER_ATCTEIE_BIT; + if (current_irq_mode == SDDF_TMU_IRQ_MODE_INSTANTANEOUS) { + regs->tier |= TMU_TIER_ITTEIE_BIT; + } else if (current_irq_mode == SDDF_TMU_IRQ_MODE_AVG) { + regs->tier |= TMU_TIER_ATTEIE_BIT; + } - // Set critical temp. We don't use this for the protocol, but it is a handy utility - // to print errors if the board is in the process of melting down. - // enable theshold monitoring + program critical temperature theshold - regs->tmhtactr |= ((TMU_TMHTACTR_EN_BIT) | (CRITICAL_TEMP & TMU_TMHTACTR_TEMP_MASK)); + // Set critical temp. We don't use this for the protocol, but it is a handy utility + // to print errors if the board is in the process of melting down. + // enable theshold monitoring + program critical temperature theshold + regs->tmhtactr |= ((TMU_TMHTACTR_EN_BIT) | (CRITICAL_TEMP & TMU_TMHTACTR_TEMP_MASK)); - // repeat for IRQ mode threshold (must happen after enabling IRQ) - if (current_irq_mode == SDDF_TMU_IRQ_MODE_INSTANTANEOUS) { - regs->tmhtitr |= (TMU_TMHTITR_EN_BIT | ((uint64_t)current_irq_thresh & TMU_TMHTITR_TEMP_MASK)); - } else if (current_irq_mode == SDDF_TMU_IRQ_MODE_AVG) { - regs->tmhtatr |= (TMU_TMHTATR_EN_BIT | ((uint64_t)current_irq_thresh & TMU_TMHTATR_TEMP_MASK)); + // repeat for IRQ mode threshold (must happen after enabling IRQ) + if (current_irq_mode == SDDF_TMU_IRQ_MODE_INSTANTANEOUS) { + regs->tmhtitr |= (TMU_TMHTITR_EN_BIT | ((uint64_t)current_irq_thresh & TMU_TMHTITR_TEMP_MASK)); + } else if (current_irq_mode == SDDF_TMU_IRQ_MODE_AVG) { + regs->tmhtatr |= (TMU_TMHTATR_EN_BIT | ((uint64_t)current_irq_thresh & TMU_TMHTATR_TEMP_MASK)); + } } - #endif // set low pass filter for average to 4 samples regs->tmr |= (0b10 & TMU_TMR_ALPF_MASK) << TMU_TMR_ALPF_OFFSET; // set a conservative polling rate that should be adequate for all input clock @@ -75,13 +77,14 @@ static inline void enable_tmu(void) { regs->tmr |= (TMU_TMR_ME_BIT | TMU_TMR_MSITE_ARM_BIT); } -static inline void get_temp(sddf_tmu_temp_info_t *info) { +static inline void get_temp(sddf_tmu_temp_info_t *info) +{ info->valid_inst = (regs->tritsr0 & TMU_TRITSR_V_BIT) != 0; info->valid_avg = (regs->tratsr0 & TMU_TRATSR_V_BIT) != 0; uint64_t inst = regs->tritsr0 & TMU_TRITSR_TEMP_MASK; uint64_t avg = regs->tratsr0 & TMU_TRATSR_TEMP_MASK; - info->temp_inst = (sddf_temp_celsius_t) inst; - info->temp_avg = (sddf_temp_celsius_t) avg; + info->temp_inst = (sddf_temp_celsius_t)inst; + info->temp_avg = (sddf_temp_celsius_t)avg; if (!info->valid_inst) { LOG_TMU_DRIVER_ERR("Invalid instantaneous reading detected\n"); } @@ -90,15 +93,16 @@ static inline void get_temp(sddf_tmu_temp_info_t *info) { } } -void init(void) { - // TODO: replace with device resource - regs = (volatile imx8mq_tmu_regs_t *)TMU_REG_BASE; +void init(void) +{ + assert(device_resources_check_magic(&device_resources)); + assert(tmu_config_check_magic(&driver_config)); + regs = (volatile imx8mq_tmu_regs_t *)device_resources.regions[0].region.vaddr; enable_tmu(); } void notified(microkit_channel ch) { - // Sanity: make sure we are still monitoring. If the monitoring interval is exceeded, // the device will stop silently. if (regs->tsr & TMU_TSR_MIE_BIT) { @@ -110,25 +114,31 @@ void notified(microkit_channel ch) LOG_TMU_DRIVER_ERR("IRQ forward channel should not notify driver!"); return; } else if (ch == IRQ_CHANNEL) { + bool irq_landed = false; // check IRQ source if (regs->tidr & TMU_TIDR_ITTE_BIT) { LOG_TMU_DRIVER("Instantaneous value threshold reached!\n"); if (current_irq_mode != SDDF_TMU_IRQ_MODE_INSTANTANEOUS) { LOG_TMU_DRIVER_ERR("Received spurious instantaneous threshold IRQ!\n"); + } else { + irq_landed = true; } - microkit_notify(IRQ_FORWARD_CHANNEL); } if (regs->tidr & TMU_TIDR_ATTE_BIT) { LOG_TMU_DRIVER("Average value threshold reached!\n"); if (current_irq_mode != SDDF_TMU_IRQ_MODE_AVG) { LOG_TMU_DRIVER_ERR("Received spurious average threshold IRQ!\n"); + } else { + irq_landed = true; } - microkit_notify(IRQ_FORWARD_CHANNEL); } if (regs->tidr & TMU_TIDR_ATCTE_BIT) { - LOG_TMU_DRIVER_ERR("WARNING: critical temperature of %f exceeded! System may be damaged!\n", - CRITICAL_TEMP); - } + LOG_TMU_DRIVER_ERR("WARNING: critical temperature of %f exceeded! System may be damaged!\n", CRITICAL_TEMP); + irq_landed = true; + } + if (driver_config.do_irq_fwd && irq_landed) { + microkit_notify(driver_config.irq_fwd_channel); + } } else { LOG_TMU_DRIVER_ERR("Unknown channel 0x%x!\n", ch); } @@ -136,12 +146,10 @@ void notified(microkit_channel ch) bool invalid_low = regs->tsr & TMU_TSR_ORL_BIT; bool invalid_high = regs->tsr & TMU_TSR_ORH_BIT; if (invalid_low) { - LOG_TMU_DRIVER_ERR("Avg monitoring detected temperature <= %f (out of range)!\n", - SENSOR_MIN_TEMP); + LOG_TMU_DRIVER_ERR("Avg monitoring detected temperature <= %f (out of range)!\n", SENSOR_MIN_TEMP); } if (invalid_high) { - LOG_TMU_DRIVER_ERR("Avg monitoring detected temperature >= %f (out of range)!\n", - SENSOR_MIN_TEMP); + LOG_TMU_DRIVER_ERR("Avg monitoring detected temperature >= %f (out of range)!\n", SENSOR_MIN_TEMP); } if (invalid_high && invalid_low) { LOG_TMU_DRIVER_ERR("Temperatures are out of high and low range! Something is wrong!\n"); @@ -166,53 +174,52 @@ microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) uint64_t ret_num = 1; sddf_tmu_temp_info_t temp; switch (microkit_msginfo_get_label(msginfo)) { - case SDDF_TMU_SET_IRQ_MODE: - if (ch != IRQ_FORWARD_CHANNEL) { - LOG_TMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_IRQ_MODE!\n", ch); - err = SDDF_TMU_ERR_UNPERMITTED; - break; - } - sddf_tmu_irq_modes_t new_mode = (sddf_tmu_irq_modes_t) - microkit_mr_get(SDDF_TMU_SET_IRQ_MODE_MODE); - - if (new_mode >= SDDF_TMU_IRQ_MODES_NUM) { - LOG_TMU_DRIVER_ERR("Invalid IRQ mode %d supplied!\n", new_mode); - err = SDDF_TMU_ERR_EINVAL; - } else { - current_irq_mode = new_mode; - enable_tmu(); - } + case SDDF_TMU_SET_IRQ_MODE: + if (ch != IRQ_FORWARD_CHANNEL) { + LOG_TMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_IRQ_MODE!\n", ch); + err = SDDF_TMU_ERR_UNPERMITTED; break; + } + sddf_tmu_irq_modes_t new_mode = (sddf_tmu_irq_modes_t)microkit_mr_get(SDDF_TMU_SET_IRQ_MODE_MODE); + + if (new_mode >= SDDF_TMU_IRQ_MODES_NUM) { + LOG_TMU_DRIVER_ERR("Invalid IRQ mode %d supplied!\n", new_mode); + err = SDDF_TMU_ERR_EINVAL; + } else { + current_irq_mode = new_mode; + enable_tmu(); + } + break; - case SDDF_TMU_SET_IRQ_THRESHOLD: - if (ch != IRQ_FORWARD_CHANNEL) { - LOG_TMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_IRQ_THRESHOLD!\n", ch); - err = SDDF_TMU_ERR_UNPERMITTED; - break; - } - sddf_temp_celsius_t new_temp = (sddf_temp_celsius_t) microkit_mr_get(SDDF_TMU_SET_IRQ_THESHOLD_THESH); - // Check theshold is valid - if (new_temp > SENSOR_MAX_TEMP || new_temp < SENSOR_MIN_TEMP) { - LOG_TMU_DRIVER_ERR("Invalid temperature %f, must be in range [%f, %f]\n", - new_temp, SENSOR_MIN_TEMP, SENSOR_MAX_TEMP); - err = SDDF_TMU_ERR_EINVAL; - } else { - current_irq_thresh = new_temp; - LOG_TMU_DRIVER("IRQ thesh set to %f\n", new_temp); - enable_tmu(); - } - break; - case SDDF_TMU_GET_TEMP: - get_temp(&temp); - microkit_mr_set(SDDF_TMU_GET_TEMP_VALIDITY, (temp.valid_inst | (temp.valid_avg << 1))); - microkit_mr_set(SDDF_TMU_GET_TEMP_INST, temp.temp_inst); - microkit_mr_set(SDDF_TMU_GET_TEMP_AVG, temp.temp_avg); - ret_num = 3; + case SDDF_TMU_SET_IRQ_THRESHOLD: + if (ch != IRQ_FORWARD_CHANNEL) { + LOG_TMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_IRQ_THRESHOLD!\n", ch); + err = SDDF_TMU_ERR_UNPERMITTED; break; + } + sddf_temp_celsius_t new_temp = (sddf_temp_celsius_t)microkit_mr_get(SDDF_TMU_SET_IRQ_THESHOLD_THESH); + // Check theshold is valid + if (new_temp > SENSOR_MAX_TEMP || new_temp < SENSOR_MIN_TEMP) { + LOG_TMU_DRIVER_ERR("Invalid temperature %f, must be in range [%f, %f]\n", new_temp, SENSOR_MIN_TEMP, + SENSOR_MAX_TEMP); + err = SDDF_TMU_ERR_EINVAL; + } else { + current_irq_thresh = new_temp; + LOG_TMU_DRIVER("IRQ thesh set to %f\n", new_temp); + enable_tmu(); + } + break; + case SDDF_TMU_GET_TEMP: + get_temp(&temp); + microkit_mr_set(SDDF_TMU_GET_TEMP_VALIDITY, (temp.valid_inst | (temp.valid_avg << 1))); + microkit_mr_set(SDDF_TMU_GET_TEMP_INST, temp.temp_inst); + microkit_mr_set(SDDF_TMU_GET_TEMP_AVG, temp.temp_avg); + ret_num = 3; + break; default: - LOG_TMU_DRIVER_ERR("Unknown request %lu to TMU driver from channel %u\n", - microkit_msginfo_get_label(msginfo), ch); + LOG_TMU_DRIVER_ERR("Unknown request %lu to TMU driver from channel %u\n", microkit_msginfo_get_label(msginfo), + ch); err = SDDF_TMU_ERR_BAD_PPC_CALL; } diff --git a/examples/tmu/client.c b/examples/tmu/client.c index 62870cb9b..72f915b9f 100644 --- a/examples/tmu/client.c +++ b/examples/tmu/client.c @@ -12,9 +12,11 @@ #include #include #include +#include __attribute__((__section__(".timer_client_config"))) timer_client_config_t timer_config; __attribute__((__section__(".serial_client_config"))) serial_client_config_t serial_config; +__attribute__((__section__(".tmu_client_config"))) tmu_client_config_t tmu_config; cothread_t t_event; cothread_t t_main; @@ -22,7 +24,7 @@ cothread_t t_main; static serial_queue_handle_t serial_tx_queue_handle; // TODO: sdfgen for tmu client channel -#define TMU_CHANNEL (0) +#define TMU_CHANNEL (tmu_config.driver_id) #define STACK_SIZE (4096) static char t_client_main_stack[STACK_SIZE]; @@ -69,16 +71,17 @@ void notified(sddf_channel ch) static uint64_t busywork_magic = 0; -void client_main(void) { +void client_main(void) +{ LOG_CLIENT("Entered main loop.\n"); int ret; - #ifdef SDDF_PMU_ENABLE_IRQ +#ifdef SDDF_TMU_ENABLE_IRQ // Set an average temperature IRQ forward @ 45 deg C int ret = sddf_tmu_set_irq_mode(TMU_CHANNEL, SDDF_TMU_IRQ_MODE_AVG); assert(!ret); ret = sddf_tmu_set_irq_threshold(TMU_CHANNEL, 45.0); assert(!ret); - #endif +#endif sddf_tmu_temp_info_t temp_info; for (;;) { @@ -93,7 +96,6 @@ void client_main(void) { LOG_CLIENT("\tInst. valid: %d\n", temp_info.valid_inst); LOG_CLIENT("\tInst. temp: %f\n", temp_info.temp_inst); } - // delay_ms(2000); // Busy wait to make heat for (uint64_t i = 0; i < 100000000; i++) { busywork_magic++; @@ -102,7 +104,7 @@ void client_main(void) { if (busywork_magic < 500) { busywork_magic = busywork_magic * 718; } else { - busywork_magic = busywork_magic - (300*busywork_magic); + busywork_magic = busywork_magic - (300 * busywork_magic); } } } @@ -116,6 +118,7 @@ void init(void) serial_putchar_init(serial_config.tx.id, &serial_tx_queue_handle); assert(timer_config_check_magic(&timer_config)); + assert(tmu_config_check_magic(&tmu_config)); sddf_printf("CLIENT|INFO: starting\n"); timer_channel = timer_config.driver_id; diff --git a/examples/tmu/meta.py b/examples/tmu/meta.py index 888c61baf..34fa87e02 100644 --- a/examples/tmu/meta.py +++ b/examples/tmu/meta.py @@ -4,83 +4,49 @@ import argparse from typing import List from dataclasses import dataclass -from sdfgen import SystemDescription, Sddf, DeviceTree -from importlib.metadata import version +from acacia import System, ProtectionDomain, MemoryRegion, Channel, DeviceTreeBlob, Map -sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../tools/meta") -) -from board import BOARDS +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) +from acacia_sddf import BOARDS, sDDFSerial, sDDFTimer, sDDFTMU -assert version("sdfgen").split(".")[1] == "28", "Unexpected sdfgen version" -ProtectionDomain = SystemDescription.ProtectionDomain -MemoryRegion = SystemDescription.MemoryRegion -Map = SystemDescription.Map -Channel = SystemDescription.Channel -IrqConventional = SystemDescription.IrqConventional - - -def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): - serial_driver = ProtectionDomain("serial_driver", "serial_driver.elf", priority=200) - # Increase the stack size as running with UBSAN uses more stack space than normal. - serial_virt_tx = ProtectionDomain( - "serial_virt_tx", "serial_virt_tx.elf", priority=199, stack_size=0x2000 - ) - - timer_driver = ProtectionDomain("timer_driver", "timer_driver.elf", priority=7) - tmu_driver = ProtectionDomain("tmu_driver", "tmu_driver.elf", priority=4) +def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): client = ProtectionDomain("client", "client.elf", priority=1) - - timer_node = dtb.node(board.timer) - assert timer_node is not None - serial_node = dtb.node(board.serial) - assert serial_node is not None - - timer_system = Sddf.Timer(sdf, timer_node, timer_driver) - timer_system.add_client(client) - - serial_system = Sddf.Serial( - sdf, serial_node, serial_driver, serial_virt_tx, enable_color=False - ) - serial_system.add_client(client) - + tmu = sDDFTMU(board.tmu.compatible, board.tmu.node_path, sdf, driver_prio=7) + tmu.add_client(client) + sdf.add_subsystem(tmu) # TODO: replace with sdfgen - if board.name == "maaxboard": - tmu_mr = MemoryRegion(sdf, "tmu_mr", 0x1000, paddr=0x30260000) - sdf.add_mr(tmu_mr) - - tmu_mr_map = Map(tmu_mr, 0x30260000, "rw", cached=False) - tmu_driver.add_map(tmu_mr_map) - tmu_driver.add_irq(IrqConventional(49 + 32, IrqConventional.Trigger.EDGE)) - - else: - print("Unsupported board!") - exit(-1) - + # if board.name == "maaxboard": + # tmu_mr = MemoryRegion(sdf, "tmu_mr", 0x1000, paddr=0x30260000) + # sdf.add_mr(tmu_mr) + # + # tmu_mr_map = Map(tmu_mr, 0x30260000, "rw", cached=False) + # tmu_driver.add_map(tmu_mr_map) + # tmu_driver.add_irq(IrqConventional(49 + 32, IrqConventional.Trigger.EDGE)) # Connect TMU client - # TODO: sdfgen for this - tmu_channel = Channel(tmu_driver, client, pp_b=True) - sdf.add_channel(tmu_channel) - pds = [ - serial_driver, - serial_virt_tx, - timer_driver, - tmu_driver, - client, - ] - for pd in pds: - sdf.add_pd(pd) - - assert serial_system.connect() - assert serial_system.serialise_config(output_dir) - assert timer_system.connect() - assert timer_system.serialise_config(output_dir) + timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) + timer.add_client(client) + sdf.add_subsystem(timer) + + serial = sDDFSerial( + board.serial.compatible, + board.serial.node_path, + sdf, + driver_prio=201, + virt_tx_prio=200, + allow_rx=False, + enable_color=False, + baud_rate=board.baud_rate if board.baud_rate else 115200, + ) + serial.add_client(client) + sdf.add_subsystem(serial) - with open(f"{output_dir}/{sdf_file}", "w+") as f: - f.write(sdf.render()) + out_file = f"{output_dir}/{sdf_file}" + sdf.make_config_structs() + print(f"Saving to {out_file}") + sdf.write_xml_file(out_file) if __name__ == "__main__": @@ -95,10 +61,7 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): board = next(filter(lambda b: b.name == args.board, BOARDS)) - sdf = SystemDescription(board.arch, board.paddr_top) - sddf = Sddf(args.sddf) - - with open(args.dtb, "rb") as f: - dtb = DeviceTree(f.read()) + dtb = DeviceTreeBlob(args.dtb) + sdf = System(board.arch, board.paddr_top, dtb) generate(args.sdf, args.output, dtb) diff --git a/examples/tmu/tmu.mk b/examples/tmu/tmu.mk index c5b3ae7b0..09814c7bf 100644 --- a/examples/tmu/tmu.mk +++ b/examples/tmu/tmu.mk @@ -71,11 +71,14 @@ client.elf: $(CLIENT_OBJS) libco.a libsddf_util.a $(SYSTEM_FILE): $(METAPROGRAM) $(IMAGES) $(DTB) $(PYTHON) $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) --dtb $(DTB) --output . --sdf $(SYSTEM_FILE) $(OBJCOPY) --update-section .device_resources=timer_driver_device_resources.data timer_driver.elf - $(OBJCOPY) --update-section .timer_client_config=timer_client_client.data client.elf $(OBJCOPY) --update-section .device_resources=serial_driver_device_resources.data serial_driver.elf - $(OBJCOPY) --update-section .serial_driver_config=serial_driver_config.data serial_driver.elf - $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx.data serial_virt_tx.elf - $(OBJCOPY) --update-section .serial_client_config=serial_client_client.data client.elf + $(OBJCOPY) --update-section .device_resources=tmu_driver_device_resources.data tmu_driver.elf + $(OBJCOPY) --update-section .serial_driver_config=serial_driver_serial_driver_config.data serial_driver.elf + $(OBJCOPY) --update-section .serial_virt_tx_config=serial_virt_tx_serial_virt_tx_config.data serial_virt_tx.elf + $(OBJCOPY) --update-section .serial_client_config=client_serial_client_config.data client.elf + $(OBJCOPY) --update-section .timer_client_config=client_timer_client_config.data client.elf + $(OBJCOPY) --update-section .tmu_client_config=client_tmu_client_config.data client.elf + $(OBJCOPY) --update-section .tmu_driver_config=tmu_driver_tmu_driver_config.data tmu_driver.elf touch $@ $(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) diff --git a/include/sddf/tmu/client.h b/include/sddf/tmu/client.h index 59df69d48..f1b2ff0db 100644 --- a/include/sddf/tmu/client.h +++ b/include/sddf/tmu/client.h @@ -65,4 +65,3 @@ static inline int sddf_tmu_get_temp(microkit_channel channel, sddf_tmu_temp_info return ret; } - diff --git a/include/sddf/tmu/driver.h b/include/sddf/tmu/driver.h index a2867a6e7..ae4602fd3 100644 --- a/include/sddf/tmu/driver.h +++ b/include/sddf/tmu/driver.h @@ -18,13 +18,12 @@ #define LOG_TMU_DRIVER_ERR(...) do{ sddf_dprintf("TMU DRIVER|ERROR: "); sddf_dprintf(__VA_ARGS__); }while(0) - // TODO: we should extract this quantisation logic to a library. This is really similar // to what is currently done in timers, i2c and more. The only difference here is that we // use float, but that just means we need a float and non-float variant. -static inline sddf_temp_celsius_t find_quantised_unit(sddf_temp_celsius_t min_temp, - sddf_temp_celsius_t max_temp, - uint32_t quantisation) { +static inline sddf_temp_celsius_t find_quantised_unit(sddf_temp_celsius_t min_temp, sddf_temp_celsius_t max_temp, + uint32_t quantisation) +{ assert(min_temp < max_temp); // calculate value of a unit in this quantisation @@ -35,7 +34,6 @@ static inline sddf_temp_celsius_t find_quantised_unit(sddf_temp_celsius_t min_te return unit; } - /** * Given a temperature in degrees, return a quantised value to put in a device register. * Args: @@ -49,8 +47,8 @@ static inline sddf_temp_celsius_t find_quantised_unit(sddf_temp_celsius_t min_te * sddf_tmu_err_t OK if fine, otherwise positive error value. */ sddf_tmu_err_t degrees_to_quantised(sddf_temp_celsius_t val_degrees, sddf_temp_celsius_t min_temp, - sddf_temp_celsius_t max_temp, uint32_t quantisation, - uint64_t *quantised_val) { + sddf_temp_celsius_t max_temp, uint32_t quantisation, uint64_t *quantised_val) +{ // Sanity: reject values that are invalid if (val_degrees < min_temp || val_degrees > max_temp) { return SDDF_TMU_ERR_EINVAL; @@ -80,9 +78,9 @@ sddf_tmu_err_t degrees_to_quantised(sddf_temp_celsius_t val_degrees, sddf_temp_c * Returns: * sddf_tmu_err_t OK if fine, otherwise positive error value. */ -sddf_tmu_err_t quantised_to_degrees(uint64_t val_quantised, sddf_temp_celsius_t min_temp, - sddf_temp_celsius_t max_temp, uint32_t quantisation, - sddf_temp_celsius_t *degrees_celsius) { +sddf_tmu_err_t quantised_to_degrees(uint64_t val_quantised, sddf_temp_celsius_t min_temp, sddf_temp_celsius_t max_temp, + uint32_t quantisation, sddf_temp_celsius_t *degrees_celsius) +{ sddf_temp_celsius_t unit = find_quantised_unit(min_temp, max_temp, quantisation); diff --git a/include/sddf/tmu/protocol.h b/include/sddf/tmu/protocol.h index aadac88b7..4a504fcac 100644 --- a/include/sddf/tmu/protocol.h +++ b/include/sddf/tmu/protocol.h @@ -67,4 +67,3 @@ typedef struct tmu_temp_info { #define SDDF_TMU_GET_TEMP_VALIDITY (0) #define SDDF_TMU_GET_TEMP_INST (1) #define SDDF_TMU_GET_TEMP_AVG (2) - From 0bd992cd48576345fb583be0d73786dfb05e7600 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Fri, 3 Jul 2026 12:27:49 +1000 Subject: [PATCH 11/15] Add TMU CI Signed-off-by: Lesley Rossouw --- acacia_sddf/__init__.py | 1 - acacia_sddf/board.py | 2 -- acacia_sddf/tmu.py | 4 ++-- ci/examples/tmu.py | 42 +++++++++++++++++++++++++++++++++++ ci/matrix.py | 10 +++++++++ examples/i2c/meta.py | 8 ------- examples/i2c_bus_scan/meta.py | 7 ------ examples/timer/meta.py | 1 + examples/tmu/meta.py | 15 +------------ 9 files changed, 56 insertions(+), 34 deletions(-) create mode 100755 ci/examples/tmu.py diff --git a/acacia_sddf/__init__.py b/acacia_sddf/__init__.py index 42e475bdc..0aee53165 100644 --- a/acacia_sddf/__init__.py +++ b/acacia_sddf/__init__.py @@ -6,4 +6,3 @@ from .serial import sDDFSerial from .sddf import sDDFDriverClass, sDDFDriverConfig, sDDFDriverManifest from .board import BOARDS, Board -from .tmu import sDDFTMU diff --git a/acacia_sddf/board.py b/acacia_sddf/board.py index 098a4e486..3ae74958b 100644 --- a/acacia_sddf/board.py +++ b/acacia_sddf/board.py @@ -23,7 +23,6 @@ class Board: timer: Optional[DriverDouble] = DriverDouble(None, None) i2c: Optional[DriverDouble] = DriverDouble(None, None) blk: Optional[DriverDouble] = DriverDouble(None, None) - tmu: Optional[DriverDouble] = DriverDouble(None, None) partition: int = 0 baud_rate: Optional[int] = None @@ -95,7 +94,6 @@ class Board: timer=DriverDouble("fsl,imx8mq-gpt", "soc@0/bus@30000000/timer@302d0000"), ethernet=DriverDouble("", "soc@0/bus@30800000/ethernet@30be0000"), blk=DriverDouble("", "soc@0/bus@30800000/mmc@30b40000"), - tmu=DriverDouble("fsl,imx8mq-tmu", "soc@0/bus@30000000/tmu@30260000"), partition=2, ), Board( diff --git a/acacia_sddf/tmu.py b/acacia_sddf/tmu.py index 078e76459..c6f56702b 100644 --- a/acacia_sddf/tmu.py +++ b/acacia_sddf/tmu.py @@ -38,10 +38,10 @@ def __init__( self.driver = ProtectionDomain( "tmu_driver", driver_elf, + self.sdf, scheduling=SchedulingProperties(driver_prio, passive=True), cpu=cpu, ) - self.pds.append(self.driver) self.cpu = cpu # Create driver resources before doing anything else @@ -64,10 +64,10 @@ def connect_clients(self): ch = Channel( Channel.End(c, can_notify=do_fwd, can_pp=True), Channel.End(self.driver, can_notify=True, can_pp=False), + self.sdf, ) if do_fwd: fwd_channel = ch - self.channels.append(ch) self.client_configs.append( self.tmu_client_config_factory(c, ch.id_for_pd(c)) ) diff --git a/ci/examples/tmu.py b/ci/examples/tmu.py new file mode 100755 index 000000000..498d43298 --- /dev/null +++ b/ci/examples/tmu.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +import asyncio +from pathlib import Path +import sys + +from ts_ci import ( + log, + wait_for_output, + TestFailureException, + HardwareBackend, +) + +sys.path.insert(1, Path(__file__).parents[2].as_posix()) +from ci import common, matrix + +DRIFT_THRESHOLD = 0.05 # 5 percent. +TIME_MEASURE_COUNT = 5 +TIME_LENGTH = 1000**3 # 1 second in nanoseconds + + +async def test(backend: HardwareBackend, test_config: common.TestConfig): + await wait_for_output(backend, b"Read successfully!\r\n") + + async with asyncio.timeout(5 + TIME_MEASURE_COUNT): + await wait_for_output(backend, b"Inst. valid: 1") + + +# export +TEST_CASES = matrix.generate_example_test_cases( + "tmu", + matrix.EXAMPLES["tmu"], + test_fn=test, + backend_fn=common.backend_fn, + no_output_timeout_s=matrix.NO_OUTPUT_DEFAULT_TIMEOUT_S, +) + + +if __name__ == "__main__": + common.run_tests(TEST_CASES) diff --git a/ci/matrix.py b/ci/matrix.py index 7bd443ca3..6e840be9d 100644 --- a/ci/matrix.py +++ b/ci/matrix.py @@ -174,6 +174,16 @@ def listify(s: str | Sequence[str]) -> Sequence[str]: {"board": "kria_k26"}, ], }, + "tmu": { + "configs": ["debug", "release"], + "build_systems": ["make"], + "boards": [ + "maaxboard", + ], + "tests_exclude": [ + {"config": "release"}, + ], + }, "vswitch": { "configs": ["debug", "release"], "build_systems": ["make"], diff --git a/examples/i2c/meta.py b/examples/i2c/meta.py index 9965e54e8..ebb1d654f 100644 --- a/examples/i2c/meta.py +++ b/examples/i2c/meta.py @@ -9,7 +9,6 @@ sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) from acacia_sddf import BOARDS, sDDFI2C, sDDFSerial, sDDFTimer -<<<<<<< HEAD def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): client_pn532 = ProtectionDomain(sdf, "client_pn532", "client_pn532.elf", priority=1) @@ -17,13 +16,6 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): sdf, "client_ds3231", "client_ds3231.elf", priority=1 ) -======= - -def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): - client_pn532 = ProtectionDomain("client_pn532", "client_pn532.elf", priority=1) - client_ds3231 = ProtectionDomain("client_ds3231", "client_ds3231.elf", priority=1) - ->>>>>>> 4eaca1d8 (Lint Python with Acacia additions) i2c = sDDFI2C( sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=199 ) diff --git a/examples/i2c_bus_scan/meta.py b/examples/i2c_bus_scan/meta.py index 966cdc86a..fc3231de6 100644 --- a/examples/i2c_bus_scan/meta.py +++ b/examples/i2c_bus_scan/meta.py @@ -10,17 +10,10 @@ from acacia_sddf import BOARDS, sDDFI2C, sDDFSerial, sDDFTimer -<<<<<<< HEAD def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): client_scan = ProtectionDomain(sdf, "client_scan", "client_scan.elf", priority=1) -======= - -def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): - client_scan = ProtectionDomain("client_scan", "client_scan.elf", priority=1) - ->>>>>>> 0cbc47d9 (Lint Python with Acacia additions) i2c = sDDFI2C( sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=199 ) diff --git a/examples/timer/meta.py b/examples/timer/meta.py index c7748b022..73880ac97 100644 --- a/examples/timer/meta.py +++ b/examples/timer/meta.py @@ -12,6 +12,7 @@ sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../..")) from acacia_sddf import BOARDS, sDDFTimer + def generate(sdf_file: str, output_dir: str): client = ProtectionDomain(sdf, "client", "client.elf", priority=1) diff --git a/examples/tmu/meta.py b/examples/tmu/meta.py index 34fa87e02..bbd7298dc 100644 --- a/examples/tmu/meta.py +++ b/examples/tmu/meta.py @@ -11,24 +11,12 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): - client = ProtectionDomain("client", "client.elf", priority=1) + client = ProtectionDomain("client", "client.elf", sdf, priority=1) tmu = sDDFTMU(board.tmu.compatible, board.tmu.node_path, sdf, driver_prio=7) tmu.add_client(client) - sdf.add_subsystem(tmu) - - # TODO: replace with sdfgen - # if board.name == "maaxboard": - # tmu_mr = MemoryRegion(sdf, "tmu_mr", 0x1000, paddr=0x30260000) - # sdf.add_mr(tmu_mr) - # - # tmu_mr_map = Map(tmu_mr, 0x30260000, "rw", cached=False) - # tmu_driver.add_map(tmu_mr_map) - # tmu_driver.add_irq(IrqConventional(49 + 32, IrqConventional.Trigger.EDGE)) - # Connect TMU client timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) timer.add_client(client) - sdf.add_subsystem(timer) serial = sDDFSerial( board.serial.compatible, @@ -41,7 +29,6 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): baud_rate=board.baud_rate if board.baud_rate else 115200, ) serial.add_client(client) - sdf.add_subsystem(serial) out_file = f"{output_dir}/{sdf_file}" sdf.make_config_structs() From 350753e2e2248c0baedde04b1cf4d8835e1726df Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Mon, 13 Jul 2026 10:19:23 +1000 Subject: [PATCH 12/15] Update tmu to use new sdf signature Signed-off-by: Lesley Rossouw --- acacia_sddf/__init__.py | 1 + acacia_sddf/board.py | 2 ++ acacia_sddf/tmu.py | 8 ++++---- examples/tmu/meta.py | 8 ++++---- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/acacia_sddf/__init__.py b/acacia_sddf/__init__.py index 0aee53165..42e475bdc 100644 --- a/acacia_sddf/__init__.py +++ b/acacia_sddf/__init__.py @@ -6,3 +6,4 @@ from .serial import sDDFSerial from .sddf import sDDFDriverClass, sDDFDriverConfig, sDDFDriverManifest from .board import BOARDS, Board +from .tmu import sDDFTMU diff --git a/acacia_sddf/board.py b/acacia_sddf/board.py index 3ae74958b..098a4e486 100644 --- a/acacia_sddf/board.py +++ b/acacia_sddf/board.py @@ -23,6 +23,7 @@ class Board: timer: Optional[DriverDouble] = DriverDouble(None, None) i2c: Optional[DriverDouble] = DriverDouble(None, None) blk: Optional[DriverDouble] = DriverDouble(None, None) + tmu: Optional[DriverDouble] = DriverDouble(None, None) partition: int = 0 baud_rate: Optional[int] = None @@ -94,6 +95,7 @@ class Board: timer=DriverDouble("fsl,imx8mq-gpt", "soc@0/bus@30000000/timer@302d0000"), ethernet=DriverDouble("", "soc@0/bus@30800000/ethernet@30be0000"), blk=DriverDouble("", "soc@0/bus@30800000/mmc@30b40000"), + tmu=DriverDouble("fsl,imx8mq-tmu", "soc@0/bus@30000000/tmu@30260000"), partition=2, ), Board( diff --git a/acacia_sddf/tmu.py b/acacia_sddf/tmu.py index c6f56702b..879498c5d 100644 --- a/acacia_sddf/tmu.py +++ b/acacia_sddf/tmu.py @@ -27,18 +27,18 @@ class sDDFTMU(sDDFDriverClass): def __init__( self, + sdf: System, dev_compatible: str, dev_dt_path: str, - sdf: System, driver_prio: int, cpu: Optional[int] = None, driver_elf: str = "tmu_driver.elf", ): - super().__init__("tmu", dev_compatible, dev_dt_path, sdf, magic="sDDF" + chr(1)) + super().__init__(sdf, "tmu", dev_compatible, dev_dt_path, magic="sDDF" + chr(1)) self.driver = ProtectionDomain( + self.sdf, "tmu_driver", driver_elf, - self.sdf, scheduling=SchedulingProperties(driver_prio, passive=True), cpu=cpu, ) @@ -62,9 +62,9 @@ def connect_clients(self): ) do_fwd = c is self.irq_fwd_client ch = Channel( + self.sdf, Channel.End(c, can_notify=do_fwd, can_pp=True), Channel.End(self.driver, can_notify=True, can_pp=False), - self.sdf, ) if do_fwd: fwd_channel = ch diff --git a/examples/tmu/meta.py b/examples/tmu/meta.py index bbd7298dc..16fb32872 100644 --- a/examples/tmu/meta.py +++ b/examples/tmu/meta.py @@ -11,17 +11,17 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): - client = ProtectionDomain("client", "client.elf", sdf, priority=1) - tmu = sDDFTMU(board.tmu.compatible, board.tmu.node_path, sdf, driver_prio=7) + client = ProtectionDomain(sdf, "client", "client.elf", priority=1) + tmu = sDDFTMU(sdf, board.tmu.compatible, board.tmu.node_path, driver_prio=7) tmu.add_client(client) - timer = sDDFTimer(board.timer.compatible, board.timer.node_path, sdf) + timer = sDDFTimer(sdf, board.timer.compatible, board.timer.node_path) timer.add_client(client) serial = sDDFSerial( + sdf, board.serial.compatible, board.serial.node_path, - sdf, driver_prio=201, virt_tx_prio=200, allow_rx=False, From e4e0ff5f5d43f2d3eef4fbe3d3c509054ed91d53 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Tue, 14 Jul 2026 11:23:24 +1000 Subject: [PATCH 13/15] Update TMU class to clarify controller client exists, not just an IRQ fwd channel. Fixed a few minor bugs Signed-off-by: Lesley Rossouw --- acacia_sddf/tmu.py | 21 ++++++++------- drivers/tmu/imx8mq/tmu.c | 9 +++---- drivers/tmu/imx8mq/tmu_driver.mk | 45 +++++++++++++++++--------------- include/sddf/tmu/client.h | 3 ++- include/sddf/tmu/config.h | 37 ++++++++++++++++++++++++++ include/sddf/tmu/protocol.h | 7 +---- 6 files changed, 79 insertions(+), 43 deletions(-) create mode 100644 include/sddf/tmu/config.h diff --git a/acacia_sddf/tmu.py b/acacia_sddf/tmu.py index 879498c5d..a2f1d2456 100644 --- a/acacia_sddf/tmu.py +++ b/acacia_sddf/tmu.py @@ -32,6 +32,7 @@ def __init__( dev_dt_path: str, driver_prio: int, cpu: Optional[int] = None, + allow_irq_fwd: bool = True, driver_elf: str = "tmu_driver.elf", ): super().__init__(sdf, "tmu", dev_compatible, dev_dt_path, magic="sDDF" + chr(1)) @@ -48,7 +49,7 @@ def __init__( self.driver_dev_resources = self.create_dtb_resources(self.driver) self.driver_config = None self.client_configs = [] - self.irq_fwd_client = None # One client may receive forwarded IRQs + self.controller_client = None # One client may receive forwarded IRQs def connect_clients(self): # Clients are connected with: @@ -60,7 +61,7 @@ def connect_clients(self): raise SubsystemBuildError( f"Client {c} has higher priority than tmu driver!" ) - do_fwd = c is self.irq_fwd_client + do_fwd = c is self.controller_client ch = Channel( self.sdf, Channel.End(c, can_notify=do_fwd, can_pp=True), @@ -74,11 +75,11 @@ def connect_clients(self): # Make driver config self.driver_config = self.tmu_driver_config_factory(fwd_channel) - def add_client(self, client: ProtectionDomain, rcv_forwarded_irq=False): - if rcv_forwarded_irq: - if self.irq_fwd_client is not None: + def add_client(self, client: ProtectionDomain, is_controller=False): + if is_controller: + if self.controller_client is not None: raise RuntimeError("TMU only supports forwarding IRQs to one client!") - self.irq_fwd_client = client + self.controller_client = client if client not in self.clients: self.clients.append(client) @@ -86,17 +87,17 @@ def generate_config_structs(self): # We've already made our structs return [self.driver_dev_resources, self.driver_config] + self.client_configs - def tmu_driver_config_factory(self, irq_fwd_channel: Channel) -> ConfigStruct: + def tmu_driver_config_factory(self, controller_channel: Channel) -> ConfigStruct: """ Create driver config """ # invariant: this PD only is a client to tmu one time. fields = {"magic": TMU_PROTOCOL_MAGIC} - if self.irq_fwd_client: - fields["irq_fwd_channel"] = irq_fwd_channel.ch_for_pd(self.driver) + if self.controller_client: + fields["controller_channel"] = controller_channel.ch_for_pd(self.driver) fields["do_irq_fwd"] = True else: - fields["irq_fwd_channel"] = 0 + fields["controller_channel"] = 0 fields["do_irq_fwd"] = False return ConfigStruct( "tmu_driver_config_t", diff --git a/drivers/tmu/imx8mq/tmu.c b/drivers/tmu/imx8mq/tmu.c index c5a7ed915..0d81294cc 100644 --- a/drivers/tmu/imx8mq/tmu.c +++ b/drivers/tmu/imx8mq/tmu.c @@ -24,7 +24,6 @@ __attribute__((__section__(".device_resources"))) device_resources_t device_resources; __attribute__((__section__(".tmu_driver_config"))) tmu_driver_config_t driver_config; #define IRQ_CHANNEL (device_resources.irqs[0].id) -#define IRQ_FORWARD_CHANNEL (1) #define CRITICAL_TEMP ((uint32_t) 85) // Warnings will be printed if this is exceeded volatile imx8mq_tmu_regs_t *regs; @@ -110,7 +109,7 @@ void notified(microkit_channel ch) assert(false); // If you get this, increase the interval setting in init() } - if (ch == IRQ_FORWARD_CHANNEL) { + if (ch == driver_config.controller_channel) { LOG_TMU_DRIVER_ERR("IRQ forward channel should not notify driver!"); return; } else if (ch == IRQ_CHANNEL) { @@ -137,7 +136,7 @@ void notified(microkit_channel ch) irq_landed = true; } if (driver_config.do_irq_fwd && irq_landed) { - microkit_notify(driver_config.irq_fwd_channel); + microkit_notify(driver_config.controller_channel); } } else { LOG_TMU_DRIVER_ERR("Unknown channel 0x%x!\n", ch); @@ -175,7 +174,7 @@ microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) sddf_tmu_temp_info_t temp; switch (microkit_msginfo_get_label(msginfo)) { case SDDF_TMU_SET_IRQ_MODE: - if (ch != IRQ_FORWARD_CHANNEL) { + if (ch != driver_config.controller_channel) { LOG_TMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_IRQ_MODE!\n", ch); err = SDDF_TMU_ERR_UNPERMITTED; break; @@ -192,7 +191,7 @@ microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) break; case SDDF_TMU_SET_IRQ_THRESHOLD: - if (ch != IRQ_FORWARD_CHANNEL) { + if (ch != driver_config.controller_channel) { LOG_TMU_DRIVER_ERR("Client %u is not permitted to TMU_SET_IRQ_THRESHOLD!\n", ch); err = SDDF_TMU_ERR_UNPERMITTED; break; diff --git a/drivers/tmu/imx8mq/tmu_driver.mk b/drivers/tmu/imx8mq/tmu_driver.mk index 60abdbe55..56f4c22bf 100644 --- a/drivers/tmu/imx8mq/tmu_driver.mk +++ b/drivers/tmu/imx8mq/tmu_driver.mk @@ -1,32 +1,35 @@ # -# Copyright 2026, UNSW +#Copyright 2026, UNSW # -# SPDX-License-Identifier: BSD-2-Clause +#SPDX - License - Identifier : BSD - 2 - Clause # -# Include this snippet in your project Makefile to build -# the Meson tmu driver +#Include this snippet in your project Makefile to build +#the Meson tmu driver # -# NOTES -# Generates tmu_driver.elf -# Requires floating point support -# Requires libsddf_util_debug.a in ${LIBS} +#NOTES +#Generates tmu_driver.elf +#Requires floating point support +#Requires libsddf_util_debug.a in ${ LIBS } -TMU_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +TMU_DRIVER_DIR : = $(dir $(lastword $(MAKEFILE_LIST))) -tmu_driver.elf: tmu/tmu_driver.o - $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + tmu_driver.elf + : tmu / tmu_driver.o $(LD) $(LDFLAGS) $ + ^ $(LIBS) + - o $ @ -tmu/tmu_driver.o: CFLAGS+=-I${TMU_DRIVER_DIR} -tmu/tmu_driver.o: ${TMU_DRIVER_DIR}/tmu.c |tmu $(SDDF_LIBC_INCLUDE) - ${CC} ${CFLAGS} -c -o $@ $< + tmu + / tmu_driver.o + : CFLAGS += -I$ { TMU_DRIVER_DIR } tmu / tmu_driver.o : $ { TMU_DRIVER_DIR } / tmu.c + | tmu $(SDDF_LIBC_INCLUDE) $ { CC } $ { CFLAGS } - c - o $ @$ < -tmu: - mkdir -p $@ + tmu : mkdir + - p $ @ -clean:: - rm -rf tmu + clean::rm + - rf tmu -clobber:: - rm -f tmu_driver.elf + clobber::rm + - f tmu_driver.elf --include tmu_driver.d + - include tmu_driver.d diff --git a/include/sddf/tmu/client.h b/include/sddf/tmu/client.h index f1b2ff0db..261fe7749 100644 --- a/include/sddf/tmu/client.h +++ b/include/sddf/tmu/client.h @@ -5,6 +5,7 @@ #pragma once +#include "protocol.h" #include #include #include @@ -32,7 +33,7 @@ static inline int sddf_tmu_set_irq_mode(microkit_channel channel, sddf_tmu_irq_m * @param threshold high temperature threshold in degrees celsius. * @return 0 on success, 1 on failure. */ -static inline int sddf_tmu_set_irq_threshold(microkit_channel channel, int64_t threshold) +static inline int sddf_tmu_set_irq_threshold(microkit_channel channel, sddf_temp_celsius_t threshold) { microkit_msginfo msginfo = microkit_msginfo_new(SDDF_TMU_SET_IRQ_THRESHOLD, 1); microkit_mr_set(0, threshold); diff --git a/include/sddf/tmu/config.h b/include/sddf/tmu/config.h new file mode 100644 index 000000000..ce36091a9 --- /dev/null +++ b/include/sddf/tmu/config.h @@ -0,0 +1,37 @@ +/* + * Copyright 2025, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#pragma once + +#include +#include +#include + +#define SDDF_TMU_MAX_CLIENTS 64 +#define SDDF_TMU_MAGIC_LEN 4 +static char SDDF_TMU_MAGIC[SDDF_TMU_MAGIC_LEN] = { 'T', 'M', 'U', 0x2 }; + +typedef struct tmu_client_config { + char magic[SDDF_TMU_MAGIC_LEN]; + uint8_t driver_id; +} tmu_client_config_t; + +typedef struct tmu_driver_config { + char magic[SDDF_TMU_MAGIC_LEN]; + uint8_t controller_channel; + bool do_irq_fwd; +} tmu_driver_config_t; + +static inline bool tmu_config_check_magic(void *config) +{ + char *magic = (char *)config; + for (int i = 0; i < SDDF_TMU_MAGIC_LEN; i++) { + if (magic[i] != SDDF_TMU_MAGIC[i]) { + return false; + } + } + + return true; +} diff --git a/include/sddf/tmu/protocol.h b/include/sddf/tmu/protocol.h index 4a504fcac..816e3ce6d 100644 --- a/include/sddf/tmu/protocol.h +++ b/include/sddf/tmu/protocol.h @@ -13,12 +13,7 @@ // ideally measuring the CPU or SoC temp. typedef double sddf_temp_celsius_t; -typedef enum { - SDDF_TMU_SET_ENABLED, - SDDF_TMU_SET_IRQ_MODE, - SDDF_TMU_SET_IRQ_THRESHOLD, - SDDF_TMU_GET_TEMP -} sddf_tmu_ppc_codes_t; +typedef enum { SDDF_TMU_SET_IRQ_MODE, SDDF_TMU_SET_IRQ_THRESHOLD, SDDF_TMU_GET_TEMP } sddf_tmu_ppc_codes_t; typedef enum { SDDF_TMU_IRQ_MODE_DISABLED, From 748a66e636cd70b4e2761b8f325616c9e7f87d58 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Tue, 14 Jul 2026 14:08:52 +1000 Subject: [PATCH 14/15] design doc: add Power/Thermal/Clock section Signed-off-by: Lesley Rossouw --- docs/design/design.tex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/design/design.tex b/docs/design/design.tex index eadbc14d7..8d8b19ff3 100644 --- a/docs/design/design.tex +++ b/docs/design/design.tex @@ -2643,12 +2643,18 @@ \subsection{PWM}\label{s:pwm} For the Pulse-Width-Modulation (PWM) class there is a single call that sets period and duty cycle. - \subsection{Status} These device-class specifications are \textbf{subject to change}; code implementing them has not yet been merged. + +\section{Power, thermal and clock devices}\label{s:sensors} + +This section describes drivers which are used for system management. All of +these drivers use a simple, low-throughput interface. + + \chapter{Hotplugging}\label{s:hotplugging} \section{Overview} From 837a4adcc2b0f62a11f28ce56cb11783035130fd Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Tue, 14 Jul 2026 14:18:35 +1000 Subject: [PATCH 15/15] Preliminary documentation for TMU class Signed-off-by: Lesley Rossouw --- docs/design/design.tex | 25 +++++++++++- docs/drivers.md | 5 +++ docs/tmu/tmu.md | 90 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 docs/tmu/tmu.md diff --git a/docs/design/design.tex b/docs/design/design.tex index 8d8b19ff3..d69bc61f7 100644 --- a/docs/design/design.tex +++ b/docs/design/design.tex @@ -2649,11 +2649,34 @@ \subsection{Status} implementing them has not yet been merged. -\section{Power, thermal and clock devices}\label{s:sensors} +\section{Power, thermal and clock devices}\label{s:powerthermalclk} This section describes drivers which are used for system management. All of these drivers use a simple, low-throughput interface. +\subsection{Thermal Monitoring Unit}\label{s:tmu} + +The Thermal Monitoring Unit (TMU) is a simple peripheral used to monitor the temperature of +an SoC. The sDDF have a simple device class for interacting with these peripherals. + +TMUs generally offer some number of temperature sensors and programmable interrupts +which fire when the sampled temperature meets some critical criteria. + +The TMU sDDF device class consists of a driver which is accessed by clients using a +\gls{ppc} interface. Up to one client may be designated a controller; i.e. it +is granted the power to configure and receive interrupts forwarded from the +TMU. + +The controller client has two \gls{ppc} call options: to set the current +interrupt mode (disabled, average level trigger, instantaneous trigger) and +to set the critical temperature. When the critical temperature is reached +(according to interrupt mode), the TMU driver will notify the controller client. + +All other clients have read-only access to the current temperature data. + +Clients can get temperature data with the \texttt{get\_temp()} \gls{ppc} interface. +This returns a structure listing the current instantaneous and average temperature +readings, alongside a validity bit for each as generated by hardware. \chapter{Hotplugging}\label{s:hotplugging} diff --git a/docs/drivers.md b/docs/drivers.md index 41aeb44f7..0d6fae7f8 100644 --- a/docs/drivers.md +++ b/docs/drivers.md @@ -87,3 +87,8 @@ Device Tree compatible strings/platforms it is known to work with. * `brcm,bcm2835-system-timer` * x86-64 TSC & HPET: * `tsc_hpet` + +## TMU (Thermal Monitoring Unit) + +* imx + * `fsl,imx8mq-tmu` diff --git a/docs/tmu/tmu.md b/docs/tmu/tmu.md new file mode 100644 index 000000000..e3863150e --- /dev/null +++ b/docs/tmu/tmu.md @@ -0,0 +1,90 @@ + + +# TMU sub-system + +The sDDF has support for thermal monitoring units: simple peripherals used to manage the +temperature of an SoC and generate interrupts when certain trip points are reached. + +The TMU sub-system consists of a driver only which is connected to clients with a channel. All +operations are client-initiated PPCs, except for a special IRQ forwarding function which +permits the driver to notify a certain client (e.g. a thermal controller client) when a +threshold interrupt is emitted. + +## Protocol + +```c +typedef enum { + SDDF_TMU_SET_IRQ_MODE, + SDDF_TMU_SET_IRQ_THRESHOLD, + SDDF_TMU_GET_TEMP +} sddf_tmu_ppc_codes_t; +``` + +There are three protected calls that clients may make, two of which are reserved for a "controller" +client. + +## Controller client + +The TMU class may designate up to one client as a controller, granting it privileges to set +the temperature threshold IRQ, change IRQ modes and have IRQs forwarded to it as notifications. + +**IMPORTANT**: we cannot use these interrupts on the `imx` boards without also reprogramming the +system management Cortex-M processor on board - by default, the system management chip will shut +off the board if an IRQ is received. + +The controller client is specified using Acacia: set `is_controller` to `True` when adding the +client. + +### Interrupt forwarding + +IRQ forwarding is will be automatically enabled if there is a controller client, unless disabled +when instantiating the TMU subsystem in Acacia. The client will receive +a notification from the driver when IRQs of any type are triggered. + +IRQ forwarding can be disabled by setting `sDDFTMU.__init__()`'s `allow_irq_fwd` kwarg to `False`. +This will not affect the other functionality afforded to the controller. + +### Client PPCs + +(see `include/sddf/tmu/protocol.h`) + +#### SDDF_TMU_SET_IRQ_MODE + +Set the interrupt mode to one of the following: +```c +typedef enum { + SDDF_TMU_IRQ_MODE_DISABLED, + SDDF_TMU_IRQ_MODE_INSTANTANEOUS, // IRQ on instant of threshold exceeding + SDDF_TMU_IRQ_MODE_AVG, // IRQ when low-passed average exceeds theshold + SDDF_TMU_IRQ_MODES_NUM +} sddf_tmu_irq_modes_t; +``` + +Using `client.h`, call: +```c +int sddf_tmu_set_irq_mode(microkit_channel channel, sddf_tmu_irq_modes_t mode) +``` + +#### SDDF_TMU_SET_IRQ_THESHOLD + +Program the configurable interrupt to trip at a certain temperature in degrees celsius +(`sddf_temp_celsius_t`). + +Using `client.h`, call: +```c +int sddf_tmu_set_irq_threshold(microkit_channel channel, sddf_temp_celsius_t threshold) +``` + +#### SDDF_TMU_GET_TEMP + +Get the current temperature information. Returns a `sddf_tmu_temp_info_t` containing the +current instantaneous and average temperatures + validity markers for each. + +Temperatures and validity markers are identical to what is emitted by hardware. +Using `client.h`, call: +```c +int sddf_tmu_get_temp(microkit_channel channel, sddf_tmu_temp_info_t *info) +```