From 959f432b0c805c6de62fc09e9ad6ad7362a6ee03 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Thu, 25 Jun 2026 16:14:53 +1000 Subject: [PATCH 01/23] (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/23] 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/23] 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/23] 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/23] 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 55b8d7bc8667f3e3e58b201b571a15c37f57ce90 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Tue, 14 Jul 2026 14:07:45 +1000 Subject: [PATCH 06/23] Add I2CAddress type and I2C address decoding for I2C class Signed-off-by: Lesley Rossouw --- acacia_sddf/i2c.py | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/acacia_sddf/i2c.py b/acacia_sddf/i2c.py index ad9d949de..52572372f 100644 --- a/acacia_sddf/i2c.py +++ b/acacia_sddf/i2c.py @@ -14,16 +14,31 @@ ConfigStruct, ) import sys, os -from .driver_manifest import sDDFDriverManifest, sDDFDriverConfig, DTSIRQ, DTSRegion -from .sddf import sDDFDriverClass, DeviceResourcesFactory, RegionResourceFactory from collections import defaultdict +from dataclasses import dataclass from typing import List, Dict, Type, Union, Optional +from .driver_manifest import sDDFDriverManifest, sDDFDriverConfig, DTSIRQ, DTSRegion +from .sddf import sDDFDriverClass, DeviceResourcesFactory, RegionResourceFactory I2C_DATA_SZ = 0x1000 I2C_NUM_BUFS = 128 # TODO: add support for dynamically sized queues I2C_PROTOCOL_MAGIC = "sDDF" + chr(0x4) +@dataclass +class I2CAddress: + addr: int + + def __post_init__(self): + if self.addr & (1 << 31): + # This bit is set in DTS I2C addresses to mark 10 bit addresses. + # If found, we strip it out and store a bool. + self.is_ten_bit = True + self.addr &= ~(1 << 31) + else: + self.is_ten_bit = False + + class sDDFI2C(sDDFDriverClass): def __init__( self, @@ -218,6 +233,28 @@ def generate_config_structs(self): virt_resources = [self.virt_config] return driver_resources + virt_resources + self.client_configs + # ### dtb utility functions for drivers that depend on i2c ### + def get_i2c_addresses_from_dtb(self, device_node: DTBNode) -> List[I2CAddress]: + """ + Try and get the i2c addresses of a device on the bus controlled by this driver. + + If the path given is not for this I2C bus or the device is not present, a + ValueError will be raised. + Args: + device_node: DTBNode - peripheral node + Returns: + List[I2CAddress] + """ + # first: check that this path belongs to us + if self.dtb_node.path not in device_node.path: + raise ValueError( + f"{device_node} doesn't belong to this I2C bus ({self.dtb_node.path})!" + ) + + # reg property contains addresses. + reg = [x[0] for x in self.dtb.get_node_regs(device_node)] + return [I2CAddress(int(x)) for x in reg] + # ### connection config struct factory functions ### def i2c_connection_resource_factory( self, req_q: Map, resp_q: Map, num_bufs: int, id: int From 0659674de2303eddb72b9cb6427448ad0ec8ba48 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Fri, 3 Jul 2026 10:02:46 +1000 Subject: [PATCH 07/23] Add IMX i2c driver Co-authored-by: mt-fns Signed-off-by: Lesley Rossouw --- acacia_sddf/board.py | 1 + drivers/i2c/imx/config.json | 21 ++ drivers/i2c/imx/driver.h | 42 ++++ drivers/i2c/imx/i2c.c | 404 ++++++++++++++++++++++++++++++++++ drivers/i2c/imx/i2c_driver.mk | 35 +++ examples/i2c/i2c.mk | 4 +- 6 files changed, 506 insertions(+), 1 deletion(-) create mode 100644 drivers/i2c/imx/config.json create mode 100644 drivers/i2c/imx/driver.h create mode 100644 drivers/i2c/imx/i2c.c create mode 100644 drivers/i2c/imx/i2c_driver.mk diff --git a/acacia_sddf/board.py b/acacia_sddf/board.py index 3ae74958b..6eba5ceb7 100644 --- a/acacia_sddf/board.py +++ b/acacia_sddf/board.py @@ -94,6 +94,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"), + i2c=DriverDouble("","soc@0/bus@30800000/i2c@30a50000") partition=2, ), Board( diff --git a/drivers/i2c/imx/config.json b/drivers/i2c/imx/config.json new file mode 100644 index 000000000..cb4f54493 --- /dev/null +++ b/drivers/i2c/imx/config.json @@ -0,0 +1,21 @@ +{ + "compatible": [ + "fsl,imx8mq-i2c", + "fsl,imx21-i2c" + ], + "resources": { + "regions": [ + { + "name": "regs", + "perms": "rw", + "size": 4096, + "dt_index": 0 + } + ], + "irqs": [ + { + "dt_index": 0 + } + ] + } +} diff --git a/drivers/i2c/imx/driver.h b/drivers/i2c/imx/driver.h new file mode 100644 index 000000000..e0811c928 --- /dev/null +++ b/drivers/i2c/imx/driver.h @@ -0,0 +1,42 @@ +/* + * Copyright 2026, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +// Base: 0x30A20000 for I2C1 +struct imx_i2c_regs { + uint16_t iadr; // 0x00 - I2C Address Register + uint16_t _pad0; + uint16_t ifdr; // 0x04 - I2C Frequency Divider Register + uint16_t _pad1; + uint16_t i2cr; // 0x08 - I2C Control Register + uint16_t _pad2; + uint16_t i2sr; // 0x0C - I2C Status Register + uint16_t _pad3; + uint16_t i2dr; // 0x10 - I2C Data I/O Register + uint16_t _pad4; +}; + +// I2CR Control Register bits +#define REG_CR_IEN (1 << 7) // I2C Enable +#define REG_CR_IIEN (1 << 6) // I2C Interrupt Enable +#define REG_CR_MSTA (1 << 5) // Master/Slave mode (1=Master, generates START when 0->1) +#define REG_CR_MTX (1 << 4) // Transmit/Receive (1=Transmit, 0=Receive) +#define REG_CR_TXAK (1 << 3) // Transmit Acknowledge (1=No ACK sent) +#define REG_CR_RSTA (1 << 2) // Repeat Start (1=Generate repeated START) + +// I2SR Status Register bits +#define REG_SR_ICF (1 << 7) // Transfer Complete (set at 9th clock falling edge) +#define REG_SR_IAAS (1 << 6) // Addressed As Slave +#define REG_SR_IBB (1 << 5) // Bus Busy (1=Busy, set by START, cleared by STOP) +#define REG_SR_IAL (1 << 4) // Arbitration Lost +#define REG_SR_SRW (1 << 2) // Slave Read/Write (1=Slave transmit) +#define REG_SR_IIF (1 << 1) // I2C Interrupt (set when byte transfer complete) +#define REG_SR_RXAK (1 << 0) // Received Acknowledge (1=No ACK received, 0=ACK received) + diff --git a/drivers/i2c/imx/i2c.c b/drivers/i2c/imx/i2c.c new file mode 100644 index 000000000..5581c3b39 --- /dev/null +++ b/drivers/i2c/imx/i2c.c @@ -0,0 +1,404 @@ +/* + * Copyright 2026, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include "driver.h" + + +__attribute__((__section__(".i2c_driver_config"))) i2c_driver_config_t config; +__attribute__((__section__(".device_resources"))) device_resources_t device_resources; + +// NOTE: i2c registers on the imx8mq are 16 bit! + +// Base addresses: I2C1=0x30A20000, I2C2=0x30A30000, I2C3=0x30A40000, I2C4=0x30A50000 +volatile struct imx_i2c_regs *regs; + +i2c_driver_data_t driver_data; +fsm_data_t fsm_data = { 0 }; +i2c_queue_handle_t queue_handle; + +bool dummy_read = false; + +i2c_state_func_t *i2c_state_table[NUM_STATES] = { + state_idle, state_req, state_sel_cmd, state_cmd, state_cmd_ret, state_resp +}; + +/** + * Initialise the i2c master interface for 400KHz fast mode. + */ +static inline void i2c_setup(void) +{ + LOG_I2C_DRIVER("initialising i2c master interface...\n"); + + // disable initially + regs->i2cr = 0; + LOG_I2C_DRIVER("a\n"); + + // clear interrupt flag + regs->i2sr &= ~REG_SR_IIF; + LOG_I2C_DRIVER("b\n"); + + // Set frequency divider for 400 KHz (IC=0x06, divider=60 for 24MHz clock) + regs->ifdr = 0x06; + LOG_I2C_DRIVER("c\n"); + + // enable, and turn on interrupts + regs->i2cr = REG_CR_IEN | REG_CR_IIEN; + LOG_I2C_DRIVER("done\n"); +} + +/** + * Aborts the current operation by generating a STOP condition and disabling master mode. + */ +int i2c_halt(void) +{ + LOG_I2C_DRIVER("I2C HALT\n"); + + // Clear MSTA bit to generate STOP + regs->i2cr &= ~(REG_CR_MSTA | REG_CR_MTX); + + // busy wait for ibb to clear ... + int timeout = 10000; + while ((regs->i2sr & REG_SR_IBB) && timeout-- > 0) {} + + if (timeout <= 0) { + LOG_I2C_DRIVER_ERR("failed to halt - bus busy timeout\n"); + return -1; + } + + // Reset to idle state (keep IEN and IIEN enabled) + regs->i2cr = REG_CR_IEN | REG_CR_IIEN; + + return 0; +} + +void init(void) +{ + assert(i2c_config_check_magic(&config)); + assert(device_resources_check_magic(&device_resources)); + assert(device_resources.num_irqs == 1 || device_resources.num_irqs == 2); + assert(device_resources.num_regions == 1); + + regs = (volatile struct imx_i2c_regs *)device_resources.regions[0].region.vaddr; + i2c_setup(); + + i2c_reset_state(&driver_data); + queue_handle = i2c_queue_init(config.virt.req_queue.vaddr, config.virt.resp_queue.vaddr); + + LOG_I2C_DRIVER("i.MX8M I2C driver initialised.\n"); +} + +/** + * Send a start condition. This method should short circuit other writes to the CR register. + */ +static inline void imx_i2c_start(bool repeat) { + // Update dummy read flag upon switch to MTX + dummy_read = false; + if (!repeat) { + regs->i2cr |= REG_CR_MSTA; + // wait until bus is clear + for (uint32_t i = 0; (i < 10000) && (regs->i2sr & REG_SR_IBB); i++) { + } + + // if our short busy wait failed, die + if (regs->i2sr & REG_SR_IBB) { + LOG_I2C_DRIVER_ERR("Failed to claim bus on start! Is there another master?\n"); + assert(0); + } + + // write other start flags + regs->i2cr |= REG_CR_MTX | REG_CR_IIEN; + } else { + regs->i2cr |= REG_CR_RSTA; + } + + // Always enable TXAK at start of a transaction. + regs->i2cr &= ~REG_CR_TXAK; +} + +/** + * After sending an address+start OR a data byte, call this function + * to check if an acknowledgement is received. + * + * This is required as the hardware will only generate an interrupt on + * the successful completion of a transfer. + */ +static inline bool imx_i2c_ackd(void) { + // TODO: figure out if a busy wait is required + return ((regs->i2sr & REG_SR_RXAK) == 0); +} + +/** + * S_CMD (command) + * Initiate a single bus operation (START, data byte, or STOP) then yield to await IRQ completion. + * The imx8mq I2C controller operates byte-by-byte, not as a list processor. + */ +void state_cmd(fsm_data_t *fsm, i2c_driver_data_t *data, i2c_queue_handle_t *queue_handle) +{ + LOG_I2C_DRIVER("S_CMD: rw_idx=%u, len=%u, await_start=%u, await_stop=%u\n", + data->rw_idx, data->active_cmd.data_len, data->await_start, data->await_stop); + + // Sanity: if IAL (arbitration lost) is ever asserted, die. This should never happen for now, + // as our protocol currently doesn't support arbitration. + if (regs->i2sr & REG_SR_IAL) { + LOG_I2C_DRIVER_ERR("Arbitration lost! This should never happen as we do not support multi master!\n"); + LOG_I2C_DRIVER_ERR("Dying now.\n"); + // clear IAL + regs->i2sr &= ~REG_SR_IAL; + data->err = I2C_ERR_OTHER; + fsm->next_state = S_RESPONSE; + return; + } + + // invariant: these tmp registers are unused if we imx_i2c_start() as the start + // op needs to directly touch the registers. + uint32_t i2cr_tmp = regs->i2cr; + uint32_t i2dr_tmp = regs->i2dr; + bool do_i2dr_write = false; + bool do_i2cr_write = false; + if (data->await_start) { + LOG_I2C_DRIVER("Sending start condition...\n"); + // This is a repeated start if await_addr is not set. + bool is_repeat = (data->await_addr == 0); + + // Sanity: if a write-read is pending, this repeated start is invalid. + // The virt should handle this, but on the imx this condition is completely + // unhandleable so we check again. + assert(!(is_repeat && data->await_wrrd)); + + imx_i2c_start(is_repeat); + data->await_start = false; + fsm->yield = false; + // If we're here, i2c_start has already waited until ibb=0. + // Return to re-enter this state and check if arbitration loss occured. + return; + } + + // Addressing stage + if (data->await_wrrd) { + // first: handle sending initial address. + // invariant: start condition already set + LOG_I2C_DRIVER("Selected WRRD\n"); + + if (data->await_wrrd == WRRD_WRADDR) { + // Write address to data register. read bit = 0 + i2dr_tmp = i2c_curr_addr(data) << 1; + do_i2dr_write = true; + // invariant: mtx already set from await_start + } else if (data->await_wrrd == WRRD_SUBADDR) { + // Write sub address byte + uint8_t payload_byte = data->active_cmd.payload.data[0]; + i2dr_tmp = payload_byte; + do_i2dr_write = true; + LOG_I2C_DRIVER("WRRD: sending address byte %u\n", payload_byte); + } else { + LOG_I2C_DRIVER("WRRD: sending repeat start and looping\n"); + // Send a new start condition and leave await_addr to set up the read + // for us. + imx_i2c_start(true); + LOG_I2C_DRIVER("WRRD: ... done!\n"); + fsm->yield = false; + data->await_wrrd = 0; + // Re-enter state to check for arb. loss and continue. + return; + } + data->await_wrrd--; + + } else if (data->await_addr) { + LOG_I2C_DRIVER("Primary addressing stage...\n"); + // Calculate address byte with R/W bit + uint8_t addr_byte = (i2c_curr_addr(data) << 1); + bool is_read = cmd_is_read(data->active_cmd); + + if (is_read) { + addr_byte |= 0x1; // Read operation (R/W bit = 1) + } else { + addr_byte &= ~0x1; // Write operation (R/W bit = 0) + } + + LOG_I2C_DRIVER("Sending ADDR 0x%02x (read=%u)\n", i2c_curr_addr(data), is_read); + + // Write address to data register - this initiates the transfer + i2dr_tmp = addr_byte; + do_i2dr_write = true; + data->await_addr = false; + + // Data transmission and end + } else { + // read case + if (cmd_is_read(data->active_cmd)) { + // For the first data byte of a read, we need to switch to receive mode + // and do a dummy read to initiate the first byte reception + if (dummy_read == false) { + // Clear MTX to enter receive mode + i2cr_tmp &= ~REG_CR_MTX; + do_i2cr_write = true; + + // If this is a single byte read, prepare NACK for it + if (data->active_cmd.data_len == 1) { + i2cr_tmp |= REG_CR_TXAK; // No ACK for single byte + } + + // Dummy read to initiate first byte reception + // The byte we read here is garbage/invalid for the first read. + // Store dummy byte in this buffer to avoid optimising out... + // TODO: check for fencepost error here. + data->active_cmd.payload.data[0] = (uint8_t) regs->i2dr; + LOG_I2C_DRIVER("MTX->RX dummy read completed...\n"); + dummy_read = true; + } + + // We must send a stop just before reading the very last byte + if (data->rw_idx >= data->active_cmd.data_len - 1) { + if (data->await_stop) { + LOG_I2C_DRIVER("Generating STOP (read)\n"); + // Clear MSTA and MTX to generate STOP condition + // Use real register here as this must happen strictly + // BEFORE doing the read. + do_i2cr_write = true; + i2cr_tmp &= ~REG_CR_MSTA; + i2cr_tmp &= ~REG_CR_MTX; + data->await_stop = false; + } else { + LOG_I2C_DRIVER("Command complete, skipping back to S_SEL_CMD\n"); + // Command complete without STOP (repeated start follows) + fsm->next_state = S_SEL_CMD; + return; + } + } + + // Set up NACK for last byte when reading second-to-last byte + if (data->rw_idx == data->active_cmd.data_len - 2) { + // This is the second-to-last byte, set TXAK for NACK on last byte + i2cr_tmp |= REG_CR_TXAK; + do_i2cr_write = true; + } + + // Real read: simply store result directly. We are effectively saving + // the result of the previous read data kick. + data->active_cmd.payload.data[data->bytes_read] = (uint8_t) regs->i2dr; + LOG_I2C_DRIVER("Reading byte 0x%02x at idx %u\n", + data->active_cmd.payload.data[data->bytes_read], data->rw_idx); + data->bytes_read++; + data->rw_idx++; + + } else { + // --- WRITE OPERATION --- + uint8_t write_byte; + write_byte = data->active_cmd.payload.data[data->rw_idx]; + + // Generate stop just after sending last byte. + if (data->rw_idx >= data->active_cmd.data_len-1) { + if (data->await_stop) { + LOG_I2C_DRIVER("Generating STOP (read)\n"); + // Clear MSTA and MTX to generate STOP condition + i2cr_tmp &= ~REG_CR_MSTA; + i2cr_tmp &= ~REG_CR_MTX; + do_i2cr_write = true; + data->await_stop = false; + } else { + // TODO: check if this is needed, we probably can't get here. + LOG_I2C_DRIVER("Command complete, skipping back to S_SEL_CMD\n"); + // Command complete without STOP (repeated start follows) + fsm->next_state = S_SEL_CMD; + return; + } + } + + LOG_I2C_DRIVER("Writing data byte 0x%02x at idx %u\n", write_byte, data->rw_idx); + regs->i2dr = write_byte; + do_i2dr_write = true; + data->rw_idx++; + } + } + + // Only write if required. Writing to the DR in read mode is UB, and unneeded writes to CR + // may have side effects. + // Important: write DR FIRST. + if (do_i2dr_write) { + regs->i2dr = i2dr_tmp; + } + if (do_i2cr_write) { + regs->i2cr = i2cr_tmp; + } + // We should always sleep and go to CMD_RET unless we decided to return earlier to a + // different state. + fsm->next_state = S_CMD_RET; + fsm->yield = true; + return; +} + +/** + * S_CMD_RET + * Handle completion interrupt from hardware. Check status, read data if applicable, + * and determine next state (continue command, select next command, or error). + */ +void state_cmd_ret(fsm_data_t *fsm, i2c_driver_data_t *data, i2c_queue_handle_t *queue_handle) +{ + // Clear interrupt + regs->i2sr &= ~REG_SR_IIF; + uint16_t status = regs->i2sr; + + LOG_I2C_DRIVER("S_CMD_RET: status=0x%04x\n", status); + + // check arbitration, just in case. + if (status & REG_SR_IAL) { + LOG_I2C_DRIVER_ERR("Arbitration lost!\n"); + data->err = I2C_ERR_OTHER; + // clear IAL + regs->i2sr &= ~REG_SR_IAL; + fsm->next_state = S_RESPONSE; + return; + } + + // Check for NACK (RXAK=1 means no ACK received) + // This applies to address phase and write phases + if (status & REG_SR_RXAK) { + // Don't print this as an error, not useful usually. + LOG_I2C_DRIVER("NACK!\n"); + data->err = I2C_ERR_NACK; + + // Generate STOP and return error + regs->i2cr &= ~(REG_CR_MSTA | REG_CR_MTX); + fsm->next_state = S_RESPONSE; + return; + } + + // Check if command is complete + if (data->rw_idx >= data->active_cmd.data_len - 1) { + // We just sent the STOP condition in state_cmd, wait for completion + // IIF will be set when STOP completes + LOG_I2C_DRIVER("STOP complete\n"); + fsm->next_state = S_SEL_CMD; + + // Clean up + i2c_setup(); + } else { + // More data to transfer + fsm->next_state = S_CMD; + } +} + +void notified(microkit_channel ch) +{ + LOG_I2C_DRIVER("Notified on channel %d\n", ch); + + if (ch == config.virt.id) { + LOG_I2C_DRIVER("Notified by virt\n"); + fsm_virt_notified(&fsm_data); + } else if (ch == device_resources.irqs[0].id) { + LOG_I2C_DRIVER("I2C IRQ\n"); + fsm_cmd_done(&fsm_data); + microkit_irq_ack(ch); + } else { + LOG_I2C_DRIVER_ERR("unexpected notification on channel %d\n", ch); + } +} + diff --git a/drivers/i2c/imx/i2c_driver.mk b/drivers/i2c/imx/i2c_driver.mk new file mode 100644 index 000000000..48aa4d606 --- /dev/null +++ b/drivers/i2c/imx/i2c_driver.mk @@ -0,0 +1,35 @@ +# +# Copyright 2026, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Include this snippet in your project Makefile to build +# the Meson i2c driver +# +# NOTES +# Generates i2c_driver.elf +# Requires libsddf_util_debug.a in ${LIBS} + +I2C_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +i2c_driver.elf: i2c/i2c_driver.o i2c/i2c_common.o + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +i2c/i2c_driver.o: CFLAGS+=-I${I2C_DRIVER_DIR} +i2c/i2c_driver.o: ${I2C_DRIVER_DIR}/i2c.c |i2c $(SDDF_LIBC_INCLUDE) + ${CC} ${CFLAGS} -c -o $@ $< + +i2c/i2c_common.o: CFLAGS+=-I${I2C_DRIVER_DIR} +i2c/i2c_common.o: ${I2C_DRIVER_DIR}/../i2c_common.c |i2c $(SDDF_LIBC_INCLUDE) + ${CC} ${CFLAGS} -c -o $@ $< + +i2c: + mkdir -p $@ + +clean:: + rm -rf i2c + +clobber:: + rm -f i2c_driver.elf + +-include i2c_driver.d diff --git a/examples/i2c/i2c.mk b/examples/i2c/i2c.mk index f2b2662d7..283af67a9 100644 --- a/examples/i2c/i2c.mk +++ b/examples/i2c/i2c.mk @@ -18,7 +18,9 @@ endif PYTHONPATH := ${SDDF}/tools/meta:${PYTHONPATH} export PYTHONPATH -SUPPORTED_BOARDS := odroidc4 +SUPPORTED_BOARDS := \ + odroidc4 \ + maaxboard include ${SDDF}/tools/make/board/common.mk From 55d99cb02c8d64c2c0881fda7548417b9451dcb7 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Fri, 3 Jul 2026 09:47:21 +1000 Subject: [PATCH 08/23] Updated ina219 and bus scan i2c examples to use acacia Signed-off-by: Lesley Rossouw --- acacia_sddf/board.py | 4 ++-- acacia_sddf/i2c.py | 8 +++++--- acacia_sddf/sddf.py | 2 +- acacia_sddf/serial.py | 18 +++++++++++------- acacia_sddf/timer.py | 1 + examples/i2c/meta.py | 4 ++-- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/acacia_sddf/board.py b/acacia_sddf/board.py index 6eba5ceb7..418ee9640 100644 --- a/acacia_sddf/board.py +++ b/acacia_sddf/board.py @@ -94,8 +94,8 @@ 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"), - i2c=DriverDouble("","soc@0/bus@30800000/i2c@30a50000") - partition=2, + i2c=DriverDouble("","soc@0/bus@30800000/i2c@30a50000"), + partition=2 ), Board( name="odroidc2", diff --git a/acacia_sddf/i2c.py b/acacia_sddf/i2c.py index 52572372f..e8d5d4f7b 100644 --- a/acacia_sddf/i2c.py +++ b/acacia_sddf/i2c.py @@ -134,6 +134,7 @@ def construct_infrastructure(self, virt_prio): 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) @@ -175,15 +176,16 @@ def connect_clients(self): 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(self.sdf, f"i2c_client_request_{c.name}", 0x1000) + c_req_q_mr = MemoryRegion(f"i2c_client_request_{c.name}", 0x1000, self.sdf) c_resp_q_mr = MemoryRegion( - self.sdf, f"i2c_client_response_{c.name}", 0x1000 + f"i2c_client_response_{c.name}", 0x1000, self.sdf ) - c_data_mr = MemoryRegion(self.sdf, f"i2c_client_data_{c.name}", I2C_DATA_SZ) + 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)) diff --git a/acacia_sddf/sddf.py b/acacia_sddf/sddf.py index d0234d5d9..b869516f2 100644 --- a/acacia_sddf/sddf.py +++ b/acacia_sddf/sddf.py @@ -155,7 +155,7 @@ def create_dtb_resources(self, driver_pd: ProtectionDomain) -> ConfigStruct: else: # This is new (or overlapping with a different start) mr = MemoryRegion( - self.sdf, region_name, mr_sz, paddr=d_paddr, cached=False + region_name, mr_sz, self.sdf, paddr=d_paddr, cached=False ) else: # This is a MR that doesn't correspond to physical memory diff --git a/acacia_sddf/serial.py b/acacia_sddf/serial.py index d63826717..0d066e51e 100644 --- a/acacia_sddf/serial.py +++ b/acacia_sddf/serial.py @@ -114,7 +114,7 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): ) driver_tx_queue_mr = MemoryRegion( - self.sdf, "serial_driver_tx_queue", self.queue_size + "serial_driver_tx_queue", self.queue_size, self.sdf ) driver_tx_data_mr = MemoryRegion( self.sdf, @@ -140,6 +140,7 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): 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,10 +157,10 @@ 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( - self.sdf, "serial_driver_rx_queue", self.queue_size + "serial_driver_rx_queue", self.queue_size, self.sdf ) driver_rx_data_mr = MemoryRegion( - self.sdf, "serial_driver_rx_data", self.data_size + "serial_driver_rx_data", self.data_size, self.sdf ) driver_rx_queue_map = self.driver.create_automap( @@ -179,6 +180,7 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): 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( @@ -223,10 +225,10 @@ def connect_clients(self): # TX connection: virt_tx -> client tx_queue_mr = MemoryRegion( - self.sdf, f"serial_tx_queue_{c.name}", self.queue_size + f"serial_tx_queue_{c.name}", self.queue_size, self.sdf ) tx_data_mr = MemoryRegion( - self.sdf, f"serial_tx_data_{c.name}", self.data_size + f"serial_tx_data_{c.name}", self.data_size, self.sdf ) virt_tx_tx_queue_map = self.virt_tx.create_automap( @@ -246,6 +248,7 @@ def connect_clients(self): 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( @@ -263,10 +266,10 @@ def connect_clients(self): client_rx_conn = None if self.virt_rx: rx_queue_mr = MemoryRegion( - self.sdf, f"serial_rx_queue_{c.name}", self.queue_size + f"serial_rx_queue_{c.name}", self.queue_size, self.sdf ) rx_data_mr = MemoryRegion( - self.sdf, f"serial_rx_data_{c.name}", self.data_size + f"serial_rx_data_{c.name}", self.data_size, self.sdf ) virt_rx_rx_queue_map = self.virt_rx.create_automap( @@ -286,6 +289,7 @@ def connect_clients(self): 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 f4c381bb6..2f688cf56 100644 --- a/acacia_sddf/timer.py +++ b/acacia_sddf/timer.py @@ -60,6 +60,7 @@ def connect_clients(self): 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 ebb1d654f..c772e4ead 100644 --- a/examples/i2c/meta.py +++ b/examples/i2c/meta.py @@ -11,9 +11,9 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): - client_pn532 = ProtectionDomain(sdf, "client_pn532", "client_pn532.elf", priority=1) + client_pn532 = ProtectionDomain("client_pn532", "client_pn532.elf", sdf, priority=1) client_ds3231 = ProtectionDomain( - sdf, "client_ds3231", "client_ds3231.elf", priority=1 + "client_ds3231", "client_ds3231.elf", sdf, priority=1 ) i2c = sDDFI2C( From 70f774264c2793e2dc96ca39a136068bdbbf007e Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Fri, 3 Jul 2026 09:58:55 +1000 Subject: [PATCH 09/23] Added CI test for imx I2C on the Maaxboard, add Acacia support Signed-off-by: Lesley Rossouw --- acacia_sddf/board.py | 2 +- acacia_sddf/i2c.py | 10 +++++++ ci/matrix.py | 2 +- drivers/i2c/imx/driver.h | 1 - drivers/i2c/imx/i2c.c | 30 ++++++++++----------- drivers/i2c/imx/i2c_driver.mk | 49 +++++++++++++++++++---------------- 6 files changed, 52 insertions(+), 42 deletions(-) diff --git a/acacia_sddf/board.py b/acacia_sddf/board.py index 418ee9640..2b4060fe6 100644 --- a/acacia_sddf/board.py +++ b/acacia_sddf/board.py @@ -95,7 +95,7 @@ class Board: ethernet=DriverDouble("", "soc@0/bus@30800000/ethernet@30be0000"), blk=DriverDouble("", "soc@0/bus@30800000/mmc@30b40000"), i2c=DriverDouble("","soc@0/bus@30800000/i2c@30a50000"), - partition=2 + partition=2, ), Board( name="odroidc2", diff --git a/acacia_sddf/i2c.py b/acacia_sddf/i2c.py index e8d5d4f7b..7be3f55ed 100644 --- a/acacia_sddf/i2c.py +++ b/acacia_sddf/i2c.py @@ -374,3 +374,13 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): irqs=[DTSIRQ(4), DTSIRQ(0), DTSIRQ(1), DTSIRQ(7), DTSIRQ(9)], ), ) + +# imx (8mq only for now? untested on others) +add_driver_config( + "imx", + sDDFDriverConfig( + compatible=["fsl,imx8mq-i2c", "fsl,imx21-i2c"], + regions=[DTSRegion("regs", "rw", 4096, 0)], + irqs=[DTSIRQ(0)], + ), +) diff --git a/ci/matrix.py b/ci/matrix.py index 7bd443ca3..d9263c95f 100644 --- a/ci/matrix.py +++ b/ci/matrix.py @@ -82,7 +82,7 @@ def listify(s: str | Sequence[str]) -> Sequence[str]: "i2c_bus_scan": { "configs": ["debug", "release"], "build_systems": ["make"], - "boards": ["serengeti"], + "boards": ["serengeti", "maaxboard"], "tests_exclude": [], }, "ina219": { diff --git a/drivers/i2c/imx/driver.h b/drivers/i2c/imx/driver.h index e0811c928..f7eccddee 100644 --- a/drivers/i2c/imx/driver.h +++ b/drivers/i2c/imx/driver.h @@ -39,4 +39,3 @@ struct imx_i2c_regs { #define REG_SR_SRW (1 << 2) // Slave Read/Write (1=Slave transmit) #define REG_SR_IIF (1 << 1) // I2C Interrupt (set when byte transfer complete) #define REG_SR_RXAK (1 << 0) // Received Acknowledge (1=No ACK received, 0=ACK received) - diff --git a/drivers/i2c/imx/i2c.c b/drivers/i2c/imx/i2c.c index 5581c3b39..0264f38fc 100644 --- a/drivers/i2c/imx/i2c.c +++ b/drivers/i2c/imx/i2c.c @@ -11,7 +11,6 @@ #include #include "driver.h" - __attribute__((__section__(".i2c_driver_config"))) i2c_driver_config_t config; __attribute__((__section__(".device_resources"))) device_resources_t device_resources; @@ -26,9 +25,8 @@ i2c_queue_handle_t queue_handle; bool dummy_read = false; -i2c_state_func_t *i2c_state_table[NUM_STATES] = { - state_idle, state_req, state_sel_cmd, state_cmd, state_cmd_ret, state_resp -}; +i2c_state_func_t *i2c_state_table[NUM_STATES] = { state_idle, state_req, state_sel_cmd, + state_cmd, state_cmd_ret, state_resp }; /** * Initialise the i2c master interface for 400KHz fast mode. @@ -98,14 +96,14 @@ void init(void) /** * Send a start condition. This method should short circuit other writes to the CR register. */ -static inline void imx_i2c_start(bool repeat) { +static inline void imx_i2c_start(bool repeat) +{ // Update dummy read flag upon switch to MTX dummy_read = false; if (!repeat) { regs->i2cr |= REG_CR_MSTA; // wait until bus is clear - for (uint32_t i = 0; (i < 10000) && (regs->i2sr & REG_SR_IBB); i++) { - } + for (uint32_t i = 0; (i < 10000) && (regs->i2sr & REG_SR_IBB); i++) {} // if our short busy wait failed, die if (regs->i2sr & REG_SR_IBB) { @@ -130,7 +128,8 @@ static inline void imx_i2c_start(bool repeat) { * This is required as the hardware will only generate an interrupt on * the successful completion of a transfer. */ -static inline bool imx_i2c_ackd(void) { +static inline bool imx_i2c_ackd(void) +{ // TODO: figure out if a busy wait is required return ((regs->i2sr & REG_SR_RXAK) == 0); } @@ -142,8 +141,8 @@ static inline bool imx_i2c_ackd(void) { */ void state_cmd(fsm_data_t *fsm, i2c_driver_data_t *data, i2c_queue_handle_t *queue_handle) { - LOG_I2C_DRIVER("S_CMD: rw_idx=%u, len=%u, await_start=%u, await_stop=%u\n", - data->rw_idx, data->active_cmd.data_len, data->await_start, data->await_stop); + LOG_I2C_DRIVER("S_CMD: rw_idx=%u, len=%u, await_start=%u, await_stop=%u\n", data->rw_idx, data->active_cmd.data_len, + data->await_start, data->await_stop); // Sanity: if IAL (arbitration lost) is ever asserted, die. This should never happen for now, // as our protocol currently doesn't support arbitration. @@ -250,7 +249,7 @@ void state_cmd(fsm_data_t *fsm, i2c_driver_data_t *data, i2c_queue_handle_t *que // The byte we read here is garbage/invalid for the first read. // Store dummy byte in this buffer to avoid optimising out... // TODO: check for fencepost error here. - data->active_cmd.payload.data[0] = (uint8_t) regs->i2dr; + data->active_cmd.payload.data[0] = (uint8_t)regs->i2dr; LOG_I2C_DRIVER("MTX->RX dummy read completed...\n"); dummy_read = true; } @@ -283,9 +282,9 @@ void state_cmd(fsm_data_t *fsm, i2c_driver_data_t *data, i2c_queue_handle_t *que // Real read: simply store result directly. We are effectively saving // the result of the previous read data kick. - data->active_cmd.payload.data[data->bytes_read] = (uint8_t) regs->i2dr; - LOG_I2C_DRIVER("Reading byte 0x%02x at idx %u\n", - data->active_cmd.payload.data[data->bytes_read], data->rw_idx); + data->active_cmd.payload.data[data->bytes_read] = (uint8_t)regs->i2dr; + LOG_I2C_DRIVER("Reading byte 0x%02x at idx %u\n", data->active_cmd.payload.data[data->bytes_read], + data->rw_idx); data->bytes_read++; data->rw_idx++; @@ -295,7 +294,7 @@ void state_cmd(fsm_data_t *fsm, i2c_driver_data_t *data, i2c_queue_handle_t *que write_byte = data->active_cmd.payload.data[data->rw_idx]; // Generate stop just after sending last byte. - if (data->rw_idx >= data->active_cmd.data_len-1) { + if (data->rw_idx >= data->active_cmd.data_len - 1) { if (data->await_stop) { LOG_I2C_DRIVER("Generating STOP (read)\n"); // Clear MSTA and MTX to generate STOP condition @@ -401,4 +400,3 @@ void notified(microkit_channel ch) LOG_I2C_DRIVER_ERR("unexpected notification on channel %d\n", ch); } } - diff --git a/drivers/i2c/imx/i2c_driver.mk b/drivers/i2c/imx/i2c_driver.mk index 48aa4d606..a60f0d276 100644 --- a/drivers/i2c/imx/i2c_driver.mk +++ b/drivers/i2c/imx/i2c_driver.mk @@ -1,35 +1,38 @@ # -# 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 i2c driver +#Include this snippet in your project Makefile to build +#the Meson i2c driver # -# NOTES -# Generates i2c_driver.elf -# Requires libsddf_util_debug.a in ${LIBS} +#NOTES +#Generates i2c_driver.elf +#Requires libsddf_util_debug.a in ${ LIBS } -I2C_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +I2C_DRIVER_DIR : = $(dir $(lastword $(MAKEFILE_LIST))) -i2c_driver.elf: i2c/i2c_driver.o i2c/i2c_common.o - $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + i2c_driver.elf : i2c + / i2c_driver.o i2c / i2c_common.o $(LD) $(LDFLAGS) $ + ^ $(LIBS) + - o $ @ -i2c/i2c_driver.o: CFLAGS+=-I${I2C_DRIVER_DIR} -i2c/i2c_driver.o: ${I2C_DRIVER_DIR}/i2c.c |i2c $(SDDF_LIBC_INCLUDE) - ${CC} ${CFLAGS} -c -o $@ $< + i2c + / i2c_driver.o + : CFLAGS += -I$ { I2C_DRIVER_DIR } i2c / i2c_driver.o : $ { I2C_DRIVER_DIR } / i2c.c + | i2c $(SDDF_LIBC_INCLUDE) $ { CC } $ { CFLAGS } - c - o $ @$ < -i2c/i2c_common.o: CFLAGS+=-I${I2C_DRIVER_DIR} -i2c/i2c_common.o: ${I2C_DRIVER_DIR}/../i2c_common.c |i2c $(SDDF_LIBC_INCLUDE) - ${CC} ${CFLAGS} -c -o $@ $< + i2c / i2c_common.o : CFLAGS += -I$ { I2C_DRIVER_DIR } i2c / i2c_common.o : $ { I2C_DRIVER_DIR } /.. + / i2c_common.c + | i2c $(SDDF_LIBC_INCLUDE) $ { CC } $ { CFLAGS } - c - o $ @$ < -i2c: - mkdir -p $@ + i2c : mkdir + - p $ @ -clean:: - rm -rf i2c + clean::rm + - rf i2c -clobber:: - rm -f i2c_driver.elf + clobber::rm + - f i2c_driver.elf --include i2c_driver.d + - include i2c_driver.d From b936dc69cca0d1468604c7c0153ce0270a83e966 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Fri, 3 Jul 2026 10:07:40 +1000 Subject: [PATCH 10/23] Lint Python with Acacia additions Signed-off-by: Lesley Rossouw --- acacia_sddf/i2c.py | 18 +++--------------- acacia_sddf/sddf.py | 4 +--- acacia_sddf/serial.py | 36 +++++++++++------------------------- acacia_sddf/timer.py | 2 +- examples/i2c/meta.py | 4 +--- 5 files changed, 17 insertions(+), 47 deletions(-) diff --git a/acacia_sddf/i2c.py b/acacia_sddf/i2c.py index 7be3f55ed..0c9de4f5a 100644 --- a/acacia_sddf/i2c.py +++ b/acacia_sddf/i2c.py @@ -134,7 +134,7 @@ def construct_infrastructure(self, virt_prio): 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.sdf ) self.channels.append(driver_virt_ch) @@ -176,15 +176,13 @@ def connect_clients(self): self.sdf, Channel.End(c, can_notify=True, can_pp=True), Channel.End(self.virt, can_notify=True, can_pp=False), - self.sdf, + 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_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 @@ -374,13 +372,3 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): irqs=[DTSIRQ(4), DTSIRQ(0), DTSIRQ(1), DTSIRQ(7), DTSIRQ(9)], ), ) - -# imx (8mq only for now? untested on others) -add_driver_config( - "imx", - sDDFDriverConfig( - compatible=["fsl,imx8mq-i2c", "fsl,imx21-i2c"], - regions=[DTSRegion("regs", "rw", 4096, 0)], - irqs=[DTSIRQ(0)], - ), -) diff --git a/acacia_sddf/sddf.py b/acacia_sddf/sddf.py index b869516f2..30c1328e8 100644 --- a/acacia_sddf/sddf.py +++ b/acacia_sddf/sddf.py @@ -154,9 +154,7 @@ 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(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) diff --git a/acacia_sddf/serial.py b/acacia_sddf/serial.py index 0d066e51e..dac783b6a 100644 --- a/acacia_sddf/serial.py +++ b/acacia_sddf/serial.py @@ -113,9 +113,7 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): cpu=self.cpu, ) - driver_tx_queue_mr = MemoryRegion( - "serial_driver_tx_queue", self.queue_size, self.sdf - ) + driver_tx_queue_mr = MemoryRegion("serial_driver_tx_queue", self.queue_size, self.sdf) driver_tx_data_mr = MemoryRegion( self.sdf, "serial_driver_tx_data", @@ -140,7 +138,7 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): 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, + self.sdf ) driver_tx_conn = self.serial_connection_resource_factory( @@ -156,12 +154,8 @@ 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("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) @@ -180,7 +174,7 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): 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, + self.sdf ) driver_rx_conn = self.serial_connection_resource_factory( @@ -224,12 +218,8 @@ 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(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) @@ -248,7 +238,7 @@ def connect_clients(self): self.sdf, Channel.End(self.virt_tx, can_notify=True, can_pp=False), Channel.End(c, can_notify=True, can_pp=False), - self.sdf, + self.sdf ) virt_tx_conn = self.serial_connection_resource_factory( @@ -265,12 +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, self.sdf - ) - rx_data_mr = MemoryRegion( - f"serial_rx_data_{c.name}", self.data_size, self.sdf - ) + 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) @@ -289,7 +275,7 @@ def connect_clients(self): self.sdf, Channel.End(self.virt_rx, can_notify=True, can_pp=False), Channel.End(c, can_notify=True, can_pp=False), - self.sdf, + self.sdf ) virt_rx_conn = self.serial_connection_resource_factory( diff --git a/acacia_sddf/timer.py b/acacia_sddf/timer.py index 2f688cf56..2c2597a1d 100644 --- a/acacia_sddf/timer.py +++ b/acacia_sddf/timer.py @@ -60,7 +60,7 @@ def connect_clients(self): self.sdf, Channel.End(c, can_notify=False, can_pp=True), Channel.End(self.driver, can_notify=True, can_pp=False), - self.sdf, + 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 c772e4ead..63a6b8cb0 100644 --- a/examples/i2c/meta.py +++ b/examples/i2c/meta.py @@ -12,9 +12,7 @@ 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_ds3231 = ProtectionDomain("client_ds3231", "client_ds3231.elf", sdf, priority=1) i2c = sDDFI2C( sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=199 From 305fa5da5ce185e6363ecd5c957ea120dd762650 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Fri, 3 Jul 2026 10:21:05 +1000 Subject: [PATCH 11/23] Fix licencing Signed-off-by: Lesley Rossouw --- drivers/i2c/imx/config.json | 21 --------------- drivers/i2c/imx/i2c_driver.mk | 49 ++++++++++++++++------------------- 2 files changed, 23 insertions(+), 47 deletions(-) delete mode 100644 drivers/i2c/imx/config.json diff --git a/drivers/i2c/imx/config.json b/drivers/i2c/imx/config.json deleted file mode 100644 index cb4f54493..000000000 --- a/drivers/i2c/imx/config.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compatible": [ - "fsl,imx8mq-i2c", - "fsl,imx21-i2c" - ], - "resources": { - "regions": [ - { - "name": "regs", - "perms": "rw", - "size": 4096, - "dt_index": 0 - } - ], - "irqs": [ - { - "dt_index": 0 - } - ] - } -} diff --git a/drivers/i2c/imx/i2c_driver.mk b/drivers/i2c/imx/i2c_driver.mk index a60f0d276..8b3fda28e 100644 --- a/drivers/i2c/imx/i2c_driver.mk +++ b/drivers/i2c/imx/i2c_driver.mk @@ -1,38 +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 i2c driver +# Include this snippet in your project Makefile to build +# the imx i2c driver # -#NOTES -#Generates i2c_driver.elf -#Requires libsddf_util_debug.a in ${ LIBS } +# NOTES +# Generates i2c_driver.elf +# Requires libsddf_util_debug.a in ${LIBS} -I2C_DRIVER_DIR : = $(dir $(lastword $(MAKEFILE_LIST))) +I2C_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) - i2c_driver.elf : i2c - / i2c_driver.o i2c / i2c_common.o $(LD) $(LDFLAGS) $ - ^ $(LIBS) - - o $ @ +i2c_driver.elf: i2c/i2c_driver.o i2c/i2c_common.o + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ - i2c - / i2c_driver.o - : CFLAGS += -I$ { I2C_DRIVER_DIR } i2c / i2c_driver.o : $ { I2C_DRIVER_DIR } / i2c.c - | i2c $(SDDF_LIBC_INCLUDE) $ { CC } $ { CFLAGS } - c - o $ @$ < +i2c/i2c_driver.o: CFLAGS+=-I${I2C_DRIVER_DIR} +i2c/i2c_driver.o: ${I2C_DRIVER_DIR}/i2c.c |i2c $(SDDF_LIBC_INCLUDE) + ${CC} ${CFLAGS} -c -o $@ $< - i2c / i2c_common.o : CFLAGS += -I$ { I2C_DRIVER_DIR } i2c / i2c_common.o : $ { I2C_DRIVER_DIR } /.. - / i2c_common.c - | i2c $(SDDF_LIBC_INCLUDE) $ { CC } $ { CFLAGS } - c - o $ @$ < +i2c/i2c_common.o: CFLAGS+=-I${I2C_DRIVER_DIR} +i2c/i2c_common.o: ${I2C_DRIVER_DIR}/../i2c_common.c |i2c $(SDDF_LIBC_INCLUDE) + ${CC} ${CFLAGS} -c -o $@ $< - i2c : mkdir - - p $ @ +i2c: + mkdir -p $@ - clean::rm - - rf i2c +clean:: + rm -rf i2c - clobber::rm - - f i2c_driver.elf +clobber:: + rm -f i2c_driver.elf - - include i2c_driver.d +-include i2c_driver.d From 677fcfef5a9e84ba7cab938b7c7d453a525d0499 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Fri, 3 Jul 2026 10:24:28 +1000 Subject: [PATCH 12/23] Replace bit shift bits with BIT macro Signed-off-by: Lesley Rossouw --- acacia_sddf/board.py | 2 +- acacia_sddf/i2c.py | 20 +++++++++++++++----- acacia_sddf/sddf.py | 4 +++- acacia_sddf/serial.py | 32 +++++++++++++++++++++----------- acacia_sddf/timer.py | 1 - drivers/i2c/imx/driver.h | 26 +++++++++++++------------- examples/i2c/meta.py | 6 ++++-- 7 files changed, 57 insertions(+), 34 deletions(-) diff --git a/acacia_sddf/board.py b/acacia_sddf/board.py index 2b4060fe6..705d3b657 100644 --- a/acacia_sddf/board.py +++ b/acacia_sddf/board.py @@ -94,7 +94,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"), - i2c=DriverDouble("","soc@0/bus@30800000/i2c@30a50000"), + i2c=DriverDouble("fsl,imx8mq-i2c", "soc@0/bus@30800000/i2c@30a20000"), partition=2, ), Board( diff --git a/acacia_sddf/i2c.py b/acacia_sddf/i2c.py index 0c9de4f5a..74da991ca 100644 --- a/acacia_sddf/i2c.py +++ b/acacia_sddf/i2c.py @@ -134,7 +134,6 @@ def construct_infrastructure(self, virt_prio): 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) @@ -176,14 +175,15 @@ def connect_clients(self): 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)) @@ -372,3 +372,13 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): irqs=[DTSIRQ(4), DTSIRQ(0), DTSIRQ(1), DTSIRQ(7), DTSIRQ(9)], ), ) + +# imx (8mq only for now? untested on others) +add_driver_config( + "imx", + sDDFDriverConfig( + compatible=["fsl,imx8mq-i2c", "fsl,imx21-i2c"], + regions=[DTSRegion("regs", "rw", 4096, 0)], + irqs=[DTSIRQ(0)], + ), +) diff --git a/acacia_sddf/sddf.py b/acacia_sddf/sddf.py index 30c1328e8..d0234d5d9 100644 --- a/acacia_sddf/sddf.py +++ b/acacia_sddf/sddf.py @@ -154,7 +154,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 dac783b6a..d63826717 100644 --- a/acacia_sddf/serial.py +++ b/acacia_sddf/serial.py @@ -113,7 +113,9 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): 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", @@ -138,7 +140,6 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): 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( @@ -154,8 +155,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) @@ -174,7 +179,6 @@ def construct_infrastructure(self, virt_rx_prio: int, virt_tx_prio: int): 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( @@ -218,8 +222,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) @@ -238,7 +246,6 @@ def connect_clients(self): 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 +262,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) @@ -275,7 +286,6 @@ def connect_clients(self): 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 2c2597a1d..f4c381bb6 100644 --- a/acacia_sddf/timer.py +++ b/acacia_sddf/timer.py @@ -60,7 +60,6 @@ def connect_clients(self): 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/drivers/i2c/imx/driver.h b/drivers/i2c/imx/driver.h index f7eccddee..20082b585 100644 --- a/drivers/i2c/imx/driver.h +++ b/drivers/i2c/imx/driver.h @@ -24,18 +24,18 @@ struct imx_i2c_regs { }; // I2CR Control Register bits -#define REG_CR_IEN (1 << 7) // I2C Enable -#define REG_CR_IIEN (1 << 6) // I2C Interrupt Enable -#define REG_CR_MSTA (1 << 5) // Master/Slave mode (1=Master, generates START when 0->1) -#define REG_CR_MTX (1 << 4) // Transmit/Receive (1=Transmit, 0=Receive) -#define REG_CR_TXAK (1 << 3) // Transmit Acknowledge (1=No ACK sent) -#define REG_CR_RSTA (1 << 2) // Repeat Start (1=Generate repeated START) +#define REG_CR_IEN BIT(7) // I2C Enable +#define REG_CR_IIEN BIT(6) // I2C Interrupt Enable +#define REG_CR_MSTA BIT(5) // Master/Slave mode (1=Master, generates START when 0->1) +#define REG_CR_MTX BIT(4) // Transmit/Receive (1=Transmit, 0=Receive) +#define REG_CR_TXAK BIT(3) // Transmit Acknowledge (1=No ACK sent) +#define REG_CR_RSTA BIT(2) // Repeat Start (1=Generate repeated START) // I2SR Status Register bits -#define REG_SR_ICF (1 << 7) // Transfer Complete (set at 9th clock falling edge) -#define REG_SR_IAAS (1 << 6) // Addressed As Slave -#define REG_SR_IBB (1 << 5) // Bus Busy (1=Busy, set by START, cleared by STOP) -#define REG_SR_IAL (1 << 4) // Arbitration Lost -#define REG_SR_SRW (1 << 2) // Slave Read/Write (1=Slave transmit) -#define REG_SR_IIF (1 << 1) // I2C Interrupt (set when byte transfer complete) -#define REG_SR_RXAK (1 << 0) // Received Acknowledge (1=No ACK received, 0=ACK received) +#define REG_SR_ICF BIT(7) // Transfer Complete (set at 9th clock falling edge) +#define REG_SR_IAAS BIT(6) // Addressed As Slave +#define REG_SR_IBB BIT(5) // Bus Busy (1=Busy, set by START, cleared by STOP) +#define REG_SR_IAL BIT(4) // Arbitration Lost +#define REG_SR_SRW BIT(2) // Slave Read/Write (1=Slave transmit) +#define REG_SR_IIF BIT(1) // I2C Interrupt (set when byte transfer complete) +#define REG_SR_RXAK BIT(0) // Received Acknowledge (1=No ACK received, 0=ACK received) diff --git a/examples/i2c/meta.py b/examples/i2c/meta.py index 63a6b8cb0..ebb1d654f 100644 --- a/examples/i2c/meta.py +++ b/examples/i2c/meta.py @@ -11,8 +11,10 @@ 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( sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=199 From 6a2be3508d824689f82f83ca250684236a3e4fee Mon Sep 17 00:00:00 2001 From: Michael Tanto Date: Fri, 13 Feb 2026 14:44:26 +1100 Subject: [PATCH 13/23] add imx i2c driver add returh of data handler to imx i2c update i2c imx registers to align with data sheet memory map update i2c imx init step to use fast-mode divider add notes on i2c clock calculation update i2c imx driver to send start when await start update imx i2c address cycle handling add transmit successful check update boards.py to contain i2c region for imx add read handling in state_cmd_ret for i2c imx update i2c imx to set TXAK register when reading second to last byte --- drivers/i2c/imx/config.json | 20 ++ drivers/i2c/imx/driver.h | 52 +--- drivers/i2c/imx/i2c.c | 533 +++++++++++++++--------------------- drivers/timer/meson/timer.c | 50 ++-- 4 files changed, 289 insertions(+), 366 deletions(-) create mode 100644 drivers/i2c/imx/config.json diff --git a/drivers/i2c/imx/config.json b/drivers/i2c/imx/config.json new file mode 100644 index 000000000..0e6e0a18a --- /dev/null +++ b/drivers/i2c/imx/config.json @@ -0,0 +1,20 @@ +{ + "compatible": [ + "fsl,imx8mq-i2c,imx21-i2c" + ], + "resources": { + "regions": [ + { + "name": "regs", + "perms": "rw", + "size": 4096, + "dt_index": 0 + } + ], + "irqs": [ + { + "dt_index": 0 + } + ] + } +} diff --git a/drivers/i2c/imx/driver.h b/drivers/i2c/imx/driver.h index 20082b585..f3fb2ca73 100644 --- a/drivers/i2c/imx/driver.h +++ b/drivers/i2c/imx/driver.h @@ -1,41 +1,17 @@ -/* - * Copyright 2026, UNSW - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - #include -#include - -// Base: 0x30A20000 for I2C1 -struct imx_i2c_regs { - uint16_t iadr; // 0x00 - I2C Address Register - uint16_t _pad0; - uint16_t ifdr; // 0x04 - I2C Frequency Divider Register - uint16_t _pad1; - uint16_t i2cr; // 0x08 - I2C Control Register - uint16_t _pad2; - uint16_t i2sr; // 0x0C - I2C Status Register - uint16_t _pad3; - uint16_t i2dr; // 0x10 - I2C Data I/O Register - uint16_t _pad4; -}; +#include "gpio.h" +#include "clk.h" +#pragma once +// TODO: might need to change this all to little endian -// I2CR Control Register bits -#define REG_CR_IEN BIT(7) // I2C Enable -#define REG_CR_IIEN BIT(6) // I2C Interrupt Enable -#define REG_CR_MSTA BIT(5) // Master/Slave mode (1=Master, generates START when 0->1) -#define REG_CR_MTX BIT(4) // Transmit/Receive (1=Transmit, 0=Receive) -#define REG_CR_TXAK BIT(3) // Transmit Acknowledge (1=No ACK sent) -#define REG_CR_RSTA BIT(2) // Repeat Start (1=Generate repeated START) +// Ctl register fields +#define REG_CTRL_IEN (BIT(7)) +#define REG_CTRL_MSTA (BIT(5)) -// I2SR Status Register bits -#define REG_SR_ICF BIT(7) // Transfer Complete (set at 9th clock falling edge) -#define REG_SR_IAAS BIT(6) // Addressed As Slave -#define REG_SR_IBB BIT(5) // Bus Busy (1=Busy, set by START, cleared by STOP) -#define REG_SR_IAL BIT(4) // Arbitration Lost -#define REG_SR_SRW BIT(2) // Slave Read/Write (1=Slave transmit) -#define REG_SR_IIF BIT(1) // I2C Interrupt (set when byte transfer complete) -#define REG_SR_RXAK BIT(0) // Received Acknowledge (1=No ACK received, 0=ACK received) +// Status register fields +#define REG_STAT_MTX (BIT(4)) +// Busy bit for bus +#define REG_STAT_IBB (BIT(5)) +#define REG_STAT_IFF (BIT(1)) +#define REG_STAT_RXAK (BIT(0)) +#define REG_STAT_TXAK (BIT(3)) diff --git a/drivers/i2c/imx/i2c.c b/drivers/i2c/imx/i2c.c index 0264f38fc..c632bd7b3 100644 --- a/drivers/i2c/imx/i2c.c +++ b/drivers/i2c/imx/i2c.c @@ -1,9 +1,15 @@ /* - * Copyright 2026, UNSW + * Copyright 2025, UNSW * * SPDX-License-Identifier: BSD-2-Clause */ +// Implementation of the i2c driver targeting the ODROID C4. +// Each instance of this driver corresponds to one of the four +// available i2c master interfaces on the device. +// Lesley Rossouw (lesley.rossouw@unsw.edu.au) +// 05/2025 + #include #include #include @@ -11,69 +17,96 @@ #include #include "driver.h" +#ifndef I2C_BUS_NUM +#error "I2C_BUS_NUM must be defined!" +#endif + +// Uncomment to enable logging +// #define DEBUG_I2C_DRIVER + +// Note that only the first 16 bits are used in these fields +// The last 16 bits in each field is for padding +struct i2c_regs { + uint32_t addr; // slave address and other fields + uint32_t freq; + uint32_t ctl; // control register + uint32_t status; + uint32_t data; +} + +// TODO: check how to configure this __attribute__((__section__(".i2c_driver_config"))) i2c_driver_config_t config; + __attribute__((__section__(".device_resources"))) device_resources_t device_resources; -// NOTE: i2c registers on the imx8mq are 16 bit! +// TODO: check what these should be on imx +// Hardware memory +uintptr_t clk_regs = 0x30000000; +uintptr_t gpio_regs = 0x30100000; -// Base addresses: I2C1=0x30A20000, I2C2=0x30A30000, I2C3=0x30A40000, I2C4=0x30A50000 -volatile struct imx_i2c_regs *regs; +volatile struct i2c_regs *regs; +// Driver state for each interface i2c_driver_data_t driver_data; fsm_data_t fsm_data = { 0 }; -i2c_queue_handle_t queue_handle; -bool dummy_read = false; +// Shared memory regions +i2c_queue_handle_t queue_handle; +// This table is responsible for relating the state enum to the state functions. +// I.e. i2c_state_table[0] == i2c_state_table[S_IDLE] == state_idle(*f, *data). +// If you change the state enum and/or add/remove states, make sure you keep this up to date! i2c_state_func_t *i2c_state_table[NUM_STATES] = { state_idle, state_req, state_sel_cmd, state_cmd, state_cmd_ret, state_resp }; /** - * Initialise the i2c master interface for 400KHz fast mode. - */ + * Initialise the i2c master interfaces. +*/ static inline void i2c_setup(void) { - LOG_I2C_DRIVER("initialising i2c master interface...\n"); - - // disable initially - regs->i2cr = 0; - LOG_I2C_DRIVER("a\n"); - - // clear interrupt flag - regs->i2sr &= ~REG_SR_IIF; - LOG_I2C_DRIVER("b\n"); + LOG_I2C_DRIVER("initialising i2c master interfaces...\n"); + // TODO: check if these bits are correct + // Initialisation sequence + regs->ctl |= REG_CTRL_IEN; + + // Set I2C frequency divider for 100 kHz + // Assuming 24 MHz system clock: 24MHz / 100kHz = 240 divider + // From IFDR table, IC=0x0F gives divider of 240 + // TODO: check if this is correct + // NOTE: should be fast-mode (400 MHz) + // I think this should be 250 actually recheck calculations + regs->freq = 0x06; + + // Set to master mode + regs->ctl |= REG_CTRL_MSTA; +} - // Set frequency divider for 400 KHz (IC=0x06, divider=60 for 24MHz clock) - regs->ifdr = 0x06; - LOG_I2C_DRIVER("c\n"); +/** + * Given a bus interface, retrieve the error code stored in the control register associated. + * Also gets bytes read and curr token (curr token can be used to find the error location) + * @return error bit - if the i2c device generates a NACK during writing + */ +static inline bool i2c_get_error(uint8_t *bytes_read, uint8_t *curr_token) +{ + volatile uint32_t ctl = regs->ctl; + bool err = ctl & (1 << 3); // bit 3 -> set if error + *bytes_read = (ctl & 0xF00) >> 8; // bits 8-11 -> number of bytes to read from rdata registers + *curr_token = (ctl & 0xF0) >> 4; // bits 4-7 -> curr token - // enable, and turn on interrupts - regs->i2cr = REG_CR_IEN | REG_CR_IIEN; - LOG_I2C_DRIVER("done\n"); + return err; } /** - * Aborts the current operation by generating a STOP condition and disabling master mode. + * Aborts the current operation by generating an I2C STOP command on the I2C bus */ int i2c_halt(void) { - LOG_I2C_DRIVER("I2C HALT\n"); - - // Clear MSTA bit to generate STOP - regs->i2cr &= ~(REG_CR_MSTA | REG_CR_MTX); - - // busy wait for ibb to clear ... - int timeout = 10000; - while ((regs->i2sr & REG_SR_IBB) && timeout-- > 0) {} - - if (timeout <= 0) { - LOG_I2C_DRIVER_ERR("failed to halt - bus busy timeout\n"); + LOG_I2C_DRIVER("LIST PROCESSOR HALT\n"); + regs->ctl &= ~0x1; + if ((regs->ctl & 0x1)) { + LOG_I2C_DRIVER("failed to halt!\n"); return -1; } - - // Reset to idle state (keep IEN and IIEN enabled) - regs->i2cr = REG_CR_IEN | REG_CR_IIEN; - return 0; } @@ -81,321 +114,205 @@ void init(void) { assert(i2c_config_check_magic(&config)); assert(device_resources_check_magic(&device_resources)); - assert(device_resources.num_irqs == 1 || device_resources.num_irqs == 2); + assert(device_resources.num_irqs == 2); assert(device_resources.num_regions == 1); - regs = (volatile struct imx_i2c_regs *)device_resources.regions[0].region.vaddr; + regs = (volatile struct i2c_regs *)device_resources.regions[0].region.vaddr; i2c_setup(); + // Set up driver state and shared regions i2c_reset_state(&driver_data); queue_handle = i2c_queue_init(config.virt.req_queue.vaddr, config.virt.resp_queue.vaddr); - LOG_I2C_DRIVER("i.MX8M I2C driver initialised.\n"); + microkit_dbg_puts("Driver initialised.\n"); } + +// 55% duty cycle -> double check fastmode spec +// we always want to target fast mode i2c +// https://www.nxp.com/docs/en/user-guide/UM10204.pdf -/** - * Send a start condition. This method should short circuit other writes to the CR register. - */ -static inline void imx_i2c_start(bool repeat) -{ - // Update dummy read flag upon switch to MTX - dummy_read = false; - if (!repeat) { - regs->i2cr |= REG_CR_MSTA; - // wait until bus is clear - for (uint32_t i = 0; (i < 10000) && (regs->i2sr & REG_SR_IBB); i++) {} - - // if our short busy wait failed, die - if (regs->i2sr & REG_SR_IBB) { - LOG_I2C_DRIVER_ERR("Failed to claim bus on start! Is there another master?\n"); - assert(0); - } - - // write other start flags - regs->i2cr |= REG_CR_MTX | REG_CR_IIEN; - } else { - regs->i2cr |= REG_CR_RSTA; - } - - // Always enable TXAK at start of a transaction. - regs->i2cr &= ~REG_CR_TXAK; -} +// IMX data sheet +// TS document on i2c +// check linux driver for i2c init +// check this for duty cycle: https://www.nxp.com/docs/en/user-guide/UM10204.pdf +// useful calculator: https://qalculate.github.io/ +// functions to change: init, state_cmd, state_cmd_ret -> everything else should be fine -/** - * After sending an address+start OR a data byte, call this function - * to check if an acknowledgement is received. - * - * This is required as the hardware will only generate an interrupt on - * the successful completion of a transfer. - */ -static inline bool imx_i2c_ackd(void) -{ - // TODO: figure out if a busy wait is required - return ((regs->i2sr & REG_SR_RXAK) == 0); -} /** * S_CMD (command) - * Initiate a single bus operation (START, data byte, or STOP) then yield to await IRQ completion. - * The imx8mq I2C controller operates byte-by-byte, not as a list processor. + * Initiate work for the current command then go to S_CMD_RET to await device completion. + * Succeeds: S_SEL_CMD, S_CMD_RET + * Sucessor(s): S_CMD_RET, S_RESPONSE (error) */ void state_cmd(fsm_data_t *fsm, i2c_driver_data_t *data, i2c_queue_handle_t *queue_handle) { - LOG_I2C_DRIVER("S_CMD: rw_idx=%u, len=%u, await_start=%u, await_stop=%u\n", data->rw_idx, data->active_cmd.data_len, - data->await_start, data->await_stop); - - // Sanity: if IAL (arbitration lost) is ever asserted, die. This should never happen for now, - // as our protocol currently doesn't support arbitration. - if (regs->i2sr & REG_SR_IAL) { - LOG_I2C_DRIVER_ERR("Arbitration lost! This should never happen as we do not support multi master!\n"); - LOG_I2C_DRIVER_ERR("Dying now.\n"); - // clear IAL - regs->i2sr &= ~REG_SR_IAL; - data->err = I2C_ERR_OTHER; - fsm->next_state = S_RESPONSE; - return; - } - // invariant: these tmp registers are unused if we imx_i2c_start() as the start - // op needs to directly touch the registers. - uint32_t i2cr_tmp = regs->i2cr; - uint32_t i2dr_tmp = regs->i2dr; - bool do_i2dr_write = false; - bool do_i2cr_write = false; - if (data->await_start) { - LOG_I2C_DRIVER("Sending start condition...\n"); - // This is a repeated start if await_addr is not set. - bool is_repeat = (data->await_addr == 0); - - // Sanity: if a write-read is pending, this repeated start is invalid. - // The virt should handle this, but on the imx this condition is completely - // unhandleable so we check again. - assert(!(is_repeat && data->await_wrrd)); - - imx_i2c_start(is_repeat); - data->await_start = false; - fsm->yield = false; - // If we're here, i2c_start has already waited until ibb=0. - // Return to re-enter this state and check if arbitration loss occured. - return; - } - - // Addressing stage - if (data->await_wrrd) { - // first: handle sending initial address. - // invariant: start condition already set - LOG_I2C_DRIVER("Selected WRRD\n"); - - if (data->await_wrrd == WRRD_WRADDR) { - // Write address to data register. read bit = 0 - i2dr_tmp = i2c_curr_addr(data) << 1; - do_i2dr_write = true; - // invariant: mtx already set from await_start - } else if (data->await_wrrd == WRRD_SUBADDR) { - // Write sub address byte - uint8_t payload_byte = data->active_cmd.payload.data[0]; - i2dr_tmp = payload_byte; - do_i2dr_write = true; - LOG_I2C_DRIVER("WRRD: sending address byte %u\n", payload_byte); - } else { - LOG_I2C_DRIVER("WRRD: sending repeat start and looping\n"); - // Send a new start condition and leave await_addr to set up the read - // for us. - imx_i2c_start(true); - LOG_I2C_DRIVER("WRRD: ... done!\n"); - fsm->yield = false; - data->await_wrrd = 0; - // Re-enter state to check for arb. loss and continue. - return; + // TODO: clear all registers first? + + // The following code loops through all available space in the registers their is either: + // a. no space in a needed register + // b. the command is over + // - add meson_token equivalent to token list register + // - if read or write increment wdata or rdata offset + // - if write add buffer data into wdata + // Note: we check <= instead of < for the cmd length because we always need an extra round to + // send a STOP token. + // TODO: check this while condition and match it with meson, check if no space in register + // TODO: check IBB register before executing anything (poll IBB) + while (data->rw_idx <= data->active_cmd.data_len) { // Discover next operation + // Poll IBB register (check if bus is busy) + // TODO: check that this is correct and that there is no better way to poll + while (!(regs->status &= (uint16_t) REG_STAT_IBB)) {} + + uint8_t meson_token; + uint32_t payload_byte; + // Handle sending start condition + if (data->await_start) { + LOG_I2C_DRIVER("Selected START\n"); + // Set to TX (transmit start) + regs->status |= (uint16_t) REG_STAT_MTX; + + meson_token = MESON_I2C_TOKEN_START; + + data->await_start = false; + // Set bus to busy + regs->status |= (uint16_t) REG_STAT_IBB; } - data->await_wrrd--; - - } else if (data->await_addr) { - LOG_I2C_DRIVER("Primary addressing stage...\n"); - // Calculate address byte with R/W bit - uint8_t addr_byte = (i2c_curr_addr(data) << 1); - bool is_read = cmd_is_read(data->active_cmd); - - if (is_read) { - addr_byte |= 0x1; // Read operation (R/W bit = 1) - } else { - addr_byte &= ~0x1; // Write operation (R/W bit = 0) - } - - LOG_I2C_DRIVER("Sending ADDR 0x%02x (read=%u)\n", i2c_curr_addr(data), is_read); - - // Write address to data register - this initiates the transfer - i2dr_tmp = addr_byte; - do_i2dr_write = true; - data->await_addr = false; - - // Data transmission and end - } else { - // read case - if (cmd_is_read(data->active_cmd)) { - // For the first data byte of a read, we need to switch to receive mode - // and do a dummy read to initiate the first byte reception - if (dummy_read == false) { - // Clear MTX to enter receive mode - i2cr_tmp &= ~REG_CR_MTX; - do_i2cr_write = true; - - // If this is a single byte read, prepare NACK for it - if (data->active_cmd.data_len == 1) { - i2cr_tmp |= REG_CR_TXAK; // No ACK for single byte - } - - // Dummy read to initiate first byte reception - // The byte we read here is garbage/invalid for the first read. - // Store dummy byte in this buffer to avoid optimising out... - // TODO: check for fencepost error here. - data->active_cmd.payload.data[0] = (uint8_t)regs->i2dr; - LOG_I2C_DRIVER("MTX->RX dummy read completed...\n"); - dummy_read = true; + // Handle sending address token + else if (data->await_addr) { + // TODO: change magic number + uint32_t transfer_direction = cmd_is_read(cmd) ? 1 : 0; + meson_token = cmd_is_read(cmd) ? MESON_I2C_TOKEN_ADDR_READ : MESON_I2C_TOKEN_ADDR_WRITE; + payload_byte = (i2c_curr_addr(data) & 0x7f << 1); + // Set transfer direction (read is 1 write is 0) + payload_byte |= transfer_direction; + data->await_addr = false; + } + // Handle sending stop condition once command has sent all bytes if we need one. + else if (data->rw_idx >= cmd.data_len) { + if (data->await_stop) { + meson_token = MESON_I2C_TOKEN_STOP; + data->await_stop = false; + } else { + // If we don't need a stop condition, skip final iteration. + break; } + data->rw_idx++; + } + // Handle data transmission + else { + // Should be the end of address cycle (TODO: check if this is right) + // If command is read, switch to RX mode and dummy read from I2C_I2DR + if (cmd_is_read(cmd) && data->rw_idx == 0) { - // We must send a stop just before reading the very last byte - if (data->rw_idx >= data->active_cmd.data_len - 1) { - if (data->await_stop) { - LOG_I2C_DRIVER("Generating STOP (read)\n"); - // Clear MSTA and MTX to generate STOP condition - // Use real register here as this must happen strictly - // BEFORE doing the read. - do_i2cr_write = true; - i2cr_tmp &= ~REG_CR_MSTA; - i2cr_tmp &= ~REG_CR_MTX; - data->await_stop = false; - } else { - LOG_I2C_DRIVER("Command complete, skipping back to S_SEL_CMD\n"); - // Command complete without STOP (repeated start follows) - fsm->next_state = S_SEL_CMD; - return; - } } - // Set up NACK for last byte when reading second-to-last byte - if (data->rw_idx == data->active_cmd.data_len - 2) { - // This is the second-to-last byte, set TXAK for NACK on last byte - i2cr_tmp |= REG_CR_TXAK; - do_i2cr_write = true; + // We are in the middle of a read or write. Pick up where we left off + if (data->rw_idx == (cmd.data_len - 1) && cmd_is_read(cmd)) { + meson_token = MESON_I2C_TOKEN_DATA_END; + // TODO: read + // meson_token = MESON_I2C_TOKEN_DATA_END; + } + else { + assert(data->rw_idx < cmd.data_len); + meson_token = MESON_I2C_TOKEN_DATA; } - - // Real read: simply store result directly. We are effectively saving - // the result of the previous read data kick. - data->active_cmd.payload.data[data->bytes_read] = (uint8_t)regs->i2dr; - LOG_I2C_DRIVER("Reading byte 0x%02x at idx %u\n", data->active_cmd.payload.data[data->bytes_read], - data->rw_idx); - data->bytes_read++; - data->rw_idx++; - - } else { - // --- WRITE OPERATION --- - uint8_t write_byte; - write_byte = data->active_cmd.payload.data[data->rw_idx]; - - // Generate stop just after sending last byte. - if (data->rw_idx >= data->active_cmd.data_len - 1) { - if (data->await_stop) { - LOG_I2C_DRIVER("Generating STOP (read)\n"); - // Clear MSTA and MTX to generate STOP condition - i2cr_tmp &= ~REG_CR_MSTA; - i2cr_tmp &= ~REG_CR_MTX; - do_i2cr_write = true; - data->await_stop = false; - } else { - // TODO: check if this is needed, we probably can't get here. - LOG_I2C_DRIVER("Command complete, skipping back to S_SEL_CMD\n"); - // Command complete without STOP (repeated start follows) - fsm->next_state = S_SEL_CMD; - return; - } + // If writing, and this is not the subaddress of a WRRD + if (!cmd_is_read(cmd)) { + payload_byte = cmd.payload.data[data->rw_idx]; // Take next byte to write } - - LOG_I2C_DRIVER("Writing data byte 0x%02x at idx %u\n", write_byte, data->rw_idx); - regs->i2dr = write_byte; - do_i2dr_write = true; data->rw_idx++; } + + // If data token and we are writing (inc. WRRD) or we are sending an address, load data into data register + if ((meson_token == MESON_I2C_TOKEN_DATA && !data_direction_rd(data)) || MESON_I2C_TOKEN_ADDR_READ || MESON_I2C_TOKEN_ADDR_WRITE) { + regs->data = payload_byte; + } + + /* If data token and we are reading (not WRRD write), increment counter of rdata */ + if ((meson_token == MESON_I2C_TOKEN_DATA || meson_token == MESON_I2C_TOKEN_DATA_END) && data_direction_rd(data)) { + // Note: rdata_offset just makes sure we don't read more than 8 times in + // a single transaction as this is the limit of the hardware. + rdata_offset++; + } } - // Only write if required. Writing to the DR in read mode is UB, and unneeded writes to CR - // may have side effects. - // Important: write DR FIRST. - if (do_i2dr_write) { - regs->i2dr = i2dr_tmp; - } - if (do_i2cr_write) { - regs->i2cr = i2cr_tmp; - } - // We should always sleep and go to CMD_RET unless we decided to return earlier to a - // different state. + // Start list processor fsm->next_state = S_CMD_RET; - fsm->yield = true; - return; + fsm->yield = true; // We want to go to sleep awaiting the IRQ coming back to us. } /** * S_CMD_RET - * Handle completion interrupt from hardware. Check status, read data if applicable, - * and determine next state (continue command, select next command, or error). + * Handle return of data from hardware after S_CMD goes to sleep post-load. Decides whether to + * continue working on the current command or to return to S_SEL_CMD for a new one. + * + * Succeeds: S_CMD + * Sucessor(s): S_CMD (cmd not finished yet), S_SEL_CMD (cmd finished), S_RESP (error) */ -void state_cmd_ret(fsm_data_t *fsm, i2c_driver_data_t *data, i2c_queue_handle_t *queue_handle) -{ - // Clear interrupt - regs->i2sr &= ~REG_SR_IIF; - uint16_t status = regs->i2sr; - - LOG_I2C_DRIVER("S_CMD_RET: status=0x%04x\n", status); - - // check arbitration, just in case. - if (status & REG_SR_IAL) { - LOG_I2C_DRIVER_ERR("Arbitration lost!\n"); - data->err = I2C_ERR_OTHER; - // clear IAL - regs->i2sr &= ~REG_SR_IAL; - fsm->next_state = S_RESPONSE; - return; - } - - // Check for NACK (RXAK=1 means no ACK received) - // This applies to address phase and write phases - if (status & REG_SR_RXAK) { - // Don't print this as an error, not useful usually. - LOG_I2C_DRIVER("NACK!\n"); - data->err = I2C_ERR_NACK; - - // Generate STOP and return error - regs->i2cr &= ~(REG_CR_MSTA | REG_CR_MTX); - fsm->next_state = S_RESPONSE; - return; - } +void state_cmd_ret(fsm_data_t *f, i2c_driver_data_t *data, i2c_queue_handle_t *queue_handle) +{ + // Note: unlike the meson driver, we can only read one byte at a time since the data register only holds one byte + // Clear IFF register upon return interrupt + regs->status &= (uint8_t)(~REG_STAT_IFF); + + // TODO: check that transmission was completed succesfully after interrupt complete + // Write error detected + // TODO: doble check this bitmask logic + // If RXAK = 0, error has occured -> return + if (!(regs->status & (uint8_t)(REG_STAT_RXAK))) { + if (curr_token == MESON_I2C_TOKEN_ADDR_READ) { + data->err = I2C_ERR_NOREAD; + } else { + data->err = I2C_ERR_NACK; + } + LOG_I2C_DRIVER("token that caused error: %d!\n", curr_token); + f->next_state = S_RESPONSE; + } else { + // Set TXAK to 1 when reading 2nd to last byte + // TODO: check if index/token write is correct + if (data->rw_idx == (data->active_cmd.data_len - 1)) { + regs->ctl |= REG_STAT_TXAK; + } - // Check if command is complete - if (data->rw_idx >= data->active_cmd.data_len - 1) { - // We just sent the STOP condition in state_cmd, wait for completion - // IIF will be set when STOP completes - LOG_I2C_DRIVER("STOP complete\n"); - fsm->next_state = S_SEL_CMD; + uint8_t value = regs->data; + data->active_cmd.payload.data[data->bytes_read] = value; + data->bytes_read++; - // Clean up - i2c_setup(); - } else { - // More data to transfer - fsm->next_state = S_CMD; + // Decide whether this is the end or to return back to sel_cmd + // Note: this isn't a signpost error. S_CMD runs for one round extra to send a STOP + if (data->rw_idx > data->active_cmd.data_len) + f->next_state = S_SEL_CMD; + else + f->next_state = S_CMD; } } void notified(microkit_channel ch) { - LOG_I2C_DRIVER("Notified on channel %d\n", ch); - + // We have only two possible cases for returning to the FSM from this entrypoint: + // 1. The virt has pinged us. No direct action needed, other than to wake the FSM + // if currently idle. If working, will be answered automagically. + // (curr state = S_IDLE) + // 2. IRQ 0 has landed indicating a completed transaction - resume FSM if we expected this. + // (curr state = S_CMD_RET) + // Any other combination requires no direct action as the FSM is still running, or is spurious. + LOG_I2C_DRIVER("Notified\n"); if (ch == config.virt.id) { - LOG_I2C_DRIVER("Notified by virt\n"); + LOG_I2C_DRIVER("Notified by virt!\n"); fsm_virt_notified(&fsm_data); } else if (ch == device_resources.irqs[0].id) { - LOG_I2C_DRIVER("I2C IRQ\n"); + LOG_I2C_DRIVER("IRQ!\n"); fsm_cmd_done(&fsm_data); microkit_irq_ack(ch); + } else if (ch == device_resources.irqs[1].id) { + /* Timeout IRQ */ + LOG_I2C_DRIVER("Timeout!\n"); + // We don't handle this as there is no clear principled way to do so. + // This IRQ is undocumented and will rapidly be followed by a NACK if + // a device disappears, so there's no clear reason to do anything here. + microkit_irq_ack(ch); } else { LOG_I2C_DRIVER_ERR("unexpected notification on channel %d\n", ch); } diff --git a/drivers/timer/meson/timer.c b/drivers/timer/meson/timer.c index d133fa99c..bda65a68f 100644 --- a/drivers/timer/meson/timer.c +++ b/drivers/timer/meson/timer.c @@ -58,7 +58,8 @@ volatile struct timer_regs *regs; /* Right now, we only service a single timeout per client. * This timeout array indicates when a timeout should occur, * indexed by client ID. */ -static uint64_t timeouts[MAX_TIMEOUTS]; + +static timer_heap_t timeouts; static uint64_t get_ticks(void) { @@ -75,25 +76,36 @@ static uint64_t get_ticks(void) static void process_timeouts(uint64_t curr_time) { - for (int i = 0; i < MAX_TIMEOUTS; i++) { - if (timeouts[i] <= curr_time) { - sddf_notify(i); - timeouts[i] = UINT64_MAX; - } + // Pop from priority heap until all timeouts are serviced + while (timer_heap_peek(&timeouts) != NULL && timer_heap_peek(&timeouts)->timestamp <= curr_time) { + timeout_t expired; + bool ret = timer_heap_pop(&timeouts, &expired); + assert(ret); // This should never happen! Peek should catch empty queue + sddf_notify(expired.client_channel); } - uint64_t next_timeout = UINT64_MAX; - for (int i = 0; i < MAX_TIMEOUTS; i++) { - if (timeouts[i] < next_timeout) { - next_timeout = timeouts[i]; - } - } + timeout_t *next = timer_heap_peek(&timeouts); - if (next_timeout != UINT64_MAX) { + // Reissue next timeout irq, if needed. + if (next != NULL) { + uint64_t next_delay = next->timestamp - curr_time; regs->mux &= ~TIMER_A_MODE; - regs->timer_a = next_timeout - curr_time; + regs->timer_a = next_delay; regs->mux |= TIMER_A_EN; } + + // uint64_t next_timeout = UINT64_MAX; + // for (int i = 0; i < MAX_TIMEOUTS; i++) { + // if (timeouts[i] < next_timeout) { + // next_timeout = timeouts[i]; + // } + // } + + // if (next_timeout != UINT64_MAX) { + // regs->mux &= ~TIMER_A_MODE; + // regs->timer_a = next_timeout - curr_time; + // regs->mux |= TIMER_A_EN; + // } } void notified(sddf_channel ch) @@ -120,8 +132,9 @@ seL4_MessageInfo_t protected(sddf_channel ch, seL4_MessageInfo_t msginfo) } case SDDF_TIMER_SET_TIMEOUT: { uint64_t curr_time = get_ticks(); - uint64_t offset_ticks = ns_to_ticks(sddf_get_mr(0), MESON_TIMER_CLK_FREQ); - timeouts[ch] = curr_time + offset_ticks; + uint64_t offset_us = sddf_get_mr(0) / NS_IN_US; + timer_heap_insert(&timeouts, curr_time + offset_us, ch); + process_timeouts(curr_time); break; } @@ -140,10 +153,6 @@ void init(void) assert(device_resources.num_irqs == 1); assert(device_resources.num_regions == 1); - for (int i = 0; i < MAX_TIMEOUTS; i++) { - timeouts[i] = UINT64_MAX; - } - regs = (void *)((uintptr_t)device_resources.regions[0].region.vaddr + TIMER_REG_START); /* Start timer E acts as a clock, while timer A can be used for timeouts from clients */ @@ -151,4 +160,5 @@ void init(void) | (TIMEOUT_TIMEBASE_1_US << TIMER_A_INPUT_CLK); regs->timer_e = 0; + timer_heap_init(&timeouts); } From 79684a3a502091078ce473db8b9c54e65af880f8 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Thu, 5 Mar 2026 09:37:21 +1100 Subject: [PATCH 14/23] Rework imx i2c to properly implement state machine Signed-off-by: Lesley Rossouw --- drivers/i2c/imx/config.json | 3 +- drivers/i2c/imx/driver.h | 53 +++-- drivers/i2c/imx/i2c.c | 436 +++++++++++++++++----------------- drivers/i2c/imx/i2c_driver.mk | 7 +- 4 files changed, 259 insertions(+), 240 deletions(-) diff --git a/drivers/i2c/imx/config.json b/drivers/i2c/imx/config.json index 0e6e0a18a..cb4f54493 100644 --- a/drivers/i2c/imx/config.json +++ b/drivers/i2c/imx/config.json @@ -1,6 +1,7 @@ { "compatible": [ - "fsl,imx8mq-i2c,imx21-i2c" + "fsl,imx8mq-i2c", + "fsl,imx21-i2c" ], "resources": { "regions": [ diff --git a/drivers/i2c/imx/driver.h b/drivers/i2c/imx/driver.h index f3fb2ca73..e0811c928 100644 --- a/drivers/i2c/imx/driver.h +++ b/drivers/i2c/imx/driver.h @@ -1,17 +1,42 @@ -#include -#include "gpio.h" -#include "clk.h" +/* + * Copyright 2026, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + #pragma once -// TODO: might need to change this all to little endian -// Ctl register fields -#define REG_CTRL_IEN (BIT(7)) -#define REG_CTRL_MSTA (BIT(5)) +#include +#include + +// Base: 0x30A20000 for I2C1 +struct imx_i2c_regs { + uint16_t iadr; // 0x00 - I2C Address Register + uint16_t _pad0; + uint16_t ifdr; // 0x04 - I2C Frequency Divider Register + uint16_t _pad1; + uint16_t i2cr; // 0x08 - I2C Control Register + uint16_t _pad2; + uint16_t i2sr; // 0x0C - I2C Status Register + uint16_t _pad3; + uint16_t i2dr; // 0x10 - I2C Data I/O Register + uint16_t _pad4; +}; + +// I2CR Control Register bits +#define REG_CR_IEN (1 << 7) // I2C Enable +#define REG_CR_IIEN (1 << 6) // I2C Interrupt Enable +#define REG_CR_MSTA (1 << 5) // Master/Slave mode (1=Master, generates START when 0->1) +#define REG_CR_MTX (1 << 4) // Transmit/Receive (1=Transmit, 0=Receive) +#define REG_CR_TXAK (1 << 3) // Transmit Acknowledge (1=No ACK sent) +#define REG_CR_RSTA (1 << 2) // Repeat Start (1=Generate repeated START) + +// I2SR Status Register bits +#define REG_SR_ICF (1 << 7) // Transfer Complete (set at 9th clock falling edge) +#define REG_SR_IAAS (1 << 6) // Addressed As Slave +#define REG_SR_IBB (1 << 5) // Bus Busy (1=Busy, set by START, cleared by STOP) +#define REG_SR_IAL (1 << 4) // Arbitration Lost +#define REG_SR_SRW (1 << 2) // Slave Read/Write (1=Slave transmit) +#define REG_SR_IIF (1 << 1) // I2C Interrupt (set when byte transfer complete) +#define REG_SR_RXAK (1 << 0) // Received Acknowledge (1=No ACK received, 0=ACK received) -// Status register fields -#define REG_STAT_MTX (BIT(4)) -// Busy bit for bus -#define REG_STAT_IBB (BIT(5)) -#define REG_STAT_IFF (BIT(1)) -#define REG_STAT_RXAK (BIT(0)) -#define REG_STAT_TXAK (BIT(3)) diff --git a/drivers/i2c/imx/i2c.c b/drivers/i2c/imx/i2c.c index c632bd7b3..fd41049b8 100644 --- a/drivers/i2c/imx/i2c.c +++ b/drivers/i2c/imx/i2c.c @@ -1,15 +1,9 @@ /* - * Copyright 2025, UNSW + * Copyright 2026, UNSW * * SPDX-License-Identifier: BSD-2-Clause */ -// Implementation of the i2c driver targeting the ODROID C4. -// Each instance of this driver corresponds to one of the four -// available i2c master interfaces on the device. -// Lesley Rossouw (lesley.rossouw@unsw.edu.au) -// 05/2025 - #include #include #include @@ -17,96 +11,64 @@ #include #include "driver.h" -#ifndef I2C_BUS_NUM -#error "I2C_BUS_NUM must be defined!" -#endif - // Uncomment to enable logging -// #define DEBUG_I2C_DRIVER - -// Note that only the first 16 bits are used in these fields -// The last 16 bits in each field is for padding -struct i2c_regs { - uint32_t addr; // slave address and other fields - uint32_t freq; - uint32_t ctl; // control register - uint32_t status; - uint32_t data; -} +#define DEBUG_I2C_DRIVER -// TODO: check how to configure this __attribute__((__section__(".i2c_driver_config"))) i2c_driver_config_t config; - __attribute__((__section__(".device_resources"))) device_resources_t device_resources; -// TODO: check what these should be on imx -// Hardware memory -uintptr_t clk_regs = 0x30000000; -uintptr_t gpio_regs = 0x30100000; +// NOTE: i2c registers on the imx8mq are 16 bit! -volatile struct i2c_regs *regs; +// Base addresses: I2C1=0x30A20000, I2C2=0x30A30000, I2C3=0x30A40000, I2C4=0x30A50000 +volatile struct imx_i2c_regs *regs; -// Driver state for each interface i2c_driver_data_t driver_data; fsm_data_t fsm_data = { 0 }; - -// Shared memory regions i2c_queue_handle_t queue_handle; -// This table is responsible for relating the state enum to the state functions. -// I.e. i2c_state_table[0] == i2c_state_table[S_IDLE] == state_idle(*f, *data). -// If you change the state enum and/or add/remove states, make sure you keep this up to date! -i2c_state_func_t *i2c_state_table[NUM_STATES] = { state_idle, state_req, state_sel_cmd, - state_cmd, state_cmd_ret, state_resp }; +i2c_state_func_t *i2c_state_table[NUM_STATES] = { + state_idle, state_req, state_sel_cmd, state_cmd, state_cmd_ret, state_resp +}; /** - * Initialise the i2c master interfaces. -*/ + * Initialise the i2c master interface for 400KHz fast mode. + */ static inline void i2c_setup(void) { - LOG_I2C_DRIVER("initialising i2c master interfaces...\n"); - // TODO: check if these bits are correct - // Initialisation sequence - regs->ctl |= REG_CTRL_IEN; - - // Set I2C frequency divider for 100 kHz - // Assuming 24 MHz system clock: 24MHz / 100kHz = 240 divider - // From IFDR table, IC=0x0F gives divider of 240 - // TODO: check if this is correct - // NOTE: should be fast-mode (400 MHz) - // I think this should be 250 actually recheck calculations - regs->freq = 0x06; - - // Set to master mode - regs->ctl |= REG_CTRL_MSTA; -} + LOG_I2C_DRIVER("initialising i2c master interface...\n"); -/** - * Given a bus interface, retrieve the error code stored in the control register associated. - * Also gets bytes read and curr token (curr token can be used to find the error location) - * @return error bit - if the i2c device generates a NACK during writing - */ -static inline bool i2c_get_error(uint8_t *bytes_read, uint8_t *curr_token) -{ - volatile uint32_t ctl = regs->ctl; - bool err = ctl & (1 << 3); // bit 3 -> set if error - *bytes_read = (ctl & 0xF00) >> 8; // bits 8-11 -> number of bytes to read from rdata registers - *curr_token = (ctl & 0xF0) >> 4; // bits 4-7 -> curr token + // disable initially + regs->i2cr = 0; + + // Set frequency divider for 400 KHz (IC=0x06, divider=60 for 24MHz clock) + regs->ifdr = 0x06; - return err; + // enable, and turn on interrupts + regs->i2cr = REG_CR_IEN | REG_CR_IIEN; } /** - * Aborts the current operation by generating an I2C STOP command on the I2C bus + * Aborts the current operation by generating a STOP condition and disabling master mode. */ int i2c_halt(void) { - LOG_I2C_DRIVER("LIST PROCESSOR HALT\n"); - regs->ctl &= ~0x1; - if ((regs->ctl & 0x1)) { - LOG_I2C_DRIVER("failed to halt!\n"); + LOG_I2C_DRIVER("I2C HALT\n"); + + // Clear MSTA bit to generate STOP + regs->i2cr &= ~(REG_CR_MSTA | REG_CR_MTX); + + // busy wait for ibb to clear ... + int timeout = 10000; + while ((regs->i2sr & REG_SR_IBB) && timeout-- > 0) {} + + if (timeout <= 0) { + LOG_I2C_DRIVER_ERR("failed to halt - bus busy timeout\n"); return -1; } + + // Reset to idle state (keep IEN and IIEN enabled) + regs->i2cr = REG_CR_IEN | REG_CR_IIEN; + return 0; } @@ -114,206 +76,234 @@ void init(void) { assert(i2c_config_check_magic(&config)); assert(device_resources_check_magic(&device_resources)); - assert(device_resources.num_irqs == 2); + assert(device_resources.num_irqs == 1 || device_resources.num_irqs == 2); assert(device_resources.num_regions == 1); - regs = (volatile struct i2c_regs *)device_resources.regions[0].region.vaddr; + regs = (volatile struct imx_i2c_regs *)device_resources.regions[0].region.vaddr; i2c_setup(); - // Set up driver state and shared regions i2c_reset_state(&driver_data); queue_handle = i2c_queue_init(config.virt.req_queue.vaddr, config.virt.resp_queue.vaddr); - microkit_dbg_puts("Driver initialised.\n"); + microkit_dbg_puts("i.MX8M I2C driver initialised.\n"); } - -// 55% duty cycle -> double check fastmode spec -// we always want to target fast mode i2c -// https://www.nxp.com/docs/en/user-guide/UM10204.pdf - -// IMX data sheet -// TS document on i2c -// check linux driver for i2c init -// check this for duty cycle: https://www.nxp.com/docs/en/user-guide/UM10204.pdf -// useful calculator: https://qalculate.github.io/ -// functions to change: init, state_cmd, state_cmd_ret -> everything else should be fine - /** * S_CMD (command) - * Initiate work for the current command then go to S_CMD_RET to await device completion. - * Succeeds: S_SEL_CMD, S_CMD_RET - * Sucessor(s): S_CMD_RET, S_RESPONSE (error) + * Initiate a single bus operation (START, data byte, or STOP) then yield to await IRQ completion. + * The imx8mq I2C controller operates byte-by-byte, not as a list processor. */ void state_cmd(fsm_data_t *fsm, i2c_driver_data_t *data, i2c_queue_handle_t *queue_handle) { + LOG_I2C_DRIVER("S_CMD: rw_idx=%u, len=%u, await_start=%u, await_stop=%u\n", + data->rw_idx, data->active_cmd.data_len, data->await_start, data->await_stop); + + // Check if we need to send START condition + if (data->await_start) { + // Wait for bus not busy + while (regs->i2sr & REG_SR_IBB) {} + + // Set master mode and transmit mode (for address phase) + // MSTA=1 generates START condition + regs->i2cr |= REG_CR_MSTA | REG_CR_MTX; - // TODO: clear all registers first? - - // The following code loops through all available space in the registers their is either: - // a. no space in a needed register - // b. the command is over - // - add meson_token equivalent to token list register - // - if read or write increment wdata or rdata offset - // - if write add buffer data into wdata - // Note: we check <= instead of < for the cmd length because we always need an extra round to - // send a STOP token. - // TODO: check this while condition and match it with meson, check if no space in register - // TODO: check IBB register before executing anything (poll IBB) - while (data->rw_idx <= data->active_cmd.data_len) { // Discover next operation - // Poll IBB register (check if bus is busy) - // TODO: check that this is correct and that there is no better way to poll - while (!(regs->status &= (uint16_t) REG_STAT_IBB)) {} - - uint8_t meson_token; - uint32_t payload_byte; - // Handle sending start condition - if (data->await_start) { - LOG_I2C_DRIVER("Selected START\n"); - // Set to TX (transmit start) - regs->status |= (uint16_t) REG_STAT_MTX; - - meson_token = MESON_I2C_TOKEN_START; - - data->await_start = false; - // Set bus to busy - regs->status |= (uint16_t) REG_STAT_IBB; + // Calculate address byte with R/W bit + uint8_t addr_byte = (i2c_curr_addr(data) << 1); + bool is_read = cmd_is_read(data->active_cmd) && !data->await_wrrd; + + if (is_read) { + addr_byte |= 0x1; // Read operation (R/W bit = 1) + } else { + addr_byte &= ~0x1; // Write operation (R/W bit = 0) } - // Handle sending address token - else if (data->await_addr) { - // TODO: change magic number - uint32_t transfer_direction = cmd_is_read(cmd) ? 1 : 0; - meson_token = cmd_is_read(cmd) ? MESON_I2C_TOKEN_ADDR_READ : MESON_I2C_TOKEN_ADDR_WRITE; - payload_byte = (i2c_curr_addr(data) & 0x7f << 1); - // Set transfer direction (read is 1 write is 0) - payload_byte |= transfer_direction; - data->await_addr = false; - } - // Handle sending stop condition once command has sent all bytes if we need one. - else if (data->rw_idx >= cmd.data_len) { - if (data->await_stop) { - meson_token = MESON_I2C_TOKEN_STOP; - data->await_stop = false; - } else { - // If we don't need a stop condition, skip final iteration. - break; - } - data->rw_idx++; - } - // Handle data transmission - else { - // Should be the end of address cycle (TODO: check if this is right) - // If command is read, switch to RX mode and dummy read from I2C_I2DR - if (cmd_is_read(cmd) && data->rw_idx == 0) { - } + LOG_I2C_DRIVER("Sending START + ADDR 0x%02x (read=%u)\n", addr_byte, is_read); - // We are in the middle of a read or write. Pick up where we left off - if (data->rw_idx == (cmd.data_len - 1) && cmd_is_read(cmd)) { - meson_token = MESON_I2C_TOKEN_DATA_END; - // TODO: read - // meson_token = MESON_I2C_TOKEN_DATA_END; - } - else { - assert(data->rw_idx < cmd.data_len); - meson_token = MESON_I2C_TOKEN_DATA; - } - // If writing, and this is not the subaddress of a WRRD - if (!cmd_is_read(cmd)) { - payload_byte = cmd.payload.data[data->rw_idx]; // Take next byte to write + // Write address to data register - this initiates the transfer + regs->i2dr = addr_byte; + + data->await_start = false; + data->await_addr = false; + fsm->next_state = S_CMD_RET; + fsm->yield = true; + return; + } + + // Handle STOP condition generation + if (data->rw_idx >= data->active_cmd.data_len) { + if (data->await_stop) { + LOG_I2C_DRIVER("Generating STOP\n"); + // Clear MSTA to generate STOP condition + regs->i2cr &= ~REG_CR_MSTA; + data->await_stop = false; + fsm->next_state = S_CMD_RET; + fsm->yield = true; + return; + } else { + // Command complete without STOP (repeated start follows) + fsm->next_state = S_SEL_CMD; + return; + } + } + + // Handle data transfer + if (cmd_is_read(data->active_cmd) && !data->await_wrrd) { + // --- READ OPERATION --- + + // For the first data byte of a read, we need to switch to receive mode + // and do a dummy read to initiate the first byte reception + if (data->rw_idx == 0) { + // Clear MTX to enter receive mode + regs->i2cr &= ~REG_CR_MTX; + + // If this is a single byte read, prepare NACK for it + if (data->active_cmd.data_len == 1) { + regs->i2cr |= REG_CR_TXAK; // No ACK for single byte } - data->rw_idx++; + + // Dummy read to initiate first byte reception + // The byte we read here is garbage/invalid for the first read + (void)regs->i2dr; + + fsm->next_state = S_CMD_RET; + fsm->yield = true; + return; } - - // If data token and we are writing (inc. WRRD) or we are sending an address, load data into data register - if ((meson_token == MESON_I2C_TOKEN_DATA && !data_direction_rd(data)) || MESON_I2C_TOKEN_ADDR_READ || MESON_I2C_TOKEN_ADDR_WRITE) { - regs->data = payload_byte; + + // For subsequent bytes, the previous read initiated this byte's reception + // Set up NACK for last byte when reading second-to-last byte + if (data->rw_idx == data->active_cmd.data_len - 2) { + // This is the second-to-last byte, set TXAK for NACK on last byte + regs->i2cr |= REG_CR_TXAK; } - - /* If data token and we are reading (not WRRD write), increment counter of rdata */ - if ((meson_token == MESON_I2C_TOKEN_DATA || meson_token == MESON_I2C_TOKEN_DATA_END) && data_direction_rd(data)) { - // Note: rdata_offset just makes sure we don't read more than 8 times in - // a single transaction as this is the limit of the hardware. - rdata_offset++; + + // Read the data byte (this also initiates next byte reception) + // Actual reading happens in state_cmd_ret after IIF + fsm->next_state = S_CMD_RET; + fsm->yield = true; + return; + + } else { + // --- WRITE OPERATION --- + + uint8_t write_byte; + + // Handle write-read subaddress injection + if (data->await_wrrd) { + // For WRRD, first byte is the register subaddress + write_byte = data->active_cmd.payload.data[0]; + data->await_wrrd = false; // WRRD write phase complete + + // After this byte completes, we'll need to do repeated start + read + // The await_wrrd flag being clear indicates we're in read phase now + // But first we need to check if there's data after subaddress in the write portion + + // Actually per sDDF protocol: WRRD means write first byte (subaddr), then read + // So we write one byte, then do repeated start + LOG_I2C_DRIVER("WRRD: writing subaddress 0x%02x\n", write_byte); + } else { + // Normal write or WRRD write portion + write_byte = data->active_cmd.payload.data[data->rw_idx]; } - } - // Start list processor - fsm->next_state = S_CMD_RET; - fsm->yield = true; // We want to go to sleep awaiting the IRQ coming back to us. + LOG_I2C_DRIVER("Writing data byte 0x%02x at idx %u\n", write_byte, data->rw_idx); + + regs->i2dr = write_byte; + data->rw_idx++; + + fsm->next_state = S_CMD_RET; + fsm->yield = true; + return; + } } /** * S_CMD_RET - * Handle return of data from hardware after S_CMD goes to sleep post-load. Decides whether to - * continue working on the current command or to return to S_SEL_CMD for a new one. - * - * Succeeds: S_CMD - * Sucessor(s): S_CMD (cmd not finished yet), S_SEL_CMD (cmd finished), S_RESP (error) + * Handle completion interrupt from hardware. Check status, read data if applicable, + * and determine next state (continue command, select next command, or error). */ void state_cmd_ret(fsm_data_t *f, i2c_driver_data_t *data, i2c_queue_handle_t *queue_handle) -{ - // Note: unlike the meson driver, we can only read one byte at a time since the data register only holds one byte - // Clear IFF register upon return interrupt - regs->status &= (uint8_t)(~REG_STAT_IFF); - - // TODO: check that transmission was completed succesfully after interrupt complete - // Write error detected - // TODO: doble check this bitmask logic - // If RXAK = 0, error has occured -> return - if (!(regs->status & (uint8_t)(REG_STAT_RXAK))) { - if (curr_token == MESON_I2C_TOKEN_ADDR_READ) { +{ + uint16_t status = regs->i2sr; + + LOG_I2C_DRIVER("S_CMD_RET: status=0x%04x\n", status); + + // Check for arbitration lost + if (status & REG_SR_IAL) { + LOG_I2C_DRIVER_ERR("Arbitration lost!\n"); + data->err = I2C_ERR_OTHER; + // Write 1 to clear IAL + regs->i2sr |= REG_SR_IAL; + f->next_state = S_RESPONSE; + return; + } + + // Check for NACK (RXAK=1 means no ACK received) + // This applies to address phase and write phases + if (status & REG_SR_RXAK) { + LOG_I2C_DRIVER_ERR("NACK received\n"); + + // Determine if this was address or data phase + if (data->rw_idx == 0 && !cmd_is_read(data->active_cmd)) { + // Address NACK during write + data->err = I2C_ERR_NACK; + } else if (data->rw_idx == 0 && cmd_is_read(data->active_cmd)) { + // Address NACK during read attempt data->err = I2C_ERR_NOREAD; } else { + // Data phase NACK data->err = I2C_ERR_NACK; } - LOG_I2C_DRIVER("token that caused error: %d!\n", curr_token); + + // Generate STOP and return error + regs->i2cr &= ~REG_CR_MSTA; f->next_state = S_RESPONSE; - } else { - // Set TXAK to 1 when reading 2nd to last byte - // TODO: check if index/token write is correct - if (data->rw_idx == (data->active_cmd.data_len - 1)) { - regs->ctl |= REG_STAT_TXAK; - } + return; + } - uint8_t value = regs->data; + // Handle read data if applicable + if (cmd_is_read(data->active_cmd) && data->rw_idx > 0) { + // Read the received byte + // Note: for the first byte (rw_idx==0), we did dummy read in state_cmd + // The byte received is now in the register from the previous transaction + uint8_t value = regs->i2dr; data->active_cmd.payload.data[data->bytes_read] = value; + LOG_I2C_DRIVER("Read byte 0x%02x at bytes_read=%u\n", value, data->bytes_read); data->bytes_read++; + data->rw_idx++; + } - // Decide whether this is the end or to return back to sel_cmd - // Note: this isn't a signpost error. S_CMD runs for one round extra to send a STOP - if (data->rw_idx > data->active_cmd.data_len) - f->next_state = S_SEL_CMD; - else - f->next_state = S_CMD; + // Check if command is complete + // For reads: rw_idx tracks progress (0..len) + // For writes: rw_idx already incremented in state_cmd + if (data->rw_idx >= data->active_cmd.data_len) { + if (data->await_stop) { + // We just sent the STOP condition in state_cmd, wait for completion + // IIF will be set when STOP completes + LOG_I2C_DRIVER("STOP complete\n"); + } + f->next_state = S_SEL_CMD; + } else { + // More data to transfer + f->next_state = S_CMD; } } void notified(microkit_channel ch) { - // We have only two possible cases for returning to the FSM from this entrypoint: - // 1. The virt has pinged us. No direct action needed, other than to wake the FSM - // if currently idle. If working, will be answered automagically. - // (curr state = S_IDLE) - // 2. IRQ 0 has landed indicating a completed transaction - resume FSM if we expected this. - // (curr state = S_CMD_RET) - // Any other combination requires no direct action as the FSM is still running, or is spurious. - LOG_I2C_DRIVER("Notified\n"); + LOG_I2C_DRIVER("Notified on channel %d\n", ch); + if (ch == config.virt.id) { - LOG_I2C_DRIVER("Notified by virt!\n"); + LOG_I2C_DRIVER("Notified by virt\n"); fsm_virt_notified(&fsm_data); } else if (ch == device_resources.irqs[0].id) { - LOG_I2C_DRIVER("IRQ!\n"); + LOG_I2C_DRIVER("I2C IRQ\n"); + // Clear IIF by writing 0 to it (W1C) + regs->i2sr &= ~REG_SR_IIF; fsm_cmd_done(&fsm_data); microkit_irq_ack(ch); - } else if (ch == device_resources.irqs[1].id) { - /* Timeout IRQ */ - LOG_I2C_DRIVER("Timeout!\n"); - // We don't handle this as there is no clear principled way to do so. - // This IRQ is undocumented and will rapidly be followed by a NACK if - // a device disappears, so there's no clear reason to do anything here. - microkit_irq_ack(ch); } else { LOG_I2C_DRIVER_ERR("unexpected notification on channel %d\n", ch); } } + diff --git a/drivers/i2c/imx/i2c_driver.mk b/drivers/i2c/imx/i2c_driver.mk index 8b3fda28e..113185e11 100644 --- a/drivers/i2c/imx/i2c_driver.mk +++ b/drivers/i2c/imx/i2c_driver.mk @@ -4,18 +4,21 @@ # SPDX-License-Identifier: BSD-2-Clause # # Include this snippet in your project Makefile to build -# the imx i2c driver +# the Meson i2c driver # # NOTES # Generates i2c_driver.elf # Requires libsddf_util_debug.a in ${LIBS} +# Has one parameter: I2C_BUS_NUM to select which bus is being driven I2C_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +# default to internal bus, connected to pmc +I2C_BUS_NUM ?= 1 i2c_driver.elf: i2c/i2c_driver.o i2c/i2c_common.o $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ -i2c/i2c_driver.o: CFLAGS+=-I${I2C_DRIVER_DIR} +i2c/i2c_driver.o: CFLAGS+=-I${I2C_DRIVER_DIR} -DI2C_BUS_NUM=${I2C_BUS_NUM} i2c/i2c_driver.o: ${I2C_DRIVER_DIR}/i2c.c |i2c $(SDDF_LIBC_INCLUDE) ${CC} ${CFLAGS} -c -o $@ $< From ba733640b9c57a9f7500f2e20705eb56b1edd1a5 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Fri, 6 Mar 2026 15:13:20 +1100 Subject: [PATCH 15/23] Working I2C driver for maaxboard. Only works on bus 1 however, unclear why. Signed-off-by: Lesley Rossouw --- drivers/i2c/imx/i2c.c | 364 +++++++++++++++++++++------------- drivers/timer/meson/timer.c | 48 ++--- examples/i2c/meta.py | 46 ++++- include/sddf/i2c/i2c_driver.h | 2 +- 4 files changed, 291 insertions(+), 169 deletions(-) diff --git a/drivers/i2c/imx/i2c.c b/drivers/i2c/imx/i2c.c index fd41049b8..2fb4c7bd0 100644 --- a/drivers/i2c/imx/i2c.c +++ b/drivers/i2c/imx/i2c.c @@ -11,8 +11,6 @@ #include #include "driver.h" -// Uncomment to enable logging -#define DEBUG_I2C_DRIVER __attribute__((__section__(".i2c_driver_config"))) i2c_driver_config_t config; __attribute__((__section__(".device_resources"))) device_resources_t device_resources; @@ -26,6 +24,8 @@ i2c_driver_data_t driver_data; fsm_data_t fsm_data = { 0 }; i2c_queue_handle_t queue_handle; +bool dummy_read = false; + i2c_state_func_t *i2c_state_table[NUM_STATES] = { state_idle, state_req, state_sel_cmd, state_cmd, state_cmd_ret, state_resp }; @@ -39,12 +39,19 @@ static inline void i2c_setup(void) // disable initially regs->i2cr = 0; + LOG_I2C_DRIVER("a\n"); + + // clear interrupt flag + regs->i2sr &= ~REG_SR_IIF; + LOG_I2C_DRIVER("b\n"); // Set frequency divider for 400 KHz (IC=0x06, divider=60 for 24MHz clock) regs->ifdr = 0x06; + LOG_I2C_DRIVER("c\n"); // enable, and turn on interrupts regs->i2cr = REG_CR_IEN | REG_CR_IIEN; + LOG_I2C_DRIVER("done\n"); } /** @@ -85,7 +92,42 @@ void init(void) i2c_reset_state(&driver_data); queue_handle = i2c_queue_init(config.virt.req_queue.vaddr, config.virt.resp_queue.vaddr); - microkit_dbg_puts("i.MX8M I2C driver initialised.\n"); + LOG_I2C_DRIVER("i.MX8M I2C driver initialised.\n"); +} + +/** + * Send a start condition. This method should short circuit other writes to the CR register. + */ +static inline void imx_i2c_start(bool repeat) { + // Update dummy read flag upon switch to MTX + dummy_read = false; + if (!repeat) { + regs->i2cr |= REG_CR_MSTA; + // wait until bus is clear + while (regs->i2sr & REG_SR_IBB) { + LOG_I2C_DRIVER("AHHHHH AHHH OHH MY GOD AHHHH\n"); + } + + // write other start flags + regs->i2cr |= REG_CR_MTX | REG_CR_IIEN; + } else { + regs->i2cr |= REG_CR_RSTA; + } + + // Always enable TXAK at start of a transaction. + regs->i2cr &= ~REG_CR_TXAK; +} + +/** + * After sending an address+start OR a data byte, call this function + * to check if an acknowledgement is received. + * + * This is required as the hardware will only generate an interrupt on + * the successful completion of a transfer. + */ +static inline bool imx_i2c_ackd(void) { + // TODO: figure out if a busy wait is required + return ((regs->i2sr & REG_SR_RXAK) == 0); } /** @@ -98,123 +140,194 @@ void state_cmd(fsm_data_t *fsm, i2c_driver_data_t *data, i2c_queue_handle_t *que LOG_I2C_DRIVER("S_CMD: rw_idx=%u, len=%u, await_start=%u, await_stop=%u\n", data->rw_idx, data->active_cmd.data_len, data->await_start, data->await_stop); - // Check if we need to send START condition - if (data->await_start) { - // Wait for bus not busy - while (regs->i2sr & REG_SR_IBB) {} - - // Set master mode and transmit mode (for address phase) - // MSTA=1 generates START condition - regs->i2cr |= REG_CR_MSTA | REG_CR_MTX; - - // Calculate address byte with R/W bit - uint8_t addr_byte = (i2c_curr_addr(data) << 1); - bool is_read = cmd_is_read(data->active_cmd) && !data->await_wrrd; - - if (is_read) { - addr_byte |= 0x1; // Read operation (R/W bit = 1) - } else { - addr_byte &= ~0x1; // Write operation (R/W bit = 0) - } + // Sanity: if IAL (arbitration lost) is ever asserted, die. This should never happen for now, + // as our protocol currently doesn't support arbitration. + if (regs->i2sr & REG_SR_IAL) { + LOG_I2C_DRIVER_ERR("Arbitration lost! This should never happen as we do not support multi master!\n"); + LOG_I2C_DRIVER_ERR("Dying now.\n"); + // clear IAL + regs->i2sr &= ~REG_SR_IAL; + data->err = I2C_ERR_OTHER; + fsm->next_state = S_RESPONSE; + return; + } - LOG_I2C_DRIVER("Sending START + ADDR 0x%02x (read=%u)\n", addr_byte, is_read); + // invariant: these tmp registers are unused if we imx_i2c_start() as the start + // op needs to directly touch the registers. + uint32_t i2cr_tmp = regs->i2cr; + uint32_t i2dr_tmp = regs->i2dr; + bool do_i2dr_write = false; + bool do_i2cr_write = false; + if (data->await_start) { + LOG_I2C_DRIVER("Sending start condition...\n"); + // This is a repeated start if await_addr is not set. + bool is_repeat = (data->await_addr == 0); - // Write address to data register - this initiates the transfer - regs->i2dr = addr_byte; + // Sanity: if a write-read is pending, this repeated start is invalid. + // The virt should handle this, but on the imx this condition is completely + // unhandleable so we check again. + assert(!(is_repeat && data->await_wrrd)); + imx_i2c_start(is_repeat); data->await_start = false; - data->await_addr = false; - fsm->next_state = S_CMD_RET; - fsm->yield = true; + fsm->yield = false; + // If we're here, i2c_start has already waited until ibb=0. + // Return to re-enter this state and check if arbitration loss occured. return; } - // Handle STOP condition generation - if (data->rw_idx >= data->active_cmd.data_len) { - if (data->await_stop) { - LOG_I2C_DRIVER("Generating STOP\n"); - // Clear MSTA to generate STOP condition - regs->i2cr &= ~REG_CR_MSTA; - data->await_stop = false; - fsm->next_state = S_CMD_RET; - fsm->yield = true; - return; + // Addressing stage + if (data->await_wrrd) { + // first: handle sending initial address. + // invariant: start condition already set + LOG_I2C_DRIVER("Selected WRRD\n"); + + if (data->await_wrrd == WRRD_WRADDR) { + // Write address to data register. read bit = 0 + i2dr_tmp = i2c_curr_addr(data) << 1; + do_i2dr_write = true; + // invariant: mtx already set from await_start + } else if (data->await_wrrd == WRRD_SUBADDR) { + // Write sub address byte + uint8_t payload_byte = data->active_cmd.payload.data[0]; + i2dr_tmp = payload_byte; + do_i2dr_write = true; + LOG_I2C_DRIVER("WRRD: sending address byte %u\n", payload_byte); } else { - // Command complete without STOP (repeated start follows) - fsm->next_state = S_SEL_CMD; + LOG_I2C_DRIVER("WRRD: sending repeat start and looping\n"); + // Send a new start condition and leave await_addr to set up the read + // for us. + imx_i2c_start(true); + LOG_I2C_DRIVER("WRRD: ... done!\n"); + fsm->yield = false; + data->await_wrrd = 0; + // Re-enter state to check for arb. loss and continue. return; } - } - - // Handle data transfer - if (cmd_is_read(data->active_cmd) && !data->await_wrrd) { - // --- READ OPERATION --- + data->await_wrrd--; - // For the first data byte of a read, we need to switch to receive mode - // and do a dummy read to initiate the first byte reception - if (data->rw_idx == 0) { - // Clear MTX to enter receive mode - regs->i2cr &= ~REG_CR_MTX; - - // If this is a single byte read, prepare NACK for it - if (data->active_cmd.data_len == 1) { - regs->i2cr |= REG_CR_TXAK; // No ACK for single byte - } - - // Dummy read to initiate first byte reception - // The byte we read here is garbage/invalid for the first read - (void)regs->i2dr; + } else if (data->await_addr) { + LOG_I2C_DRIVER("Primary addressing stage...\n"); + // Calculate address byte with R/W bit + uint8_t addr_byte = (i2c_curr_addr(data) << 1); + bool is_read = cmd_is_read(data->active_cmd); - fsm->next_state = S_CMD_RET; - fsm->yield = true; - return; + if (is_read) { + addr_byte |= 0x1; // Read operation (R/W bit = 1) + } else { + addr_byte &= ~0x1; // Write operation (R/W bit = 0) } - // For subsequent bytes, the previous read initiated this byte's reception - // Set up NACK for last byte when reading second-to-last byte - if (data->rw_idx == data->active_cmd.data_len - 2) { - // This is the second-to-last byte, set TXAK for NACK on last byte - regs->i2cr |= REG_CR_TXAK; - } + LOG_I2C_DRIVER("Sending ADDR 0x%02x (read=%u)\n", i2c_curr_addr(data), is_read); - // Read the data byte (this also initiates next byte reception) - // Actual reading happens in state_cmd_ret after IIF - fsm->next_state = S_CMD_RET; - fsm->yield = true; - return; + // Write address to data register - this initiates the transfer + i2dr_tmp = addr_byte; + do_i2dr_write = true; + data->await_addr = false; + // Data transmission and end } else { - // --- WRITE OPERATION --- + // read case + if (cmd_is_read(data->active_cmd)) { + // For the first data byte of a read, we need to switch to receive mode + // and do a dummy read to initiate the first byte reception + if (dummy_read == false) { + // Clear MTX to enter receive mode + i2cr_tmp &= ~REG_CR_MTX; + do_i2cr_write = true; + + // If this is a single byte read, prepare NACK for it + if (data->active_cmd.data_len == 1) { + i2cr_tmp |= REG_CR_TXAK; // No ACK for single byte + } + + // Dummy read to initiate first byte reception + // The byte we read here is garbage/invalid for the first read. + // Store dummy byte in this buffer to avoid optimising out... + // TODO: check for fencepost error here. + data->active_cmd.payload.data[0] = (uint8_t) regs->i2dr; + LOG_I2C_DRIVER("MTX->RX dummy read completed...\n"); + dummy_read = true; + } - uint8_t write_byte; + // We must send a stop just before reading the very last byte + if (data->rw_idx >= data->active_cmd.data_len - 1) { + if (data->await_stop) { + LOG_I2C_DRIVER("Generating STOP (read)\n"); + // Clear MSTA and MTX to generate STOP condition + // Use real register here as this must happen strictly + // BEFORE doing the read. + do_i2cr_write = true; + i2cr_tmp &= ~REG_CR_MSTA; + i2cr_tmp &= ~REG_CR_MTX; + data->await_stop = false; + } else { + LOG_I2C_DRIVER("Command complete, skipping back to S_SEL_CMD\n"); + // Command complete without STOP (repeated start follows) + fsm->next_state = S_SEL_CMD; + return; + } + } - // Handle write-read subaddress injection - if (data->await_wrrd) { - // For WRRD, first byte is the register subaddress - write_byte = data->active_cmd.payload.data[0]; - data->await_wrrd = false; // WRRD write phase complete + // Set up NACK for last byte when reading second-to-last byte + if (data->rw_idx == data->active_cmd.data_len - 2) { + // This is the second-to-last byte, set TXAK for NACK on last byte + i2cr_tmp |= REG_CR_TXAK; + do_i2cr_write = true; + } - // After this byte completes, we'll need to do repeated start + read - // The await_wrrd flag being clear indicates we're in read phase now - // But first we need to check if there's data after subaddress in the write portion + // Real read: simply store result directly. We are effectively saving + // the result of the previous read data kick. + data->active_cmd.payload.data[data->bytes_read] = (uint8_t) regs->i2dr; + LOG_I2C_DRIVER("Reading byte 0x%02x at idx %u\n", + data->active_cmd.payload.data[data->bytes_read], data->rw_idx); + data->bytes_read++; + data->rw_idx++; - // Actually per sDDF protocol: WRRD means write first byte (subaddr), then read - // So we write one byte, then do repeated start - LOG_I2C_DRIVER("WRRD: writing subaddress 0x%02x\n", write_byte); } else { - // Normal write or WRRD write portion + // --- WRITE OPERATION --- + uint8_t write_byte; write_byte = data->active_cmd.payload.data[data->rw_idx]; - } - LOG_I2C_DRIVER("Writing data byte 0x%02x at idx %u\n", write_byte, data->rw_idx); + // Generate stop just after sending last byte. + if (data->rw_idx >= data->active_cmd.data_len-1) { + if (data->await_stop) { + LOG_I2C_DRIVER("Generating STOP (read)\n"); + // Clear MSTA and MTX to generate STOP condition + i2cr_tmp &= ~REG_CR_MSTA; + i2cr_tmp &= ~REG_CR_MTX; + do_i2cr_write = true; + data->await_stop = false; + } else { + // TODO: check if this is needed, we probably can't get here. + LOG_I2C_DRIVER("Command complete, skipping back to S_SEL_CMD\n"); + // Command complete without STOP (repeated start follows) + fsm->next_state = S_SEL_CMD; + return; + } + } - regs->i2dr = write_byte; - data->rw_idx++; + LOG_I2C_DRIVER("Writing data byte 0x%02x at idx %u\n", write_byte, data->rw_idx); + regs->i2dr = write_byte; + do_i2dr_write = true; + data->rw_idx++; + } + } - fsm->next_state = S_CMD_RET; - fsm->yield = true; - return; + // Only write if required. Writing to the DR in read mode is UB, and unneeded writes to CR + // may have side effects. + // Important: write DR FIRST. + if (do_i2dr_write) { + regs->i2dr = i2dr_tmp; } + if (do_i2cr_write) { + regs->i2cr = i2cr_tmp; + } + // We should always sleep and go to CMD_RET unless we decided to return earlier to a + // different state. + fsm->next_state = S_CMD_RET; + fsm->yield = true; + return; } /** @@ -222,70 +335,49 @@ void state_cmd(fsm_data_t *fsm, i2c_driver_data_t *data, i2c_queue_handle_t *que * Handle completion interrupt from hardware. Check status, read data if applicable, * and determine next state (continue command, select next command, or error). */ -void state_cmd_ret(fsm_data_t *f, i2c_driver_data_t *data, i2c_queue_handle_t *queue_handle) +void state_cmd_ret(fsm_data_t *fsm, i2c_driver_data_t *data, i2c_queue_handle_t *queue_handle) { + // Clear interrupt + regs->i2sr &= ~REG_SR_IIF; uint16_t status = regs->i2sr; LOG_I2C_DRIVER("S_CMD_RET: status=0x%04x\n", status); - // Check for arbitration lost + // check arbitration, just in case. if (status & REG_SR_IAL) { LOG_I2C_DRIVER_ERR("Arbitration lost!\n"); data->err = I2C_ERR_OTHER; - // Write 1 to clear IAL - regs->i2sr |= REG_SR_IAL; - f->next_state = S_RESPONSE; + // clear IAL + regs->i2sr &= ~REG_SR_IAL; + fsm->next_state = S_RESPONSE; return; } // Check for NACK (RXAK=1 means no ACK received) // This applies to address phase and write phases if (status & REG_SR_RXAK) { - LOG_I2C_DRIVER_ERR("NACK received\n"); - - // Determine if this was address or data phase - if (data->rw_idx == 0 && !cmd_is_read(data->active_cmd)) { - // Address NACK during write - data->err = I2C_ERR_NACK; - } else if (data->rw_idx == 0 && cmd_is_read(data->active_cmd)) { - // Address NACK during read attempt - data->err = I2C_ERR_NOREAD; - } else { - // Data phase NACK - data->err = I2C_ERR_NACK; - } + // Don't print this as an error, not useful usually. + LOG_I2C_DRIVER("NACK!\n"); + data->err = I2C_ERR_NACK; // Generate STOP and return error - regs->i2cr &= ~REG_CR_MSTA; - f->next_state = S_RESPONSE; + regs->i2cr &= ~(REG_CR_MSTA | REG_CR_MTX); + fsm->next_state = S_RESPONSE; return; } - // Handle read data if applicable - if (cmd_is_read(data->active_cmd) && data->rw_idx > 0) { - // Read the received byte - // Note: for the first byte (rw_idx==0), we did dummy read in state_cmd - // The byte received is now in the register from the previous transaction - uint8_t value = regs->i2dr; - data->active_cmd.payload.data[data->bytes_read] = value; - LOG_I2C_DRIVER("Read byte 0x%02x at bytes_read=%u\n", value, data->bytes_read); - data->bytes_read++; - data->rw_idx++; - } - // Check if command is complete - // For reads: rw_idx tracks progress (0..len) - // For writes: rw_idx already incremented in state_cmd - if (data->rw_idx >= data->active_cmd.data_len) { - if (data->await_stop) { - // We just sent the STOP condition in state_cmd, wait for completion - // IIF will be set when STOP completes - LOG_I2C_DRIVER("STOP complete\n"); - } - f->next_state = S_SEL_CMD; + if (data->rw_idx >= data->active_cmd.data_len - 1) { + // We just sent the STOP condition in state_cmd, wait for completion + // IIF will be set when STOP completes + LOG_I2C_DRIVER("STOP complete\n"); + fsm->next_state = S_SEL_CMD; + + // Clean up + i2c_setup(); } else { // More data to transfer - f->next_state = S_CMD; + fsm->next_state = S_CMD; } } @@ -298,8 +390,6 @@ void notified(microkit_channel ch) fsm_virt_notified(&fsm_data); } else if (ch == device_resources.irqs[0].id) { LOG_I2C_DRIVER("I2C IRQ\n"); - // Clear IIF by writing 0 to it (W1C) - regs->i2sr &= ~REG_SR_IIF; fsm_cmd_done(&fsm_data); microkit_irq_ack(ch); } else { diff --git a/drivers/timer/meson/timer.c b/drivers/timer/meson/timer.c index bda65a68f..c5c2bf6e2 100644 --- a/drivers/timer/meson/timer.c +++ b/drivers/timer/meson/timer.c @@ -58,8 +58,7 @@ volatile struct timer_regs *regs; /* Right now, we only service a single timeout per client. * This timeout array indicates when a timeout should occur, * indexed by client ID. */ - -static timer_heap_t timeouts; +static uint64_t timeouts[MAX_TIMEOUTS]; static uint64_t get_ticks(void) { @@ -76,36 +75,25 @@ static uint64_t get_ticks(void) static void process_timeouts(uint64_t curr_time) { - // Pop from priority heap until all timeouts are serviced - while (timer_heap_peek(&timeouts) != NULL && timer_heap_peek(&timeouts)->timestamp <= curr_time) { - timeout_t expired; - bool ret = timer_heap_pop(&timeouts, &expired); - assert(ret); // This should never happen! Peek should catch empty queue - sddf_notify(expired.client_channel); + for (int i = 0; i < MAX_TIMEOUTS; i++) { + if (timeouts[i] <= curr_time) { + sddf_notify(i); + timeouts[i] = UINT64_MAX; + } } - timeout_t *next = timer_heap_peek(&timeouts); + uint64_t next_timeout = UINT64_MAX; + for (int i = 0; i < MAX_TIMEOUTS; i++) { + if (timeouts[i] < next_timeout) { + next_timeout = timeouts[i]; + } + } - // Reissue next timeout irq, if needed. - if (next != NULL) { - uint64_t next_delay = next->timestamp - curr_time; + if (next_timeout != UINT64_MAX) { regs->mux &= ~TIMER_A_MODE; - regs->timer_a = next_delay; + regs->timer_a = next_timeout - curr_time; regs->mux |= TIMER_A_EN; } - - // uint64_t next_timeout = UINT64_MAX; - // for (int i = 0; i < MAX_TIMEOUTS; i++) { - // if (timeouts[i] < next_timeout) { - // next_timeout = timeouts[i]; - // } - // } - - // if (next_timeout != UINT64_MAX) { - // regs->mux &= ~TIMER_A_MODE; - // regs->timer_a = next_timeout - curr_time; - // regs->mux |= TIMER_A_EN; - // } } void notified(sddf_channel ch) @@ -133,8 +121,7 @@ seL4_MessageInfo_t protected(sddf_channel ch, seL4_MessageInfo_t msginfo) case SDDF_TIMER_SET_TIMEOUT: { uint64_t curr_time = get_ticks(); uint64_t offset_us = sddf_get_mr(0) / NS_IN_US; - timer_heap_insert(&timeouts, curr_time + offset_us, ch); - + timeouts[ch] = curr_time + offset_us; process_timeouts(curr_time); break; } @@ -153,6 +140,10 @@ void init(void) assert(device_resources.num_irqs == 1); assert(device_resources.num_regions == 1); + for (int i = 0; i < MAX_TIMEOUTS; i++) { + timeouts[i] = UINT64_MAX; + } + regs = (void *)((uintptr_t)device_resources.regions[0].region.vaddr + TIMER_REG_START); /* Start timer E acts as a clock, while timer A can be used for timeouts from clients */ @@ -160,5 +151,4 @@ void init(void) | (TIMEOUT_TIMEBASE_1_US << TIMER_A_INPUT_CLK); regs->timer_e = 0; - timer_heap_init(&timeouts); } diff --git a/examples/i2c/meta.py b/examples/i2c/meta.py index ebb1d654f..8c139881b 100644 --- a/examples/i2c/meta.py +++ b/examples/i2c/meta.py @@ -16,8 +16,50 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): sdf, "client_ds3231", "client_ds3231.elf", priority=1 ) - i2c = sDDFI2C( - sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=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) + client_pn532 = ProtectionDomain("client_pn532", "client_pn532.elf", priority=1) + client_ds3231 = ProtectionDomain("client_ds3231", "client_ds3231.elf", priority=1) + + # HACK: sdfgen doesn't support multiple regions for a device resource yet + # or the clk class. This will be removed in the pending sdfgen refactor. + regions = [] # tuples of (mr, map var name) + if board.name == "maaxboard": + # HACK: override DTS node to enable external I2C bus (I2C2). + # This should also be fixed in the sdfgen refactor + board.i2c = "soc@0/bus@30800000/i2c@30a30000" + + elif board.name == "odroidc4": + # Right now we do not have separate clk and GPIO drivers and so our I2C driver does manual + # clk/GPIO setup for I2C. + 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)) + else: + print("Unsupported board!") + exit(-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_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 ) i2c.add_client(client_ds3231) i2c.add_client(client_pn532) diff --git a/include/sddf/i2c/i2c_driver.h b/include/sddf/i2c/i2c_driver.h index 141b18e5e..8d60e6212 100644 --- a/include/sddf/i2c/i2c_driver.h +++ b/include/sddf/i2c/i2c_driver.h @@ -28,7 +28,7 @@ #pragma once -// #define DEBUG_I2C_DRIVER +#define DEBUG_I2C_DRIVER #ifdef DEBUG_I2C_DRIVER #define LOG_I2C_DRIVER(...) do{ sddf_dprintf("I2C DRIVER|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) #else From d43a147ab224eacea56f759dd25ffa75eff4062d Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Tue, 10 Mar 2026 12:34:17 +1100 Subject: [PATCH 16/23] Clean up build files and fix board.py Signed-off-by: Lesley Rossouw --- drivers/i2c/imx/i2c.c | 9 +++++++-- drivers/i2c/imx/i2c_driver.mk | 5 +---- include/sddf/i2c/i2c_driver.h | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/drivers/i2c/imx/i2c.c b/drivers/i2c/imx/i2c.c index 2fb4c7bd0..5581c3b39 100644 --- a/drivers/i2c/imx/i2c.c +++ b/drivers/i2c/imx/i2c.c @@ -104,8 +104,13 @@ static inline void imx_i2c_start(bool repeat) { if (!repeat) { regs->i2cr |= REG_CR_MSTA; // wait until bus is clear - while (regs->i2sr & REG_SR_IBB) { - LOG_I2C_DRIVER("AHHHHH AHHH OHH MY GOD AHHHH\n"); + for (uint32_t i = 0; (i < 10000) && (regs->i2sr & REG_SR_IBB); i++) { + } + + // if our short busy wait failed, die + if (regs->i2sr & REG_SR_IBB) { + LOG_I2C_DRIVER_ERR("Failed to claim bus on start! Is there another master?\n"); + assert(0); } // write other start flags diff --git a/drivers/i2c/imx/i2c_driver.mk b/drivers/i2c/imx/i2c_driver.mk index 113185e11..48aa4d606 100644 --- a/drivers/i2c/imx/i2c_driver.mk +++ b/drivers/i2c/imx/i2c_driver.mk @@ -9,16 +9,13 @@ # NOTES # Generates i2c_driver.elf # Requires libsddf_util_debug.a in ${LIBS} -# Has one parameter: I2C_BUS_NUM to select which bus is being driven I2C_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) -# default to internal bus, connected to pmc -I2C_BUS_NUM ?= 1 i2c_driver.elf: i2c/i2c_driver.o i2c/i2c_common.o $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ -i2c/i2c_driver.o: CFLAGS+=-I${I2C_DRIVER_DIR} -DI2C_BUS_NUM=${I2C_BUS_NUM} +i2c/i2c_driver.o: CFLAGS+=-I${I2C_DRIVER_DIR} i2c/i2c_driver.o: ${I2C_DRIVER_DIR}/i2c.c |i2c $(SDDF_LIBC_INCLUDE) ${CC} ${CFLAGS} -c -o $@ $< diff --git a/include/sddf/i2c/i2c_driver.h b/include/sddf/i2c/i2c_driver.h index 8d60e6212..141b18e5e 100644 --- a/include/sddf/i2c/i2c_driver.h +++ b/include/sddf/i2c/i2c_driver.h @@ -28,7 +28,7 @@ #pragma once -#define DEBUG_I2C_DRIVER +// #define DEBUG_I2C_DRIVER #ifdef DEBUG_I2C_DRIVER #define LOG_I2C_DRIVER(...) do{ sddf_dprintf("I2C DRIVER|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) #else From dcc0b5a8252b686b8141d9e726f639fd8864f712 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Tue, 10 Mar 2026 12:15:59 +1100 Subject: [PATCH 17/23] Added no-coroutine build mode for libi2c Signed-off-by: Lesley Rossouw --- i2c/libi2c.c | 4 ++++ include/sddf/i2c/libi2c.h | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/i2c/libi2c.c b/i2c/libi2c.c index f6713c847..4d61b0a62 100644 --- a/i2c/libi2c.c +++ b/i2c/libi2c.c @@ -33,6 +33,7 @@ static inline int check_data_buf(void *data_buf) return 0; } +#ifndef LIBI2C_NOCO /** * Block on a notification using libco or libmicrokitco for blocking calls. */ @@ -48,6 +49,7 @@ static void __i2c_block(libi2c_conf_t *conf) microkit_cothread_wait_on_channel(i2c_config.virt.id); #endif } +#endif /** * Given a buffer pointer from the DATA region, create an I2C op, dispatch and return when @@ -130,6 +132,7 @@ static i2c_err_t __i2c_handle_response(libi2c_conf_t *conf, i2c_addr_t *returned // #### Blocking API using libco/libmicrokitco #### +#ifndef LIBI2C_NOCO /** * Perform a simple I2C write given a DATA region buffer containing data. * To perform a write to a device register, ensure the FIRST byte of write_buf contains @@ -215,6 +218,7 @@ int sddf_i2c_dispatch(libi2c_conf_t *conf, i2c_addr_t address, void *buf, uint16 assert(returned_addr == address); return err; } +#endif // #### Non-blocking API #### /** diff --git a/include/sddf/i2c/libi2c.h b/include/sddf/i2c/libi2c.h index 9baccb0eb..6bc054c8a 100644 --- a/include/sddf/i2c/libi2c.h +++ b/include/sddf/i2c/libi2c.h @@ -12,6 +12,11 @@ // necessary in most cases! If your usage requires more commands per request, do not use // this library and instead implement direct calls to the protocol in // +// Three operating modes are supplied based on coroutine support: +// a. libmicrokitco: use libmicrokitco.h for blocking API +// b. libi2c_raw: use raw libco for blocking API +// c. libi2c_noco: blocking API fully disabled for use in drivers +// // See i2c/queue.h for details about the I2C transport layer. #pragma once @@ -19,6 +24,7 @@ #include #include #include +#ifndef LIBI2C_NOCO #ifdef LIBI2C_RAW #include // Client must define and set up these cothreads for this interface to function. @@ -27,6 +33,7 @@ extern cothread_t t_main; #else #include #endif +#endif // Client must define this. E.g. // __attribute__((__section__(".i2c_client_config"))) i2c_client_config_t i2c_config; @@ -56,11 +63,13 @@ typedef struct libi2c_conf { int libi2c_init(libi2c_conf_t *conf_struct, i2c_queue_handle_t *queue_handle); +#ifndef LIBI2C_NOCO // Blocking interface int sddf_i2c_write(libi2c_conf_t *conf, i2c_addr_t address, void *write_buf, uint16_t len); int sddf_i2c_read(libi2c_conf_t *conf, i2c_addr_t address, void *read_buf, uint16_t len); int sddf_i2c_writeread(libi2c_conf_t *conf, i2c_addr_t address, i2c_addr_t reg_address, void *read_buf, uint16_t len); int sddf_i2c_dispatch(libi2c_conf_t *conf, i2c_addr_t address, void *buf, uint16_t len, uint8_t flag_mask); +#endif // Non-blocking interface. Separates dispatch and completion for applications // with custom concurrency models instead of libco/libmicrokitco (e.g. micropython) From 2d89eda6a4582e3502c4d1b865dc35a13855cc94 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Tue, 10 Mar 2026 12:18:04 +1100 Subject: [PATCH 18/23] Added PMIC driver class and stub driver for Maaxboard. Currently supports only the subset of features needed for contract deliverable (DVFS) Signed-off-by: Lesley Rossouw --- drivers/pmic/bd71837amwv/bd71837.c | 258 ++++++++++++++ drivers/pmic/bd71837amwv/bd71837.h | 427 +++++++++++++++++++++++ drivers/pmic/bd71837amwv/pmic_driver.mk | 32 ++ examples/pmic/Makefile | 31 ++ examples/pmic/client.c | 110 ++++++ examples/pmic/meta.py | 101 ++++++ examples/pmic/pmic.mk | 110 ++++++ include/sddf/pmic/bd71837amwv-bindings.h | 46 +++ include/sddf/pmic/client.h | 114 ++++++ include/sddf/pmic/driver.h | 97 +++++ include/sddf/pmic/protocol.h | 121 +++++++ tools/make/board/maaxboard.mk | 1 + 12 files changed, 1448 insertions(+) create mode 100644 drivers/pmic/bd71837amwv/bd71837.c create mode 100644 drivers/pmic/bd71837amwv/bd71837.h create mode 100644 drivers/pmic/bd71837amwv/pmic_driver.mk create mode 100644 examples/pmic/Makefile create mode 100644 examples/pmic/client.c create mode 100644 examples/pmic/meta.py create mode 100644 examples/pmic/pmic.mk create mode 100644 include/sddf/pmic/bd71837amwv-bindings.h create mode 100644 include/sddf/pmic/client.h create mode 100644 include/sddf/pmic/driver.h create mode 100644 include/sddf/pmic/protocol.h diff --git a/drivers/pmic/bd71837amwv/bd71837.c b/drivers/pmic/bd71837amwv/bd71837.c new file mode 100644 index 000000000..3aabd3bae --- /dev/null +++ b/drivers/pmic/bd71837amwv/bd71837.c @@ -0,0 +1,258 @@ +/* + * Copyright 2026, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include "bd71837.h" + +__attribute__((__section__(".i2c_client_config"))) i2c_client_config_t i2c_config; + +libi2c_conf_t libi2c_conf; +i2c_queue_handle_t queue; +uint8_t *i2c_data_region; + +// TODO: add support for sdfgen to pass in PMIC i2c address +#define PMIC_I2C_ADDR (0x4b) + +#ifndef I2C_DATA_REGION +#define I2C_DATA_REGION ((uint8_t *)i2c_config.data.vaddr) +#endif + +// HACK: this driver skips around a limitation in microkit ... we return a success for ops +// before sending an i2c transaction as we cannot wait for a notification in the middle of +// protected(). This means that if the i2c transaction fails, the client will not know! +// There is no way to fix this other than hacking the microkit event loop locally, which isn't +// suitable for a driver. We will handle this in the future by changing microkit. + +pmic_driver_state_t state; + +static inline sddf_pmic_err_t pmic_not_implemented() { + LOG_PMIC_DRIVER_ERR("This PPC not implemented for this platform!\n"); + return SDDF_PMIC_ERR_NOT_IMPLEMENTED; +} + +sddf_pmic_err_t pmic_drv_enable_reg(uint64_t reg_id) { + return pmic_not_implemented(); +} + +sddf_pmic_err_t pmic_drv_disable_reg(uint64_t reg_id) { + return pmic_not_implemented(); +} + +sddf_pmic_err_t pmic_drv_set_vout(uint64_t reg_id, uint64_t voltage_uv) { + // Look up regulator info + bd71837_reg_t *regulator = &bd71837_regulator_table[reg_id]; + + // sanity checks + if (!regulator->reg.enabled) { + LOG_PMIC_DRIVER_ERR("Cannot set voltage for a disabled regulator\n"); + return SDDF_PMIC_ERR_FAIL_REG; + } + if (!regulator->reg.capabilities.voltage.adjustable) { + LOG_PMIC_DRIVER_ERR("Tried to set voltage for incompatible regulator %zu!\n", reg_id); + return SDDF_PMIC_ERR_BAD_SETTING; + } + + // check new voltage is valid + pmic_unit_t max = regulator->reg.capabilities.voltage.max_value; + pmic_unit_t min = regulator->reg.capabilities.voltage.min_value; + if (voltage_uv >= max || voltage_uv <= min) { + LOG_PMIC_DRIVER_ERR("Incompatible voltage requested!\n"); + return SDDF_PMIC_ERR_BAD_SETTING; + } + // prepare regulator register write + // most registers simply require us to write to bits [quantisation:0], but some + // are special cases + uint8_t i2c_reg_val; + if (reg_id == BD718XX_BUCK5) { + return pmic_not_implemented(); + } else if (reg_id == BD718XX_LDO1) { + return pmic_not_implemented(); + } else if (reg_id == BD718XX_LDO2) { + return pmic_not_implemented(); + } else { + // all other regulators + uint64_t uv_range = max - min; + uint64_t uv_per_step = uv_range / (1 << regulator->reg.capabilities.voltage.quantisation); + + uint64_t target_val = (voltage_uv - min) / uv_per_step; + + // If our target can't fit in the register, we've screwed up. + LOG_PMIC_DRIVER("Setting voltage to %zu - target value = %zu\n", voltage_uv, target_val); + assert(target_val <= (1 << regulator->reg.capabilities.voltage.quantisation)); + + i2c_reg_val = (uint8_t) (target_val & 0xff); + } + + // prepare i2c transaction + i2c_data_region[0] = regulator->i2c_reg_addr; + i2c_data_region[1] = i2c_reg_val; + sddf_i2c_nb_write(&libi2c_conf, PMIC_I2C_ADDR, i2c_data_region, 2); + // We set the state machine up in protected() + return SDDF_PMIC_ERR_OK; +} + +sddf_pmic_err_t pmic_drv_set_climit(uint64_t reg_id, uint64_t current_ua) { + return pmic_not_implemented(); +} + +sddf_pmic_err_t pmic_drv_get_info(uint64_t reg_id, sddf_pmic_reg_info_t *info) { + return pmic_not_implemented(); +} + + +void init(void) +{ + // Setup i2c client connection + assert(i2c_config_check_magic((void *)&i2c_config)); + i2c_data_region = (uint8_t *)i2c_config.data.vaddr; + queue = i2c_queue_init(i2c_config.virt.req_queue.vaddr, i2c_config.virt.resp_queue.vaddr); + + bool claimed = i2c_bus_claim(i2c_config.virt.id, PMIC_I2C_ADDR); + if (!claimed) { + LOG_PMIC_DRIVER_ERR("Failed to claim PMIC bus address! Dying now!\n"); + assert(false); + } + + /* Initialise libi2c */ + libi2c_init(&libi2c_conf, &queue); + + pmic_reset_state(&state); + LOG_PMIC_DRIVER("Initialised.\n"); +} + +void notified(microkit_channel ch) +{ + if (ch == i2c_config.virt.id) { + if (state.curr_ppc_op == SDDF_PMIC_PPC_INVALID) { + LOG_PMIC_DRIVER_ERR("Spurious I2C interrupt!\n"); + return; + } + // Simply report if we failed to complete. + i2c_addr_t returned_addr; + size_t err_cmd_idx = 0; + int ret = sddf_i2c_nb_return(&libi2c_conf, &returned_addr, &err_cmd_idx); + + if (ret == I2C_ERR_OK) { + LOG_PMIC_DRIVER("Completed request for client %u. Opcode = %zu\n", state.curr_client, state.curr_ppc_op); + } else { + LOG_PMIC_DRIVER_ERR("I2C request failed for client %u! Opcode = %zu\n", state.curr_client, state.curr_ppc_op); + } + + // Clean up state for next request. + pmic_reset_state(&state); + } else { + LOG_PMIC_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); + // Due to our HACK to support I2C, there exists a race condition on repeated calls to + // this driver. If we're currently waiting for an I2C response, simply reject any PPC. + if (state.curr_ppc_op != SDDF_PMIC_PPC_INVALID) { + LOG_PMIC_DRIVER_ERR("Rejecting incoming PPC due to outstanding request! Try again soon...\n"); + err = SDDF_PMIC_ERR_BUSY; + return microkit_msginfo_new(err, 1); + } + switch (microkit_msginfo_get_label(msginfo)) { + case SDDF_PMIC_ENABLE_REG: { + if (argc != 1) { + LOG_PMIC_DRIVER_ERR("Incorrect number of arguments %u != 1\n", argc); + err = SDDF_PMIC_ERR_BAD_PPC_CALL; + break; + } + uint32_t reg_id = (uint32_t)microkit_mr_get(SDDF_PMIC_ENABLE_REG_REG_ID); + LOG_PMIC_DRIVER("get request pmic_enable_reg(%d)\n", reg_id); + state.err = pmic_drv_enable_reg(reg_id); + break; + } + case SDDF_PMIC_DISABLE_REG: { + if (argc != 1) { + LOG_PMIC_DRIVER_ERR("Incorrect number of arguments %u != 1\n", argc); + err = SDDF_PMIC_ERR_BAD_PPC_CALL; + break; + } + uint32_t reg_id = (uint32_t)microkit_mr_get(SDDF_PMIC_DISABLE_REG_REG_ID); + LOG_PMIC_DRIVER("get request pmic_disable_reg(%d)\n", reg_id); + state.err = pmic_drv_disable_reg(reg_id); + break; + } + case SDDF_PMIC_SET_VOUT: { + if (argc != 3) { + LOG_PMIC_DRIVER_ERR("Incorrect number of arguments %u != 3\n", argc); + err = SDDF_PMIC_ERR_BAD_PPC_CALL; + break; + } + uint32_t reg_id = (uint32_t)microkit_mr_get(SDDF_PMIC_SET_VOUT_REG_ID); + uint64_t voltage_uv = microkit_mr_get(SDDF_PMIC_SET_VOUT_VOLTAGE_UV); + uint32_t op_mode_id = (uint32_t)microkit_mr_get(SDDF_PMIC_SET_VOUT_OP_MODE_ID); + LOG_PMIC_DRIVER("get request pmic_set_vout(%d, %lu, %d)\n", reg_id, voltage_uv, op_mode_id); + state.err = pmic_drv_set_vout(reg_id, voltage_uv); + break; + } + case SDDF_PMIC_SET_CLIMIT: { + if (argc != 3) { + LOG_PMIC_DRIVER_ERR("Incorrect number of arguments %u != 3\n", argc); + err = SDDF_PMIC_ERR_BAD_PPC_CALL; + break; + } + uint32_t reg_id = (uint32_t)microkit_mr_get(SDDF_PMIC_SET_CLIMIT_REG_ID); + uint64_t current_ua = microkit_mr_get(SDDF_PMIC_SET_CLIMIT_CURRENT_UA); + uint32_t op_mode_id = (uint32_t)microkit_mr_get(SDDF_PMIC_SET_CLIMIT_OP_MODE_ID); + LOG_PMIC_DRIVER("get request pmic_set_climit(%d, %lu, %d)\n", reg_id, current_ua, op_mode_id); + state.err = pmic_drv_set_climit(reg_id, current_ua); + break; + } + case SDDF_PMIC_GET_REG_INFO: { + if (argc != 1) { + LOG_PMIC_DRIVER_ERR("Incorrect number of arguments %u != 1\n", argc); + err = SDDF_PMIC_ERR_BAD_PPC_CALL; + break; + } + uint32_t reg_id = (uint32_t)microkit_mr_get(SDDF_PMIC_GET_REG_INFO_REG_ID); + sddf_pmic_reg_info_t info = {0}; + LOG_PMIC_DRIVER("get request pmic_get_reg_info(%d)\n", reg_id); + state.err = pmic_drv_get_info(reg_id, &info); + if (err == SDDF_PMIC_GET_REG_INFO_SUCCESS) { + microkit_mr_set(SDDF_PMIC_GET_REG_INFO_ENABLED, info.enabled); + microkit_mr_set(SDDF_PMIC_GET_REG_INFO_VOLTAGE_UV, info.voltage_uv); + microkit_mr_set(SDDF_PMIC_GET_REG_INFO_CURRENT_UA, info.current_ua); + microkit_mr_set(SDDF_PMIC_GET_REG_INFO_MIN_VOLTAGE_UV, info.min_voltage_uv); + microkit_mr_set(SDDF_PMIC_GET_REG_INFO_MAX_VOLTAGE_UV, info.max_voltage_uv); + microkit_mr_set(SDDF_PMIC_GET_REG_INFO_MIN_CURRENT_UA, info.min_current_ua); + microkit_mr_set(SDDF_PMIC_GET_REG_INFO_MAX_CURRENT_UA, info.max_current_ua); + microkit_mr_set(SDDF_PMIC_GET_REG_INFO_RAMPRATE, info.ramprate); + ret_num = 9; /* MR0-MR8 */ + } + break; + } + default: + LOG_PMIC_DRIVER_ERR("Unknown request %lu to PMIC driver from channel %u\n", + microkit_msginfo_get_label(msginfo), ch); + err = SDDF_PMIC_ERR_BAD_PPC_CALL; + } + if (state.err != SDDF_PMIC_ERR_OK) { + // Give up and die if we failed to set up a valid op + err = state.err; + } else { + // We didn't explode, proceed to sleep for i2c operation. + // Per the HACK label earlier, we pre-emptively return a success + // to clients here! + LOG_PMIC_DRIVER("Preparing to sleep for i2c return...\n"); + } + + return microkit_msginfo_new(err, ret_num); +} + diff --git a/drivers/pmic/bd71837amwv/bd71837.h b/drivers/pmic/bd71837amwv/bd71837.h new file mode 100644 index 000000000..135ab229c --- /dev/null +++ b/drivers/pmic/bd71837amwv/bd71837.h @@ -0,0 +1,427 @@ +/* + * Copyright 2026, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once +#include + +typedef struct bd71837_reg { + const char *name; + sddf_regulator_t reg; + uint8_t i2c_reg_addr; +} bd71837_reg_t; + + +// IMPORTANT: this driver currently ONLY supports setting "run" mode parameters. +// We don't support setting idle or suspend mode parameters as these features +// cannot be harmoniously touched in LionsOS at the time of writing. +// TODO: sanity check the below, i am rushing and tired +#define BD71837_NUM_REGULATORS (15) +static bd71837_reg_t bd71837_regulator_table[BD71837_NUM_REGULATORS] = { + /* BUCK1: VDD_SOC, 3.6A, 0.7-1.3V @ 10mV step (6-bit DVS) */ + { + .name = "BUCK1", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 700000, .max_value = 1300000, .quantisation = 6 }, + .current = { .adjustable = false, .min_value = 3600000, .max_value = 3600000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 3600000, + .enabled = true, + }, + .i2c_reg_addr = 0x0D, /* BUCK1_VOLT_RUN */ + }, + /* BUCK2: VDD_ARM, 4.0A, 0.7-1.3V @ 10mV step (6-bit DVS) */ + { + .name = "BUCK2", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 700000, .max_value = 1300000, .quantisation = 6 }, + .current = { .adjustable = false, .min_value = 4000000, .max_value = 4000000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 4000000, + .enabled = true, + }, + .i2c_reg_addr = 0x10, /* BUCK2_VOLT_RUN */ + }, + /* BUCK3: VDD_GPU, 2.1A, 0.7-1.3V @ 10mV step (6-bit DVS) */ + { + .name = "BUCK3", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 700000, .max_value = 1300000, .quantisation = 6 }, + .current = { .adjustable = false, .min_value = 2100000, .max_value = 2100000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 2100000, + .enabled = true, + }, + .i2c_reg_addr = 0x12, /* BUCK3_VOLT_RUN */ + }, + /* BUCK4: VDD_VPU, 1.0A, 0.7-1.3V @ 10mV step (6-bit DVS) */ + { + .name = "BUCK4", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 700000, .max_value = 1300000, .quantisation = 6 }, + .current = { .adjustable = false, .min_value = 1000000, .max_value = 1000000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 1000000, + .enabled = true, + }, + .i2c_reg_addr = 0x13, /* BUCK4_VOLT_RUN */ + }, + /* BUCK5: VDD_DRAM, 2.5A, 0.7-1.35V 8-step (3-bit, non-linear) */ + { + .name = "BUCK5", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 700000, .max_value = 1350000, .quantisation = 3 }, + .current = { .adjustable = false, .min_value = 2500000, .max_value = 2500000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 2500000, + .enabled = true, + }, + .i2c_reg_addr = 0x14, /* BUCK5_VOLT */ + }, + /* BUCK6: NVCC_3P3, 3.0A, 3.0-3.3V @ 100mV step (2-bit, 4 values) */ + { + .name = "BUCK6", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 3000000, .max_value = 3300000, .quantisation = 2 }, + .current = { .adjustable = false, .min_value = 3000000, .max_value = 3000000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 3000000, + .enabled = true, + }, + .i2c_reg_addr = 0x15, /* BUCK6_VOLT */ + }, + /* BUCK7: NVCC_1P8, 1.5A, 1.605-1.995V 8-step (3-bit, discrete steps) */ + { + .name = "BUCK7", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 1605000, .max_value = 1995000, .quantisation = 3 }, + .current = { .adjustable = false, .min_value = 1500000, .max_value = 1500000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 1500000, + .enabled = true, + }, + .i2c_reg_addr = 0x16, /* BUCK7_VOLT */ + }, + /* BUCK8: NVCC_DRAM, 3.0A, 0.8-1.4V @ 10mV step (6-bit) */ + { + .name = "BUCK8", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 800000, .max_value = 1400000, .quantisation = 6 }, + .current = { .adjustable = false, .min_value = 3000000, .max_value = 3000000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 3000000, + .enabled = true, + }, + .i2c_reg_addr = 0x17, /* BUCK8_VOLT */ + }, + /* LDO1: NVCC_SNVS, 10mA, 3.0-3.3V or 1.6-1.9V (2-bit + range sel) */ + { + .name = "LDO1", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 1600000, .max_value = 3300000, .quantisation = 2 }, + .current = { .adjustable = false, .min_value = 10000, .max_value = 10000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 10000, + .enabled = true, + }, + .i2c_reg_addr = 0x18, /* LDO1_VOLT */ + }, + /* LDO2: VDD_SNVS, 10mA, 0.8V or 0.9V (1-bit) */ + { + .name = "LDO2", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 800000, .max_value = 900000, .quantisation = 1 }, + .current = { .adjustable = false, .min_value = 10000, .max_value = 10000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 10000, + .enabled = true, + }, + .i2c_reg_addr = 0x19, /* LDO2_VOLT */ + }, + /* LDO3: VDDA_1P8/DRAM, 300mA, 1.8-3.3V @ 100mV step (4-bit) */ + { + .name = "LDO3", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 1800000, .max_value = 3300000, .quantisation = 4 }, + .current = { .adjustable = false, .min_value = 300000, .max_value = 300000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 300000, + .enabled = true, + }, + .i2c_reg_addr = 0x1A, /* LDO3_VOLT */ + }, + /* LDO4: VDDA_0P9, 250mA, 0.9-1.8V @ 100mV step (4-bit) */ + { + .name = "LDO4", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 900000, .max_value = 1800000, .quantisation = 4 }, + .current = { .adjustable = false, .min_value = 250000, .max_value = 250000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 250000, + .enabled = true, + }, + .i2c_reg_addr = 0x1B, /* LDO4_VOLT */ + }, + /* LDO5: PHY_1P8, 300mA, 1.8-3.3V @ 100mV step (4-bit) */ + { + .name = "LDO5", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 1800000, .max_value = 3300000, .quantisation = 4 }, + .current = { .adjustable = false, .min_value = 300000, .max_value = 300000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 300000, + .enabled = true, + }, + .i2c_reg_addr = 0x1C, /* LDO5_VOLT */ + }, + /* LDO6: PHY_0P9, 300mA, 0.9-1.8V @ 100mV step (4-bit) */ + { + .name = "LDO6", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 900000, .max_value = 1800000, .quantisation = 4 }, + .current = { .adjustable = false, .min_value = 300000, .max_value = 300000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 300000, + .enabled = true, + }, + .i2c_reg_addr = 0x1D, /* LDO6_VOLT */ + }, + /* LDO7: PHY_3P3, 150mA, 1.8-3.3V @ 100mV step (4-bit) */ + { + .name = "LDO7", + .reg = { + .capabilities = { + .voltage = { .adjustable = true, .min_value = 1800000, .max_value = 3300000, .quantisation = 4 }, + .current = { .adjustable = false, .min_value = 150000, .max_value = 150000, .quantisation = 0 }, + .toggleable = true, + }, + .voltage_uv = 0, + .current_ua = 150000, + .enabled = true, + }, + .i2c_reg_addr = 0x1E, /* LDO7_VOLT */ + }, +}; + +/* Registers specific to BD71837 */ +enum { + BD71837_REG_BUCK3_CTRL = 0x07, + BD71837_REG_BUCK4_CTRL = 0x08, + BD71837_REG_BUCK3_VOLT_RUN = 0x12, + BD71837_REG_BUCK4_VOLT_RUN = 0x13, + BD71837_REG_LDO7_VOLT = 0x1E, +}; + +/* Registers common for BD71837 and BD71847 */ +enum { + BD718XX_REG_REV = 0x00, + BD718XX_REG_SWRESET = 0x01, + BD718XX_REG_I2C_DEV = 0x02, + BD718XX_REG_PWRCTRL0 = 0x03, + BD718XX_REG_PWRCTRL1 = 0x04, + BD718XX_REG_BUCK1_CTRL = 0x05, + BD718XX_REG_BUCK2_CTRL = 0x06, + BD718XX_REG_1ST_NODVS_BUCK_CTRL = 0x09, + BD718XX_REG_2ND_NODVS_BUCK_CTRL = 0x0A, + BD718XX_REG_3RD_NODVS_BUCK_CTRL = 0x0B, + BD718XX_REG_4TH_NODVS_BUCK_CTRL = 0x0C, + BD718XX_REG_BUCK1_VOLT_RUN = 0x0D, + BD718XX_REG_BUCK1_VOLT_IDLE = 0x0E, + BD718XX_REG_BUCK1_VOLT_SUSP = 0x0F, + BD718XX_REG_BUCK2_VOLT_RUN = 0x10, + BD718XX_REG_BUCK2_VOLT_IDLE = 0x11, + BD718XX_REG_1ST_NODVS_BUCK_VOLT = 0x14, + BD718XX_REG_2ND_NODVS_BUCK_VOLT = 0x15, + BD718XX_REG_3RD_NODVS_BUCK_VOLT = 0x16, + BD718XX_REG_4TH_NODVS_BUCK_VOLT = 0x17, + BD718XX_REG_LDO1_VOLT = 0x18, + BD718XX_REG_LDO2_VOLT = 0x19, + BD718XX_REG_LDO3_VOLT = 0x1A, + BD718XX_REG_LDO4_VOLT = 0x1B, + BD718XX_REG_LDO5_VOLT = 0x1C, + BD718XX_REG_LDO6_VOLT = 0x1D, + BD718XX_REG_TRANS_COND0 = 0x1F, + BD718XX_REG_TRANS_COND1 = 0x20, + BD718XX_REG_VRFAULTEN = 0x21, + BD718XX_REG_MVRFLTMASK0 = 0x22, + BD718XX_REG_MVRFLTMASK1 = 0x23, + BD718XX_REG_MVRFLTMASK2 = 0x24, + BD718XX_REG_RCVCFG = 0x25, + BD718XX_REG_RCVNUM = 0x26, + BD718XX_REG_PWRONCONFIG0 = 0x27, + BD718XX_REG_PWRONCONFIG1 = 0x28, + BD718XX_REG_RESETSRC = 0x29, + BD718XX_REG_MIRQ = 0x2A, + BD718XX_REG_IRQ = 0x2B, + BD718XX_REG_IN_MON = 0x2C, + BD718XX_REG_POW_STATE = 0x2D, + BD718XX_REG_OUT32K = 0x2E, + BD718XX_REG_REGLOCK = 0x2F, + BD718XX_REG_OTPVER = 0xFF, + BD718XX_MAX_REGISTER = 0x100, +}; + +#define REGLOCK_PWRSEQ 0x1 +#define REGLOCK_VREG 0x10 + +/* Generic BUCK control masks */ +#define BD718XX_BUCK_SEL 0x02 +#define BD718XX_BUCK_EN 0x01 +#define BD718XX_BUCK_RUN_ON 0x04 + +/* Generic LDO masks */ +#define BD718XX_LDO_SEL 0x80 +#define BD718XX_LDO_EN 0x40 + +/* BD71837 BUCK ramp rate CTRL reg bits */ +#define BUCK_RAMPRATE_MASK 0xC0 +#define BUCK_RAMPRATE_10P00MV 0x0 +#define BUCK_RAMPRATE_5P00MV 0x1 +#define BUCK_RAMPRATE_2P50MV 0x2 +#define BUCK_RAMPRATE_1P25MV 0x3 + +#define DVS_BUCK_RUN_MASK 0x3F +#define DVS_BUCK_SUSP_MASK 0x3F +#define DVS_BUCK_IDLE_MASK 0x3F + +#define BD718XX_1ST_NODVS_BUCK_MASK 0x07 +#define BD718XX_3RD_NODVS_BUCK_MASK 0x07 +#define BD718XX_4TH_NODVS_BUCK_MASK 0x3F + +#define BD71847_BUCK3_MASK 0x07 +#define BD71847_BUCK3_RANGE_MASK 0xC0 +#define BD71847_BUCK4_MASK 0x03 +#define BD71847_BUCK4_RANGE_MASK 0x40 + +#define BD71837_BUCK5_MASK 0x07 +#define BD71837_BUCK5_RANGE_MASK 0x80 +#define BD71837_BUCK6_MASK 0x03 + +#define BD718XX_LDO1_MASK 0x03 +#define BD718XX_LDO1_RANGE_MASK 0x20 +#define BD718XX_LDO2_MASK 0x20 +#define BD718XX_LDO3_MASK 0x0F +#define BD718XX_LDO4_MASK 0x0F +#define BD718XX_LDO6_MASK 0x0F + +#define BD71837_LDO5_MASK 0x0F +#define BD71847_LDO5_MASK 0x0F +#define BD71847_LDO5_RANGE_MASK 0x20 + +#define BD71837_LDO7_MASK 0x0F + +/* BD718XX Voltage monitoring masks */ +#define BD718XX_BUCK1_VRMON80 0x1 +#define BD718XX_BUCK1_VRMON130 0x2 +#define BD718XX_BUCK2_VRMON80 0x4 +#define BD718XX_BUCK2_VRMON130 0x8 +#define BD718XX_1ST_NODVS_BUCK_VRMON80 0x1 +#define BD718XX_1ST_NODVS_BUCK_VRMON130 0x2 +#define BD718XX_2ND_NODVS_BUCK_VRMON80 0x4 +#define BD718XX_2ND_NODVS_BUCK_VRMON130 0x8 +#define BD718XX_3RD_NODVS_BUCK_VRMON80 0x10 +#define BD718XX_3RD_NODVS_BUCK_VRMON130 0x20 +#define BD718XX_4TH_NODVS_BUCK_VRMON80 0x40 +#define BD718XX_4TH_NODVS_BUCK_VRMON130 0x80 +#define BD718XX_LDO1_VRMON80 0x1 +#define BD718XX_LDO2_VRMON80 0x2 +#define BD718XX_LDO3_VRMON80 0x4 +#define BD718XX_LDO4_VRMON80 0x8 +#define BD718XX_LDO5_VRMON80 0x10 +#define BD718XX_LDO6_VRMON80 0x20 + +/* BD71837 specific voltage monitoring masks */ +#define BD71837_BUCK3_VRMON80 0x10 +#define BD71837_BUCK3_VRMON130 0x20 +#define BD71837_BUCK4_VRMON80 0x40 +#define BD71837_BUCK4_VRMON130 0x80 +#define BD71837_LDO7_VRMON80 0x40 + +/* BD718XX_REG_IRQ bits */ +#define IRQ_SWRST 0x40 +#define IRQ_PWRON_S 0x20 +#define IRQ_PWRON_L 0x10 +#define IRQ_PWRON 0x08 +#define IRQ_WDOG 0x04 +#define IRQ_ON_REQ 0x02 +#define IRQ_STBY_REQ 0x01 + +/* ROHM BD718XX irqs */ +enum { + BD718XX_INT_STBY_REQ, + BD718XX_INT_ON_REQ, + BD718XX_INT_WDOG, + BD718XX_INT_PWRBTN, + BD718XX_INT_PWRBTN_L, + BD718XX_INT_PWRBTN_S, + BD718XX_INT_SWRST +}; + +/* ROHM BD718XX interrupt masks */ +#define BD718XX_INT_SWRST_MASK 0x40 +#define BD718XX_INT_PWRBTN_S_MASK 0x20 +#define BD718XX_INT_PWRBTN_L_MASK 0x10 +#define BD718XX_INT_PWRBTN_MASK 0x8 +#define BD718XX_INT_WDOG_MASK 0x4 +#define BD718XX_INT_ON_REQ_MASK 0x2 +#define BD718XX_INT_STBY_REQ_MASK 0x1 + +/* Register write induced reset settings */ + +/* + * Even though the bit zero is not SWRESET type we still want to write zero + * to it when changing type. Bit zero is 'SWRESET' trigger bit and if we + * write 1 to it we will trigger the action. So always write 0 to it when + * changning SWRESET action - no matter what we read from it. + */ +#define BD718XX_SWRESET_TYPE_MASK 7 +#define BD718XX_SWRESET_TYPE_DISABLED 0 +#define BD718XX_SWRESET_TYPE_COLD 4 +#define BD718XX_SWRESET_TYPE_WARM 6 + +#define BD718XX_SWRESET_RESET_MASK 1 +#define BD718XX_SWRESET_RESET 1 + diff --git a/drivers/pmic/bd71837amwv/pmic_driver.mk b/drivers/pmic/bd71837amwv/pmic_driver.mk new file mode 100644 index 000000000..24d9eec42 --- /dev/null +++ b/drivers/pmic/bd71837amwv/pmic_driver.mk @@ -0,0 +1,32 @@ +# +# Copyright 2026, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Include this snippet in your project Makefile to build +# the Meson pmic driver +# +# NOTES +# Generates pmic_driver.elf +# Requires libsddf_util_debug.a in ${LIBS} +# Requires libi2c.a + +PMIC_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +pmic_driver.elf: pmic/pmic_driver.o libi2c.a + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +pmic/pmic_driver.o: CFLAGS+=-I${PMIC_DRIVER_DIR} +pmic/pmic_driver.o: ${PMIC_DRIVER_DIR}/bd71837.c |pmic $(SDDF_LIBC_INCLUDE) + ${CC} ${CFLAGS} -c -o $@ $< + +pmic: + mkdir -p $@ + +clean:: + rm -rf pmic + +clobber:: + rm -f pmic_driver.elf + +-include pmic_driver.d diff --git a/examples/pmic/Makefile b/examples/pmic/Makefile new file mode 100644 index 000000000..19369ba43 --- /dev/null +++ b/examples/pmic/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/pmic/client.c b/examples/pmic/client.c new file mode 100644 index 000000000..e51b76879 --- /dev/null +++ b/examples/pmic/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/pmic/meta.py b/examples/pmic/meta.py new file mode 100644 index 000000000..0f04cd3ef --- /dev/null +++ b/examples/pmic/meta.py @@ -0,0 +1,101 @@ +# 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) + i2c_driver = ProtectionDomain("i2c_driver", "i2c_driver.elf", priority=6) + i2c_virt = ProtectionDomain("i2c_virt", "i2c_virt.elf", priority=5) + pmic_driver = ProtectionDomain("pmic_driver", "pmic_driver.elf", priority=4) + client = ProtectionDomain("client", "client.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 + # TODO: add sdfgen support for passing in i2c addresses to pmic + + i2c_system = Sddf.I2c(sdf, i2c_node, i2c_driver, i2c_virt) + i2c_system.add_client(pmic_driver) + + 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 PMIC client + # TODO: sdfgen for this + pmic_channel = Channel(pmic_driver, client, pp_b=True) + sdf.add_channel(pmic_channel) + + pds = [ + serial_driver, + serial_virt_tx, + timer_driver, + i2c_driver, + pmic_driver, + i2c_virt, + client, + ] + 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()) + + +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/pmic/pmic.mk b/examples/pmic/pmic.mk new file mode 100644 index 000000000..80165d766 --- /dev/null +++ b/examples/pmic/pmic.mk @@ -0,0 +1,110 @@ +# +# 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/pmic +I2C := $(SDDF)/i2c +SERIAL := $(SDDF)/serial +I2C_DRIVER := $(SDDF)/drivers/i2c/${I2C_DRIV_DIR} +TIMER_DRIVER := $(SDDF)/drivers/timer/${TIMER_DRIV_DIR} +SERIAL_DRIVER := $(SDDF)/drivers/serial/${UART_DRIV_DIR} +PMIC_DRIVER := $(SDDF)/drivers/pmic/${PMIC_DRIV_DIR} + +IMAGES := i2c_virt.elf \ + i2c_driver.elf \ + client.elf \ + timer_driver.elf \ + serial_driver.elf \ + serial_virt_tx.elf \ + pmic_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 = pmic.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) \ + -DLIBI2C_NOCO \ + -MD \ + -MP + +CLIENT_OBJS := client.o + +VPATH := ${TOP} +all: $(IMAGE_FILE) + +client.o: client.c + +client.elf: $(CLIENT_OBJS) libco.a libsddf_util.a libi2c.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 .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_pmic_driver.data pmic_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 ${I2C}/components/i2c_virt.mk +include ${SERIAL}/components/serial_components.mk +include ${SERIAL_DRIVER}/serial_driver.mk +include ${TIMER_DRIVER}/timer_driver.mk +include ${LIBCO}/libco.mk +include ${I2C_DRIVER}/i2c_driver.mk +include ${PMIC_DRIVER}/pmic_driver.mk +include ${I2C}/libi2c.mk diff --git a/include/sddf/pmic/bd71837amwv-bindings.h b/include/sddf/pmic/bd71837amwv-bindings.h new file mode 100644 index 000000000..a75cc693e --- /dev/null +++ b/include/sddf/pmic/bd71837amwv-bindings.h @@ -0,0 +1,46 @@ +/* + * Copyright 2026, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2018 ROHM Semiconductors */ +// (for snippets derived from rohm-bd718x7.h in Linux kernel) + +typedef enum { + BD718XX_BUCK1 = 0, + BD718XX_BUCK2, + BD718XX_BUCK3, + BD718XX_BUCK4, + BD718XX_BUCK5, + BD718XX_BUCK6, + BD718XX_BUCK7, + BD718XX_BUCK8, + BD718XX_LDO1, + BD718XX_LDO2, + BD718XX_LDO3, + BD718XX_LDO4, + BD718XX_LDO5, + BD718XX_LDO6, + BD718XX_LDO7, + BD718XX_REGULATOR_AMOUNT, +} bd718xx_regulators; + +/* Common voltage configurations */ +#define BD718XX_DVS_BUCK_VOLTAGE_NUM 0x3D +#define BD718XX_4TH_NODVS_BUCK_VOLTAGE_NUM 0x3D + +#define BD718XX_LDO1_VOLTAGE_NUM 0x08 +#define BD718XX_LDO2_VOLTAGE_NUM 0x02 +#define BD718XX_LDO3_VOLTAGE_NUM 0x10 +#define BD718XX_LDO4_VOLTAGE_NUM 0x0A +#define BD718XX_LDO6_VOLTAGE_NUM 0x0A + +/* BD71837 specific voltage configurations */ +#define BD71837_BUCK5_VOLTAGE_NUM 0x10 +#define BD71837_BUCK6_VOLTAGE_NUM 0x04 +#define BD71837_BUCK7_VOLTAGE_NUM 0x08 +#define BD71837_LDO5_VOLTAGE_NUM 0x10 +#define BD71837_LDO7_VOLTAGE_NUM 0x10 + + diff --git a/include/sddf/pmic/client.h b/include/sddf/pmic/client.h new file mode 100644 index 000000000..d5ae48aac --- /dev/null +++ b/include/sddf/pmic/client.h @@ -0,0 +1,114 @@ +/* + * Copyright 2026, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +/** + * Enable a given regulator via PPC to the passive PMIC driver. + * @param channel of PMIC driver. + * @param reg_id identifier of target regulator. + * @return 0 on success, nonzero on failure. + */ +static inline int sddf_pmic_enable_reg(microkit_channel channel, uint32_t reg_id) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_PMIC_ENABLE_REG, 1); + microkit_mr_set(SDDF_PMIC_ENABLE_REG_REG_ID, reg_id); + + msginfo = microkit_ppcall(channel, msginfo); + + return (int)microkit_msginfo_get_label(msginfo); +} + +/** + * Disable a given regulator via PPC to the passive PMIC driver. + * @param channel of PMIC driver. + * @param reg_id identifier of target regulator. + * @return 0 on success, nonzero on failure. + */ +static inline int sddf_pmic_disable_reg(microkit_channel channel, uint32_t reg_id) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_PMIC_DISABLE_REG, 1); + microkit_mr_set(SDDF_PMIC_DISABLE_REG_REG_ID, reg_id); + + msginfo = microkit_ppcall(channel, msginfo); + + return (int)microkit_msginfo_get_label(msginfo); +} + +/** + * Set the voltage output level for a regulator via PPC to the passive PMIC driver. + * @param channel of PMIC driver. + * @param reg_id identifier of target regulator. + * @param voltage_uv target voltage in microvolts. + * @return 0 on success, 1 if regulator invalid, 2 if voltage setting invalid. + */ +static inline int sddf_pmic_set_vout(microkit_channel channel, uint32_t reg_id, + uint64_t voltage_uv) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_PMIC_SET_VOUT, 3); + microkit_mr_set(SDDF_PMIC_SET_VOUT_REG_ID, reg_id); + microkit_mr_set(SDDF_PMIC_SET_VOUT_VOLTAGE_UV, voltage_uv); + // microkit_mr_set(SDDF_PMIC_SET_VOUT_OP_MODE_ID, op_mode_id); + + msginfo = microkit_ppcall(channel, msginfo); + + return (int)microkit_msginfo_get_label(msginfo); +} + +/** + * Set the current limit for a regulator via PPC to the passive PMIC driver. + * @param channel of PMIC driver. + * @param reg_id identifier of target regulator. + * @param current_ua target current limit in microamps. + * @return 0 on success, 1 if regulator invalid, 2 if current setting invalid. + */ +static inline int sddf_pmic_set_climit(microkit_channel channel, uint32_t reg_id, + uint64_t current_ua) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_PMIC_SET_CLIMIT, 3); + microkit_mr_set(SDDF_PMIC_SET_CLIMIT_REG_ID, reg_id); + microkit_mr_set(SDDF_PMIC_SET_CLIMIT_CURRENT_UA, current_ua); + // microkit_mr_set(SDDF_PMIC_SET_CLIMIT_OP_MODE_ID, op_mode_id); + + msginfo = microkit_ppcall(channel, msginfo); + + return (int)microkit_msginfo_get_label(msginfo); +} + +/** + * Get information about a regulator via PPC to the passive PMIC driver. + * @param channel of PMIC driver. + * @param reg_id identifier of target regulator. + * @param info pointer to structure to populate with regulator information. + * @return 0 on success, 1 if regulator invalid. + */ +static inline int sddf_pmic_get_reg_info(microkit_channel channel, uint32_t reg_id, + sddf_pmic_reg_info_t *info) +{ + microkit_msginfo msginfo = microkit_msginfo_new(SDDF_PMIC_GET_REG_INFO, 1); + microkit_mr_set(SDDF_PMIC_GET_REG_INFO_REG_ID, reg_id); + + msginfo = microkit_ppcall(channel, msginfo); + + int ret = (int)microkit_msginfo_get_label(msginfo); + if (ret == SDDF_PMIC_GET_REG_INFO_SUCCESS) { + info->enabled = microkit_mr_get(SDDF_PMIC_GET_REG_INFO_ENABLED); + info->voltage_uv = microkit_mr_get(SDDF_PMIC_GET_REG_INFO_VOLTAGE_UV); + info->current_ua = microkit_mr_get(SDDF_PMIC_GET_REG_INFO_CURRENT_UA); + info->min_voltage_uv = microkit_mr_get(SDDF_PMIC_GET_REG_INFO_MIN_VOLTAGE_UV); + info->max_voltage_uv = microkit_mr_get(SDDF_PMIC_GET_REG_INFO_MAX_VOLTAGE_UV); + info->min_current_ua = microkit_mr_get(SDDF_PMIC_GET_REG_INFO_MIN_CURRENT_UA); + info->max_current_ua = microkit_mr_get(SDDF_PMIC_GET_REG_INFO_MAX_CURRENT_UA); + info->ramprate = microkit_mr_get(SDDF_PMIC_GET_REG_INFO_RAMPRATE); + } + + return ret; +} + diff --git a/include/sddf/pmic/driver.h b/include/sddf/pmic/driver.h new file mode 100644 index 000000000..b696b9874 --- /dev/null +++ b/include/sddf/pmic/driver.h @@ -0,0 +1,97 @@ +/* + * Copyright 2026, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +// This header file defines the state machine used by the driver to function. +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + + +#define DEBUG_PMIC_DRIVER +#ifdef DEBUG_PMIC_DRIVER +#define LOG_PMIC_DRIVER(...) do{ sddf_dprintf("PMIC DRIVER|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) +#else +#define LOG_PMIC_DRIVER(...) do{}while(0) +#endif + +#define LOG_PMIC_DRIVER_ERR(...) do{ sddf_dprintf("PMIC DRIVER|ERROR: "); sddf_dprintf(__VA_ARGS__); }while(0) + +// Utility types +typedef uint32_t pmic_unit_t; +typedef uint32_t pmic_reg_id_t; + +typedef struct unit_capability { + bool adjustable; + pmic_unit_t min_value; + pmic_unit_t max_value; + pmic_unit_t quantisation; // Number of bits used to parameterise value in PMIC - i.e. conf reg size +} unit_capability_t; + +typedef struct reg_capabilities { + unit_capability_t voltage; + unit_capability_t current; + bool toggleable; // Can this regulator be turned on and off? +} reg_capabilities_t; + +typedef struct sddf_regulator { + reg_capabilities_t capabilities; + pmic_unit_t voltage_uv; + pmic_unit_t current_ua; + bool enabled; +} sddf_regulator_t; + +typedef struct pmic_driver_state { + microkit_channel curr_client; + uint64_t curr_ppc_op; + uint64_t err; + uint64_t get_info_cnt; // Progress through get info call. Easier to count than add N states! +} pmic_driver_state_t; + +// typedef enum { S_IDLE, S_REQ_RET, NUM_STATES } pmic_state_t; + +// State machine: +// Idle: sleeping +// Request: a PPC has arrived and we've dispatched an I2C command to handle it +// This state is IMPLICIT. We are here by merit of a PPC arriving. +// // Request return: notification has arrived from I2C, finish PPC and go to sleep. +// // Invariant: one I2C request per PPC only, allowing state machine to be simple. +// +// typedef struct fsm { +// pmic_state_t curr_state; +// pmic_state_t next_state; +// bool yield; // fsm funcs can set this to tell the FSM loop to allow the PD to sleep +// } fsm_data_t; +// +// // Each state implements a single state function which is called by the FSM. +// typedef void pmic_state_func_t(fsm_data_t *fsm, pmic_driver_state_t *state); +// +// Prototype for FSM function +// void fsm(fsm_data_t *f); +// +// void state_idle(fsm_data_t *fsm, pmic_driver_state_t *state); +// void state_req(fsm_data_t *fsm, pmic_driver_state_t *state); +// void state_req_return(fsm_data_t *fsm, pmic_driver_state_t *state); + +// PPC handlers +sddf_pmic_err_t pmic_drv_enable_reg(uint64_t reg_id); +sddf_pmic_err_t pmic_drv_disable_reg(uint64_t reg_id); +sddf_pmic_err_t pmic_drv_set_vout(uint64_t reg_id, uint64_t voltage_uv); +sddf_pmic_err_t pmic_drv_set_climit(uint64_t reg_id, uint64_t current_ua); +sddf_pmic_err_t pmic_drv_get_info(uint64_t reg_id, sddf_pmic_reg_info_t *info); + +static void pmic_reset_state(pmic_driver_state_t *s) +{ + memset(s, 0, sizeof(pmic_driver_state_t)); + s->curr_ppc_op = SDDF_PMIC_PPC_INVALID; + // HACK: sentinel value due to HACK required for i2c-based pmics. +} + diff --git a/include/sddf/pmic/protocol.h b/include/sddf/pmic/protocol.h new file mode 100644 index 000000000..02792f7ae --- /dev/null +++ b/include/sddf/pmic/protocol.h @@ -0,0 +1,121 @@ +/* + * Copyright 2026, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once +#include + +// PPC interface for PMIC device class +// DANGER: current PMIC class is DUMB. It will not automatically solve constraints, +// each operation here is a best-effort direct operation on hardware. + +typedef enum { + SDDF_PMIC_ENABLE_REG = 0U, + SDDF_PMIC_DISABLE_REG, + SDDF_PMIC_SET_VOUT, + SDDF_PMIC_SET_CLIMIT, + SDDF_PMIC_GET_REG_INFO, + SDDF_PMIC_PPC_INVALID +} sddf_pmic_ppc_codes_t; + +typedef enum { + SDDF_PMIC_ERR_OK = 0U, + SDDF_PMIC_ERR_FAIL_REG, + SDDF_PMIC_ERR_BAD_SETTING, + SDDF_PMIC_ERR_NOT_IMPLEMENTED, + SDDF_PMIC_ERR_BAD_PPC_CALL, + SDDF_PMIC_ERR_BUSY, + SDDF_PMIC_ERR_OTHER, +} sddf_pmic_err_t; + +typedef struct { + uint64_t enabled; + uint64_t voltage_uv; + uint64_t current_ua; + uint64_t min_voltage_uv; + uint64_t max_voltage_uv; + uint64_t min_current_ua; + uint64_t max_current_ua; + uint64_t ramprate; +} sddf_pmic_reg_info_t; + +// ## PPCs ## +// SDDF_PMIC_ENABLE_REG +// Enable a given regulator +// Args: +// MR0: reg_id (defined per device, in bindings file) +// Returns: +// MR0: 0 on success, nonzero on failure +#define SDDF_PMIC_ENABLE_REG_REG_ID (0) +#define SDDF_PMIC_ENABLE_REG_SUCCESS (0) +#define SDDF_PMIC_ENABLE_REG_FAIL (1) + +// SDDF_PMIC_DISABLE_REG +// Disable a given regulator +// Args: +// MR0: reg_id +// Returns: +// MR0: 0 on success, nonzero on failure +#define SDDF_PMIC_DISABLE_REG_REG_ID (0) +#define SDDF_PMIC_DISABLE_REG_SUCCESS (0) +#define SDDF_PMIC_DISABLE_REG_FAIL (1) + +// SDDF_PMIC_SET_VOUT +// Set the voltage output level for a regulator, if that regulator has adjustable voltage. +// Args: +// MR0: reg_id +// MR1: voltage (microvolts) +// MR2: operating mode id (defined per regulator, per device, in bindings file) +// Returns: +// MR0: 0 on success, 1 if regulator invalid, 2 if voltage setting invalid for valid reg. +// NOTE: if a regulator doesn't support voltage setting, error 1 is returned. +#define SDDF_PMIC_SET_VOUT_REG_ID (0) +#define SDDF_PMIC_SET_VOUT_VOLTAGE_UV (1) +#define SDDF_PMIC_SET_VOUT_OP_MODE_ID (2) +#define SDDF_PMIC_SET_VOUT_SUCCESS (0) +#define SDDF_PMIC_SET_VOUT_FAIL_BADREG (1) +#define SDDF_PMIC_SET_VOUT_FAIL_BADVOLTAGE (2) + +// SDDF_PMIC_SET_CLIMIT +// Set the current limit of a regulator, if that regulator has a current limit setting. +// Args: +// MR0: reg_id +// MR1: current (uA) +// MR2: operating mode id (defined per regulator, per device, in bindings file) +// Returns: +// MR0: 0 on success, 1 if regulator invalid, 2 if current setting invalid for valid reg. +// NOTE: if a regulator doesn't support current setting, error 1 is returned. +#define SDDF_PMIC_SET_CLIMIT_REG_ID (0) +#define SDDF_PMIC_SET_CLIMIT_CURRENT_UA (1) +#define SDDF_PMIC_SET_CLIMIT_OP_MODE_ID (2) +#define SDDF_PMIC_SET_CLIMIT_SUCCESS (0) +#define SDDF_PMIC_SET_CLIMIT_FAIL_BADREG (1) +#define SDDF_PMIC_SET_CLIMIT_FAIL_BADCURRENT (2) + +// SDDF_PMIC_GET_REG_INFO +// Get information about a regulator, including the current state as well as capabilities. +// Args: +// MR0: reg_id +// Returns: +// MR0: 0 on success, 1 if invalid. +// MR1: enabled +// MR2: current voltage (microvolts) +// MR3: current current (uA) +// MR4: min voltage (uV) +// MR5: max voltage (uV) +// MR6: min current (uA) +// MR7: max current (uA) +// MR8: ramprate (NOT IMPLEMENTED) +#define SDDF_PMIC_GET_REG_INFO_REG_ID (0) +#define SDDF_PMIC_GET_REG_INFO_SUCCESS (0) +#define SDDF_PMIC_GET_REG_INFO_FAIL_INVALID (1) +#define SDDF_PMIC_GET_REG_INFO_ENABLED (1) +#define SDDF_PMIC_GET_REG_INFO_VOLTAGE_UV (2) +#define SDDF_PMIC_GET_REG_INFO_CURRENT_UA (3) +#define SDDF_PMIC_GET_REG_INFO_MIN_VOLTAGE_UV (4) +#define SDDF_PMIC_GET_REG_INFO_MAX_VOLTAGE_UV (5) +#define SDDF_PMIC_GET_REG_INFO_MIN_CURRENT_UA (6) +#define SDDF_PMIC_GET_REG_INFO_MAX_CURRENT_UA (7) +#define SDDF_PMIC_GET_REG_INFO_RAMPRATE (8) + diff --git a/tools/make/board/maaxboard.mk b/tools/make/board/maaxboard.mk index 5c9372dfb..863f5efb8 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} +PMIC_DRIV_DIR := bd71837amwv CPU := cortex-a53 From a3bf6e22c385790916ac754c90994ad78ee711d4 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Tue, 14 Jul 2026 14:08:01 +1000 Subject: [PATCH 19/23] PMIC class (wip) Signed-off-by: Lesley Rossouw --- acacia_sddf/pmic.py | 195 +++++++++++++++++++++++++++++++++++++++++++ examples/i2c/meta.py | 2 +- 2 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 acacia_sddf/pmic.py diff --git a/acacia_sddf/pmic.py b/acacia_sddf/pmic.py new file mode 100644 index 000000000..50083d860 --- /dev/null +++ b/acacia_sddf/pmic.py @@ -0,0 +1,195 @@ +# 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 .i2c import sDDFI2C, I2CAddress +from collections import defaultdict +from typing import List, Dict, Type, Union, Optional + + +class sDDFPMIC(sDDFDriverClass): + def __init__( + self, + sdf: System, + dev_compatible: str, + dev_dt_path: str, + i2c: sDDFI2C, + i2c_addr: Optional[I2CAddress] = None, + driver_prio: int = 254, + cpu: Optional[int] = None, + driver_elf: str = "timer_driver.elf", + ): + self.i2c = i2c + assert not i2c.built # We need to be a client! + super().__init__( + sdf, "pmic", dev_compatible, dev_dt_path, magic="sDDF" + chr(1) + ) + self.driver = ProtectionDomain( + self.sdf, + "timer_driver", + driver_elf, + scheduling=SchedulingProperties(driver_prio, passive=True), + ) + self.cpu = cpu + + # PMIC doesn't need device resources (currently)! Just an I2C client for imx. + # self.driver_dev_resources = self.create_dtb_resources(self.driver) + + # If no address is provided, we should try find one matching our dev_dt_path. + dtb_i2c_addrs = self.i2c.get_i2c_addresses_from_dtb(self.dtb_node) + if i2c_addr: + if i2c_addr not in dtb_i2c_addrs: + raise ValueError(f"PMIC @ addr={i2c_addr} not present in DTB! Eligible: {dtb_i2c_addrs}") + self.i2c_addr = i2c_addr + else: + if len(dtb_i2c_addrs) != 1: + # Perhaps we can have better default behaviour? + raise RuntimeWarning(f"PMIC doesn't have a single I2C address in DTB (found `{dtb_i2c_addrs}`)! Please specify using i2c_addr=(preferred)") + self.i2c_addr = dtb_i2c_addrs[0] # Should only be one! + 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( + self.sdf, + Channel.End(c, can_notify=False, can_pp=True), + Channel.End(self.driver, can_notify=True, can_pp=False), + ) + self.client_configs.append( + 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 + + 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, + ) + + +# Driver configs +def add_driver_config(driver_name: str, config: sDDFDriverConfig): + sDDFDriverManifest().add_driver_config(sDDFPMIC, 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/meta.py b/examples/i2c/meta.py index 8c139881b..7746da2b0 100644 --- a/examples/i2c/meta.py +++ b/examples/i2c/meta.py @@ -24,7 +24,7 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): # HACK: sdfgen doesn't support multiple regions for a device resource yet # or the clk class. This will be removed in the pending sdfgen refactor. - regions = [] # tuples of (mr, map var name) + regions = [] # tuples of (mr, map var name) if board.name == "maaxboard": # HACK: override DTS node to enable external I2C bus (I2C2). # This should also be fixed in the sdfgen refactor From 0fdda3b29c4e3b3e9196c8eb317153ee0bb77613 Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Tue, 14 Jul 2026 16:39:25 +1000 Subject: [PATCH 20/23] Add acacia support for pmic, add config to pmic Signed-off-by: Lesley Rossouw --- acacia_sddf/__init__.py | 1 + acacia_sddf/board.py | 2 + acacia_sddf/pmic.py | 153 ++++++++--------------- drivers/pmic/bd71837amwv/bd71837.c | 41 +++--- drivers/pmic/bd71837amwv/bd71837.h | 116 +++++++++-------- examples/i2c/meta.py | 46 +------ examples/pmic/client.c | 9 +- examples/pmic/meta.py | 97 +++++--------- examples/pmic/pmic.mk | 17 +-- include/sddf/pmic/bd71837amwv-bindings.h | 34 +++-- include/sddf/pmic/client.h | 10 +- include/sddf/pmic/driver.h | 2 - include/sddf/pmic/protocol.h | 1 - 13 files changed, 204 insertions(+), 325 deletions(-) diff --git a/acacia_sddf/__init__.py b/acacia_sddf/__init__.py index 0aee53165..d8477dd19 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 .pmic import sDDFPMIC diff --git a/acacia_sddf/board.py b/acacia_sddf/board.py index 705d3b657..f78377a87 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) + pmic: Optional[DriverDouble] = DriverDouble(None, None) partition: int = 0 baud_rate: Optional[int] = None @@ -95,6 +96,7 @@ class Board: ethernet=DriverDouble("", "soc@0/bus@30800000/ethernet@30be0000"), blk=DriverDouble("", "soc@0/bus@30800000/mmc@30b40000"), i2c=DriverDouble("fsl,imx8mq-i2c", "soc@0/bus@30800000/i2c@30a20000"), + pmic=DriverDouble("rohm,bd71837", "soc@0/bus@30800000/i2c@30a20000/pmic@4b"), partition=2, ), Board( diff --git a/acacia_sddf/pmic.py b/acacia_sddf/pmic.py index 50083d860..9c24f589f 100644 --- a/acacia_sddf/pmic.py +++ b/acacia_sddf/pmic.py @@ -31,22 +31,29 @@ def __init__( dev_dt_path: str, i2c: sDDFI2C, i2c_addr: Optional[I2CAddress] = None, - driver_prio: int = 254, + driver_prio: Optional[int] = None, cpu: Optional[int] = None, - driver_elf: str = "timer_driver.elf", + driver_elf: str = "pmic_driver.elf", ): self.i2c = i2c + self.cpu = cpu assert not i2c.built # We need to be a client! super().__init__( sdf, "pmic", dev_compatible, dev_dt_path, magic="sDDF" + chr(1) ) + if driver_prio: + assert driver_prio < i2c.virt.priority + else: + # Default to just below i2c virt + driver_prio = i2c.virt.priority - 1 self.driver = ProtectionDomain( self.sdf, - "timer_driver", + "pmic_driver", driver_elf, - scheduling=SchedulingProperties(driver_prio, passive=True), + scheduling=SchedulingProperties(driver_prio), + cpu=self.cpu, ) - self.cpu = cpu + i2c.add_client(self.driver) # PMIC doesn't need device resources (currently)! Just an I2C client for imx. # self.driver_dev_resources = self.create_dtb_resources(self.driver) @@ -55,31 +62,41 @@ def __init__( dtb_i2c_addrs = self.i2c.get_i2c_addresses_from_dtb(self.dtb_node) if i2c_addr: if i2c_addr not in dtb_i2c_addrs: - raise ValueError(f"PMIC @ addr={i2c_addr} not present in DTB! Eligible: {dtb_i2c_addrs}") + raise ValueError( + f"PMIC @ addr={i2c_addr} not present in DTB! Eligible: {dtb_i2c_addrs}" + ) self.i2c_addr = i2c_addr else: if len(dtb_i2c_addrs) != 1: # Perhaps we can have better default behaviour? - raise RuntimeWarning(f"PMIC doesn't have a single I2C address in DTB (found `{dtb_i2c_addrs}`)! Please specify using i2c_addr=(preferred)") - self.i2c_addr = dtb_i2c_addrs[0] # Should only be one! + raise RuntimeWarning( + f"PMIC doesn't have a single I2C address in DTB (found `{dtb_i2c_addrs}`)! Please specify using i2c_addr=(preferred)" + ) + self.i2c_addr = dtb_i2c_addrs[0] # Should only be one! + + if self.i2c_addr.is_ten_bit: + raise RuntimeError( + "The sDDF does not currently support ten-bit I2C addresses!" + ) + + # TODO: claim i2c address from i2c driver once support is added to do so self.client_configs = [] def connect_clients(self): # Clients are connected with: - # a. channel allowing PPCs -> driver, notifications -> clienet - # ... that's it! + # a. channel allowing PPCs -> driver for c in self.clients: if c.priority > self.driver.priority: raise SubsystemBuildError( - f"Client {c} has higher priority than timer driver!" + f"Client {c} has higher priority than pmic driver!" ) ch = Channel( self.sdf, Channel.End(c, can_notify=False, can_pp=True), - Channel.End(self.driver, can_notify=True, can_pp=False), + Channel.End(self.driver, can_notify=False, can_pp=False), ) self.client_configs.append( - self.timer_client_config_factory(c, ch.id_for_pd(c)) + self.pmic_client_config_factory(c, ch.id_for_pd(c)) ) def x86_resources(self): @@ -87,20 +104,32 @@ def x86_resources(self): def generate_config_structs(self): # We've already made our structs - return [self.driver_dev_resources] + self.client_configs + return [self.pmic_driver_config_factory()] + self.client_configs + + def pmic_driver_config_factory(self) -> ConfigStruct: + """ + create pmic_driver_config_t for driver. + """ + fields = {"magic": "sDDF" + chr(7), "i2c_addr": self.i2c_addr.addr} + return ConfigStruct( + "pmic_driver_config_t", + target_file=self.driver.prog_image, + section_name="pmic_driver_config", + fields=fields, + ) - def timer_client_config_factory( + def pmic_client_config_factory( self, client_pd: ProtectionDomain, driver_id: int ) -> ConfigStruct: """ - create timer_client_config for client_pd with serial id n + create pmic_client_config for client_pd """ - # invariant: this PD only is a client to timer one time. - fields = {"magic": "sDDF" + chr(6), "driver_id": driver_id} + # invariant: this PD only is a client to pmic one time. + fields = {"magic": "sDDF" + chr(7), "driver_id": driver_id} return ConfigStruct( - "timer_client_config_t", + "pmic_client_config_t", target_file=client_pd.prog_image, - section_name="timer_client_config", + section_name="pmic_client_config", fields=fields, ) @@ -110,86 +139,12 @@ def add_driver_config(driver_name: str, config: sDDFDriverConfig): sDDFDriverManifest().add_driver_config(sDDFPMIC, 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 +# imx add_driver_config( - "rk3568", + "bd71837", sDDFDriverConfig( - compatible="rockchip,rk3568-timer", - regions=[DTSRegion("regs", dt_idx=0)], - irqs=[DTSIRQ(0), DTSIRQ(1)], + "rohm,bd71837", + regions=[], + irqs=[], ), ) diff --git a/drivers/pmic/bd71837amwv/bd71837.c b/drivers/pmic/bd71837amwv/bd71837.c index 3aabd3bae..df54aa620 100644 --- a/drivers/pmic/bd71837amwv/bd71837.c +++ b/drivers/pmic/bd71837amwv/bd71837.c @@ -8,19 +8,22 @@ #include #include #include +#include #include #include #include +#include +#include #include "bd71837.h" __attribute__((__section__(".i2c_client_config"))) i2c_client_config_t i2c_config; +__attribute__((__section__(".pmic_driver_config"))) pmic_driver_config_t pmic_config; libi2c_conf_t libi2c_conf; i2c_queue_handle_t queue; uint8_t *i2c_data_region; -// TODO: add support for sdfgen to pass in PMIC i2c address -#define PMIC_I2C_ADDR (0x4b) +#define PMIC_I2C_ADDR ((i2c_addr_t)pmic_config.i2c_addr) #ifndef I2C_DATA_REGION #define I2C_DATA_REGION ((uint8_t *)i2c_config.data.vaddr) @@ -34,20 +37,24 @@ uint8_t *i2c_data_region; pmic_driver_state_t state; -static inline sddf_pmic_err_t pmic_not_implemented() { +static inline sddf_pmic_err_t pmic_not_implemented() +{ LOG_PMIC_DRIVER_ERR("This PPC not implemented for this platform!\n"); return SDDF_PMIC_ERR_NOT_IMPLEMENTED; } -sddf_pmic_err_t pmic_drv_enable_reg(uint64_t reg_id) { +sddf_pmic_err_t pmic_drv_enable_reg(uint64_t reg_id) +{ return pmic_not_implemented(); } -sddf_pmic_err_t pmic_drv_disable_reg(uint64_t reg_id) { +sddf_pmic_err_t pmic_drv_disable_reg(uint64_t reg_id) +{ return pmic_not_implemented(); } -sddf_pmic_err_t pmic_drv_set_vout(uint64_t reg_id, uint64_t voltage_uv) { +sddf_pmic_err_t pmic_drv_set_vout(uint64_t reg_id, uint64_t voltage_uv) +{ // Look up regulator info bd71837_reg_t *regulator = &bd71837_regulator_table[reg_id]; @@ -89,7 +96,7 @@ sddf_pmic_err_t pmic_drv_set_vout(uint64_t reg_id, uint64_t voltage_uv) { LOG_PMIC_DRIVER("Setting voltage to %zu - target value = %zu\n", voltage_uv, target_val); assert(target_val <= (1 << regulator->reg.capabilities.voltage.quantisation)); - i2c_reg_val = (uint8_t) (target_val & 0xff); + i2c_reg_val = (uint8_t)(target_val & 0xff); } // prepare i2c transaction @@ -100,19 +107,21 @@ sddf_pmic_err_t pmic_drv_set_vout(uint64_t reg_id, uint64_t voltage_uv) { return SDDF_PMIC_ERR_OK; } -sddf_pmic_err_t pmic_drv_set_climit(uint64_t reg_id, uint64_t current_ua) { +sddf_pmic_err_t pmic_drv_set_climit(uint64_t reg_id, uint64_t current_ua) +{ return pmic_not_implemented(); } -sddf_pmic_err_t pmic_drv_get_info(uint64_t reg_id, sddf_pmic_reg_info_t *info) { +sddf_pmic_err_t pmic_drv_get_info(uint64_t reg_id, sddf_pmic_reg_info_t *info) +{ return pmic_not_implemented(); } - void init(void) { // Setup i2c client connection assert(i2c_config_check_magic((void *)&i2c_config)); + assert(pmic_config_check_magic((void *)&pmic_config)); i2c_data_region = (uint8_t *)i2c_config.data.vaddr; queue = i2c_queue_init(i2c_config.virt.req_queue.vaddr, i2c_config.virt.resp_queue.vaddr); @@ -144,7 +153,8 @@ void notified(microkit_channel ch) if (ret == I2C_ERR_OK) { LOG_PMIC_DRIVER("Completed request for client %u. Opcode = %zu\n", state.curr_client, state.curr_ppc_op); } else { - LOG_PMIC_DRIVER_ERR("I2C request failed for client %u! Opcode = %zu\n", state.curr_client, state.curr_ppc_op); + LOG_PMIC_DRIVER_ERR("I2C request failed for client %u! Opcode = %zu\n", state.curr_client, + state.curr_ppc_op); } // Clean up state for next request. @@ -170,7 +180,7 @@ microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) case SDDF_PMIC_ENABLE_REG: { if (argc != 1) { LOG_PMIC_DRIVER_ERR("Incorrect number of arguments %u != 1\n", argc); - err = SDDF_PMIC_ERR_BAD_PPC_CALL; + err = SDDF_PMIC_ERR_BAD_PPC_CALL; break; } uint32_t reg_id = (uint32_t)microkit_mr_get(SDDF_PMIC_ENABLE_REG_REG_ID); @@ -222,7 +232,7 @@ microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) break; } uint32_t reg_id = (uint32_t)microkit_mr_get(SDDF_PMIC_GET_REG_INFO_REG_ID); - sddf_pmic_reg_info_t info = {0}; + sddf_pmic_reg_info_t info = { 0 }; LOG_PMIC_DRIVER("get request pmic_get_reg_info(%d)\n", reg_id); state.err = pmic_drv_get_info(reg_id, &info); if (err == SDDF_PMIC_GET_REG_INFO_SUCCESS) { @@ -239,8 +249,8 @@ microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) break; } default: - LOG_PMIC_DRIVER_ERR("Unknown request %lu to PMIC driver from channel %u\n", - microkit_msginfo_get_label(msginfo), ch); + LOG_PMIC_DRIVER_ERR("Unknown request %lu to PMIC driver from channel %u\n", microkit_msginfo_get_label(msginfo), + ch); err = SDDF_PMIC_ERR_BAD_PPC_CALL; } if (state.err != SDDF_PMIC_ERR_OK) { @@ -255,4 +265,3 @@ microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) return microkit_msginfo_new(err, ret_num); } - diff --git a/drivers/pmic/bd71837amwv/bd71837.h b/drivers/pmic/bd71837amwv/bd71837.h index 135ab229c..eaed7d71d 100644 --- a/drivers/pmic/bd71837amwv/bd71837.h +++ b/drivers/pmic/bd71837amwv/bd71837.h @@ -12,7 +12,6 @@ typedef struct bd71837_reg { uint8_t i2c_reg_addr; } bd71837_reg_t; - // IMPORTANT: this driver currently ONLY supports setting "run" mode parameters. // We don't support setting idle or suspend mode parameters as these features // cannot be harmoniously touched in LionsOS at the time of writing. @@ -248,60 +247,60 @@ static bd71837_reg_t bd71837_regulator_table[BD71837_NUM_REGULATORS] = { /* Registers specific to BD71837 */ enum { - BD71837_REG_BUCK3_CTRL = 0x07, - BD71837_REG_BUCK4_CTRL = 0x08, - BD71837_REG_BUCK3_VOLT_RUN = 0x12, - BD71837_REG_BUCK4_VOLT_RUN = 0x13, - BD71837_REG_LDO7_VOLT = 0x1E, + BD71837_REG_BUCK3_CTRL = 0x07, + BD71837_REG_BUCK4_CTRL = 0x08, + BD71837_REG_BUCK3_VOLT_RUN = 0x12, + BD71837_REG_BUCK4_VOLT_RUN = 0x13, + BD71837_REG_LDO7_VOLT = 0x1E, }; /* Registers common for BD71837 and BD71847 */ enum { - BD718XX_REG_REV = 0x00, - BD718XX_REG_SWRESET = 0x01, - BD718XX_REG_I2C_DEV = 0x02, - BD718XX_REG_PWRCTRL0 = 0x03, - BD718XX_REG_PWRCTRL1 = 0x04, - BD718XX_REG_BUCK1_CTRL = 0x05, - BD718XX_REG_BUCK2_CTRL = 0x06, - BD718XX_REG_1ST_NODVS_BUCK_CTRL = 0x09, - BD718XX_REG_2ND_NODVS_BUCK_CTRL = 0x0A, - BD718XX_REG_3RD_NODVS_BUCK_CTRL = 0x0B, - BD718XX_REG_4TH_NODVS_BUCK_CTRL = 0x0C, - BD718XX_REG_BUCK1_VOLT_RUN = 0x0D, - BD718XX_REG_BUCK1_VOLT_IDLE = 0x0E, - BD718XX_REG_BUCK1_VOLT_SUSP = 0x0F, - BD718XX_REG_BUCK2_VOLT_RUN = 0x10, - BD718XX_REG_BUCK2_VOLT_IDLE = 0x11, - BD718XX_REG_1ST_NODVS_BUCK_VOLT = 0x14, - BD718XX_REG_2ND_NODVS_BUCK_VOLT = 0x15, - BD718XX_REG_3RD_NODVS_BUCK_VOLT = 0x16, - BD718XX_REG_4TH_NODVS_BUCK_VOLT = 0x17, - BD718XX_REG_LDO1_VOLT = 0x18, - BD718XX_REG_LDO2_VOLT = 0x19, - BD718XX_REG_LDO3_VOLT = 0x1A, - BD718XX_REG_LDO4_VOLT = 0x1B, - BD718XX_REG_LDO5_VOLT = 0x1C, - BD718XX_REG_LDO6_VOLT = 0x1D, - BD718XX_REG_TRANS_COND0 = 0x1F, - BD718XX_REG_TRANS_COND1 = 0x20, - BD718XX_REG_VRFAULTEN = 0x21, - BD718XX_REG_MVRFLTMASK0 = 0x22, - BD718XX_REG_MVRFLTMASK1 = 0x23, - BD718XX_REG_MVRFLTMASK2 = 0x24, - BD718XX_REG_RCVCFG = 0x25, - BD718XX_REG_RCVNUM = 0x26, - BD718XX_REG_PWRONCONFIG0 = 0x27, - BD718XX_REG_PWRONCONFIG1 = 0x28, - BD718XX_REG_RESETSRC = 0x29, - BD718XX_REG_MIRQ = 0x2A, - BD718XX_REG_IRQ = 0x2B, - BD718XX_REG_IN_MON = 0x2C, - BD718XX_REG_POW_STATE = 0x2D, - BD718XX_REG_OUT32K = 0x2E, - BD718XX_REG_REGLOCK = 0x2F, - BD718XX_REG_OTPVER = 0xFF, - BD718XX_MAX_REGISTER = 0x100, + BD718XX_REG_REV = 0x00, + BD718XX_REG_SWRESET = 0x01, + BD718XX_REG_I2C_DEV = 0x02, + BD718XX_REG_PWRCTRL0 = 0x03, + BD718XX_REG_PWRCTRL1 = 0x04, + BD718XX_REG_BUCK1_CTRL = 0x05, + BD718XX_REG_BUCK2_CTRL = 0x06, + BD718XX_REG_1ST_NODVS_BUCK_CTRL = 0x09, + BD718XX_REG_2ND_NODVS_BUCK_CTRL = 0x0A, + BD718XX_REG_3RD_NODVS_BUCK_CTRL = 0x0B, + BD718XX_REG_4TH_NODVS_BUCK_CTRL = 0x0C, + BD718XX_REG_BUCK1_VOLT_RUN = 0x0D, + BD718XX_REG_BUCK1_VOLT_IDLE = 0x0E, + BD718XX_REG_BUCK1_VOLT_SUSP = 0x0F, + BD718XX_REG_BUCK2_VOLT_RUN = 0x10, + BD718XX_REG_BUCK2_VOLT_IDLE = 0x11, + BD718XX_REG_1ST_NODVS_BUCK_VOLT = 0x14, + BD718XX_REG_2ND_NODVS_BUCK_VOLT = 0x15, + BD718XX_REG_3RD_NODVS_BUCK_VOLT = 0x16, + BD718XX_REG_4TH_NODVS_BUCK_VOLT = 0x17, + BD718XX_REG_LDO1_VOLT = 0x18, + BD718XX_REG_LDO2_VOLT = 0x19, + BD718XX_REG_LDO3_VOLT = 0x1A, + BD718XX_REG_LDO4_VOLT = 0x1B, + BD718XX_REG_LDO5_VOLT = 0x1C, + BD718XX_REG_LDO6_VOLT = 0x1D, + BD718XX_REG_TRANS_COND0 = 0x1F, + BD718XX_REG_TRANS_COND1 = 0x20, + BD718XX_REG_VRFAULTEN = 0x21, + BD718XX_REG_MVRFLTMASK0 = 0x22, + BD718XX_REG_MVRFLTMASK1 = 0x23, + BD718XX_REG_MVRFLTMASK2 = 0x24, + BD718XX_REG_RCVCFG = 0x25, + BD718XX_REG_RCVNUM = 0x26, + BD718XX_REG_PWRONCONFIG0 = 0x27, + BD718XX_REG_PWRONCONFIG1 = 0x28, + BD718XX_REG_RESETSRC = 0x29, + BD718XX_REG_MIRQ = 0x2A, + BD718XX_REG_IRQ = 0x2B, + BD718XX_REG_IN_MON = 0x2C, + BD718XX_REG_POW_STATE = 0x2D, + BD718XX_REG_OUT32K = 0x2E, + BD718XX_REG_REGLOCK = 0x2F, + BD718XX_REG_OTPVER = 0xFF, + BD718XX_MAX_REGISTER = 0x100, }; #define REGLOCK_PWRSEQ 0x1 @@ -391,13 +390,13 @@ enum { /* ROHM BD718XX irqs */ enum { - BD718XX_INT_STBY_REQ, - BD718XX_INT_ON_REQ, - BD718XX_INT_WDOG, - BD718XX_INT_PWRBTN, - BD718XX_INT_PWRBTN_L, - BD718XX_INT_PWRBTN_S, - BD718XX_INT_SWRST + BD718XX_INT_STBY_REQ, + BD718XX_INT_ON_REQ, + BD718XX_INT_WDOG, + BD718XX_INT_PWRBTN, + BD718XX_INT_PWRBTN_L, + BD718XX_INT_PWRBTN_S, + BD718XX_INT_SWRST }; /* ROHM BD718XX interrupt masks */ @@ -424,4 +423,3 @@ enum { #define BD718XX_SWRESET_RESET_MASK 1 #define BD718XX_SWRESET_RESET 1 - diff --git a/examples/i2c/meta.py b/examples/i2c/meta.py index 7746da2b0..ebb1d654f 100644 --- a/examples/i2c/meta.py +++ b/examples/i2c/meta.py @@ -16,50 +16,8 @@ def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): sdf, "client_ds3231", "client_ds3231.elf", priority=1 ) - 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_pn532 = ProtectionDomain("client_pn532", "client_pn532.elf", priority=1) - client_ds3231 = ProtectionDomain("client_ds3231", "client_ds3231.elf", priority=1) - - # HACK: sdfgen doesn't support multiple regions for a device resource yet - # or the clk class. This will be removed in the pending sdfgen refactor. - regions = [] # tuples of (mr, map var name) - if board.name == "maaxboard": - # HACK: override DTS node to enable external I2C bus (I2C2). - # This should also be fixed in the sdfgen refactor - board.i2c = "soc@0/bus@30800000/i2c@30a30000" - - elif board.name == "odroidc4": - # Right now we do not have separate clk and GPIO drivers and so our I2C driver does manual - # clk/GPIO setup for I2C. - 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)) - else: - print("Unsupported board!") - exit(-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_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 + i2c = sDDFI2C( + sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=199 ) i2c.add_client(client_ds3231) i2c.add_client(client_pn532) diff --git a/examples/pmic/client.c b/examples/pmic/client.c index e51b76879..1d3d190ba 100644 --- a/examples/pmic/client.c +++ b/examples/pmic/client.c @@ -12,6 +12,7 @@ #include #include #include +#include #ifdef CONFIG_PLAT_MAAXBOARD #include #define TARGET_REGULATOR (BD718XX_BUCK2) // VDD_ARM @@ -23,14 +24,14 @@ __attribute__((__section__(".timer_client_config"))) timer_client_config_t timer_config; __attribute__((__section__(".serial_client_config"))) serial_client_config_t serial_config; +__attribute__((__section__(".pmic_client_config"))) pmic_client_config_t pmic_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 PMIC_CHANNEL (pmic_config.driver_id) #define STACK_SIZE (4096) static char t_client_main_stack[STACK_SIZE]; @@ -73,7 +74,8 @@ void notified(sddf_channel ch) } } -void client_main(void) { +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 @@ -96,6 +98,7 @@ void init(void) serial_putchar_init(serial_config.tx.id, &serial_tx_queue_handle); assert(timer_config_check_magic(&timer_config)); + assert(pmic_config_check_magic(&pmic_config)); sddf_printf("CLIENT|INFO: starting\n"); timer_channel = timer_config.driver_id; diff --git a/examples/pmic/meta.py b/examples/pmic/meta.py index 0f04cd3ef..0ed411561 100644 --- a/examples/pmic/meta.py +++ b/examples/pmic/meta.py @@ -4,80 +4,42 @@ 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 +from acacia import System, ProtectionDomain, MemoryRegion, Channel, DeviceTreeBlob, Map -assert version("sdfgen").split(".")[1] == "28", "Unexpected sdfgen version" +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) +from acacia_sddf import BOARDS, sDDFI2C, sDDFSerial, sDDFTimer, sDDFPMIC -ProtectionDomain = SystemDescription.ProtectionDomain -MemoryRegion = SystemDescription.MemoryRegion -Map = SystemDescription.Map -Channel = SystemDescription.Channel +def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): + client = ProtectionDomain(sdf, "client", "client.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 - ) - - timer_driver = ProtectionDomain("timer_driver", "timer_driver.elf", priority=7) - i2c_driver = ProtectionDomain("i2c_driver", "i2c_driver.elf", priority=6) - i2c_virt = ProtectionDomain("i2c_virt", "i2c_virt.elf", priority=5) - pmic_driver = ProtectionDomain("pmic_driver", "pmic_driver.elf", priority=4) - client = ProtectionDomain("client", "client.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 - # TODO: add sdfgen support for passing in i2c addresses to pmic - - i2c_system = Sddf.I2c(sdf, i2c_node, i2c_driver, i2c_virt) - i2c_system.add_client(pmic_driver) + timer = sDDFTimer(sdf, board.timer.compatible, board.timer.node_path) + timer.add_client(client) - 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 = sDDFSerial( + sdf, + board.serial.compatible, + board.serial.node_path, + 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) - - # Connect PMIC client - # TODO: sdfgen for this - pmic_channel = Channel(pmic_driver, client, pp_b=True) - sdf.add_channel(pmic_channel) + serial.add_client(client) - pds = [ - serial_driver, - serial_virt_tx, - timer_driver, - i2c_driver, - pmic_driver, - i2c_virt, - client, - ] - for pd in pds: - sdf.add_pd(pd) + i2c = sDDFI2C( + sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=199 + ) - 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) + pmic = sDDFPMIC(sdf, board.pmic.compatible, board.pmic.node_path, i2c) + pmic.add_client(client) - 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__": @@ -92,10 +54,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/pmic/pmic.mk b/examples/pmic/pmic.mk index 80165d766..5ad333d43 100644 --- a/examples/pmic/pmic.mk +++ b/examples/pmic/pmic.mk @@ -76,14 +76,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_pmic_driver.data pmic_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 .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=pmic_driver_i2c_client_config.data pmic_driver.elf + $(OBJCOPY) --update-section .timer_client_config=client_timer_client_config.data client.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 .pmic_client_config=client_pmic_client_config.data client.elf + $(OBJCOPY) --update-section .pmic_driver_config=pmic_driver_pmic_driver_config.data pmic_driver.elf + touch $@ $(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) diff --git a/include/sddf/pmic/bd71837amwv-bindings.h b/include/sddf/pmic/bd71837amwv-bindings.h index a75cc693e..be4cd20b1 100644 --- a/include/sddf/pmic/bd71837amwv-bindings.h +++ b/include/sddf/pmic/bd71837amwv-bindings.h @@ -8,22 +8,22 @@ // (for snippets derived from rohm-bd718x7.h in Linux kernel) typedef enum { - BD718XX_BUCK1 = 0, - BD718XX_BUCK2, - BD718XX_BUCK3, - BD718XX_BUCK4, - BD718XX_BUCK5, - BD718XX_BUCK6, - BD718XX_BUCK7, - BD718XX_BUCK8, - BD718XX_LDO1, - BD718XX_LDO2, - BD718XX_LDO3, - BD718XX_LDO4, - BD718XX_LDO5, - BD718XX_LDO6, - BD718XX_LDO7, - BD718XX_REGULATOR_AMOUNT, + BD718XX_BUCK1 = 0, + BD718XX_BUCK2, + BD718XX_BUCK3, + BD718XX_BUCK4, + BD718XX_BUCK5, + BD718XX_BUCK6, + BD718XX_BUCK7, + BD718XX_BUCK8, + BD718XX_LDO1, + BD718XX_LDO2, + BD718XX_LDO3, + BD718XX_LDO4, + BD718XX_LDO5, + BD718XX_LDO6, + BD718XX_LDO7, + BD718XX_REGULATOR_AMOUNT, } bd718xx_regulators; /* Common voltage configurations */ @@ -42,5 +42,3 @@ typedef enum { #define BD71837_BUCK7_VOLTAGE_NUM 0x08 #define BD71837_LDO5_VOLTAGE_NUM 0x10 #define BD71837_LDO7_VOLTAGE_NUM 0x10 - - diff --git a/include/sddf/pmic/client.h b/include/sddf/pmic/client.h index d5ae48aac..fee93a041 100644 --- a/include/sddf/pmic/client.h +++ b/include/sddf/pmic/client.h @@ -49,8 +49,7 @@ static inline int sddf_pmic_disable_reg(microkit_channel channel, uint32_t reg_i * @param voltage_uv target voltage in microvolts. * @return 0 on success, 1 if regulator invalid, 2 if voltage setting invalid. */ -static inline int sddf_pmic_set_vout(microkit_channel channel, uint32_t reg_id, - uint64_t voltage_uv) +static inline int sddf_pmic_set_vout(microkit_channel channel, uint32_t reg_id, uint64_t voltage_uv) { microkit_msginfo msginfo = microkit_msginfo_new(SDDF_PMIC_SET_VOUT, 3); microkit_mr_set(SDDF_PMIC_SET_VOUT_REG_ID, reg_id); @@ -69,8 +68,7 @@ static inline int sddf_pmic_set_vout(microkit_channel channel, uint32_t reg_id, * @param current_ua target current limit in microamps. * @return 0 on success, 1 if regulator invalid, 2 if current setting invalid. */ -static inline int sddf_pmic_set_climit(microkit_channel channel, uint32_t reg_id, - uint64_t current_ua) +static inline int sddf_pmic_set_climit(microkit_channel channel, uint32_t reg_id, uint64_t current_ua) { microkit_msginfo msginfo = microkit_msginfo_new(SDDF_PMIC_SET_CLIMIT, 3); microkit_mr_set(SDDF_PMIC_SET_CLIMIT_REG_ID, reg_id); @@ -89,8 +87,7 @@ static inline int sddf_pmic_set_climit(microkit_channel channel, uint32_t reg_id * @param info pointer to structure to populate with regulator information. * @return 0 on success, 1 if regulator invalid. */ -static inline int sddf_pmic_get_reg_info(microkit_channel channel, uint32_t reg_id, - sddf_pmic_reg_info_t *info) +static inline int sddf_pmic_get_reg_info(microkit_channel channel, uint32_t reg_id, sddf_pmic_reg_info_t *info) { microkit_msginfo msginfo = microkit_msginfo_new(SDDF_PMIC_GET_REG_INFO, 1); microkit_mr_set(SDDF_PMIC_GET_REG_INFO_REG_ID, reg_id); @@ -111,4 +108,3 @@ static inline int sddf_pmic_get_reg_info(microkit_channel channel, uint32_t reg_ return ret; } - diff --git a/include/sddf/pmic/driver.h b/include/sddf/pmic/driver.h index b696b9874..3cd669b8a 100644 --- a/include/sddf/pmic/driver.h +++ b/include/sddf/pmic/driver.h @@ -15,7 +15,6 @@ #include #include - #define DEBUG_PMIC_DRIVER #ifdef DEBUG_PMIC_DRIVER #define LOG_PMIC_DRIVER(...) do{ sddf_dprintf("PMIC DRIVER|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) @@ -94,4 +93,3 @@ static void pmic_reset_state(pmic_driver_state_t *s) s->curr_ppc_op = SDDF_PMIC_PPC_INVALID; // HACK: sentinel value due to HACK required for i2c-based pmics. } - diff --git a/include/sddf/pmic/protocol.h b/include/sddf/pmic/protocol.h index 02792f7ae..3c779a6bb 100644 --- a/include/sddf/pmic/protocol.h +++ b/include/sddf/pmic/protocol.h @@ -118,4 +118,3 @@ typedef struct { #define SDDF_PMIC_GET_REG_INFO_MIN_CURRENT_UA (6) #define SDDF_PMIC_GET_REG_INFO_MAX_CURRENT_UA (7) #define SDDF_PMIC_GET_REG_INFO_RAMPRATE (8) - From f8d4f82a87df56d97a0c16407b0f960d78305eca Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Tue, 14 Jul 2026 17:10:07 +1000 Subject: [PATCH 21/23] Add dev docs for PMIC Signed-off-by: Lesley Rossouw --- docs/drivers.md | 5 ++++ docs/pmic/pmic.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 docs/pmic/pmic.md diff --git a/docs/drivers.md b/docs/drivers.md index 41aeb44f7..a62818134 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` + +## PMIC + +* Rohm BD71837 (iMX boards) + * `rohm,bd71837` diff --git a/docs/pmic/pmic.md b/docs/pmic/pmic.md new file mode 100644 index 000000000..a72f38c45 --- /dev/null +++ b/docs/pmic/pmic.md @@ -0,0 +1,59 @@ + +# sDDF PMIC Subsystem + +The sDDF has support for I²C-based PMIC devices, using the sDDF I²C driver as +supporting infrastructure. + +## Power Management ICs + +Many SoCs have an external **Power Management IC** +(PMIC) controlled via I2C. This is a chip that is responsible for feeding in +several power lines to the chip and controlling their output with an array of +regulators. + +## PMIC driver class + +**CURRENT STATUS**: the PMIC class is a WIP and only supports controlling the output voltage +of regulators in the `bd71837` driver. The protocol supports all operations, but they are +not implemented there as of the time of writing. This is sufficient for implementing +basic system management features like DVFS (dynamic voltage and frequency scaling). + +A PMIC contains many programmable regulators, each generating a voltage rail for +various board functions. +Each regulator has different: + - voltage output ranges (different minima and maxima), + - ability to set a current limit, with different maxima and minima, + - ability to be turned on or off, and + - step sizes for configuring voltages and currents. + +The PMIC driver must be able to deal with manipulating these heterogeneous +regulators programmatically because clients may generate requests +that are not possible to implement, or that conflict with the +requirements for other clients. + +The core of the PMIC driver is a table of regulator descriptions, parameterised +by regulator IDs that are stored in a shared PMIC bindings header file for the +specific PMIC. The bindings header file is available to clients of the PMIC +driver as a way to identify specific regulators. The regulator description +table encodes all capabilities of every register such that our driver can +return descriptive errors for incorrect requests and avoid unnecessary I²C +operations. + +The PMIC protocol provides a \emph{Protected Procedure Call} (PPC) interface to +clients, with the following methods available: +- Enable or disable a target regulator, +- Set the output voltage of a regulator, +- Set the current limit of a regulator, +- Get info about the state of a regulator. + +Upon receipt of a PPC from a client, the driver will validate that any +arguments supplied are valid before attempting an I²C transaction. If the +arguments supplied are not within the bounds of the regulator description +table, the driver returns an error to the client. Otherwise, it will +generate an I²C request to read or write appropriate state to the PMIC. + + From d7bfbd2d6ec7a699c08c410b6ce1e615928996da Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Tue, 14 Jul 2026 14:08:52 +1000 Subject: [PATCH 22/23] 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 a04f76009158128b77d4b888a2c9926475f64c8c Mon Sep 17 00:00:00 2001 From: Lesley Rossouw Date: Tue, 14 Jul 2026 17:17:20 +1000 Subject: [PATCH 23/23] Draft pmic design doc section based on NCSC reporting Signed-off-by: Lesley Rossouw --- docs/design/design.tex | 55 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/docs/design/design.tex b/docs/design/design.tex index 8d8b19ff3..e9328792d 100644 --- a/docs/design/design.tex +++ b/docs/design/design.tex @@ -2655,6 +2655,61 @@ \section{Power, thermal and clock devices}\label{s:sensors} these drivers use a simple, low-throughput interface. +\subsection{PMIC}\label{s:pmic} + +Many SoCs have an external \emph{Power Management IC} +(PMIC) controlled via I2C. This is a chip that is responsible for feeding in +several power lines to the chip and controlling their output with an array of +regulators. + +The PMIC +contains 15 programmable regulators, each generating a voltage rail for +various board functions. +Each regulator has different: +\begin{enumerate} + \item voltage output ranges (different minima and maxima), + \item ability to set a current limit, with different maxima and minima, + \item ability to be turned on or off, and + \item step sizes for configuring voltages and currents. +\end{enumerate} + +The PMIC driver must be able to deal with manipulating these heterogeneous +regulators programmatically because clients may generate requests +that are not possible to implement, or that conflict with the +requirements for other clients. + +The core of the PMIC driver is a table of regulator descriptions, parameterised +by regulator IDs that are stored in a shared PMIC bindings header file for the +specific PMIC. The bindings header file is available to clients of the PMIC +driver as a way to identify specific regulators. The regulator description +table encodes all capabilities of every register such that our driver can +return descriptive errors for incorrect requests and avoid unnecessary \gls{i2c} +operations. + +The PMIC protocol provides a \emph{Protected Procedure Call} (PPC) interface to +clients, with the following methods available: +\begin{enumerate} +\item Enable or disable a target regulator, +\item Set the output voltage of a regulator, +\item Set the current limit of a regulator, +\item Get info about the state of a regulator. +\end{enumerate} + +In the current version, we have implemented only the ability to +set the output voltage for now. + +Upon receipt of a PPC from a client, the driver will validate that any +arguments supplied are valid before attempting an \gls{i2c} transaction. If the +arguments supplied are not within the bounds of the regulator description +table, the driver returns an error to the client. Otherwise, it will +generate an \gls{i2c} request to read or write appropriate state to the PMIC. + +Unlike most other classes, the PMIC driver is itself a client to the \gls{i2c} +driver class. +The PMIC driver issues requests to the \gls{i2c} virtualiser via a queue +in a shared +memory interface, abstracted using \emph{libi2c}. + \chapter{Hotplugging}\label{s:hotplugging} \section{Overview}