diff --git a/acacia_sddf/__init__.py b/acacia_sddf/__init__.py new file mode 100644 index 000000000..7f9b417cf --- /dev/null +++ b/acacia_sddf/__init__.py @@ -0,0 +1,9 @@ +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +from .i2c import sDDFI2C +from .timer import sDDFTimer +from .serial import sDDFSerial +from .blk import sDDFBlk +from .sddf import sDDFDriverClass, sDDFDriverConfig, sDDFDriverManifest +from .board import BOARDS, Board diff --git a/acacia_sddf/blk.py b/acacia_sddf/blk.py new file mode 100644 index 000000000..12bd47185 --- /dev/null +++ b/acacia_sddf/blk.py @@ -0,0 +1,426 @@ +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +from acacia import ( + System, + Subsystem, + ProtectionDomain, + Channel, + Map, + MemoryRegion, + DTBNode, + DeviceTreeBlob, + SchedulingProperties, + ConfigStruct, + SubsystemBuildError, +) +import sys, os +from collections import defaultdict +from typing import List, Dict, Type, Union, Optional +from dataclasses import dataclass +from acacia.x86 import IOPort +from acacia.irq import IrqIoapic +from .driver_manifest import sDDFDriverManifest, sDDFDriverConfig, DTSIRQ, DTSRegion +from .sddf import ( + sDDFDriverClass, + DeviceResourcesFactory, + RegionResourceFactory, + DeviceRegionResourceFactory, +) + +BLK_PROTOCOL_MAGIC = "sDDF" + chr(0x2) +BLK_STORAGE_INFO_SZ = 0x1000 + + +@dataclass(frozen=True) +class BlkClientOptions: + partition_number: int + queue_capacity: int = 128 + data_size: int = 2 * 1024 * 1024 # 2 mibibyte + + +class sDDFBlk(sDDFDriverClass): + + def __init__( + self, + sdf: System, + dev_compatible: str, + dev_dt_path: str, + driver_prio: int, + virt_prio: int, + cpu: Optional[int] = None, + # We leave this as configurable just in case... + driver_data_size: int = 0x1000, + virt_elf: str = "blk_virt.elf", + driver_elf: str = "blk_driver.elf", + ): + assert driver_prio > virt_prio > 0 + + self.cpu = cpu + self.driver_data_size = driver_data_size + self.virt = None + self.virt_elf = virt_elf + driver = ProtectionDomain( + sdf, + "blk_driver", + driver_elf, + scheduling=SchedulingProperties(driver_prio), + cpu=self.cpu, + ) + super().__init__( + sdf, driver, "blk", dev_compatible, dev_dt_path, magic="sDDF" + chr(0x1) + ) + + # Client config dict. Maps client PD object -> options. + self.client_blk_configs: Dict[ProtectionDomain, BlkClientOptions] = {} + + # 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_conn = None + self.client_config_protos = [] + self.construct_infrastructure(virt_prio) + + def construct_infrastructure(self, virt_prio: int): + self.virt = ProtectionDomain( + self.sdf, + "blk_virt", + self.virt_elf, + scheduling=SchedulingProperties(virt_prio), + cpu=self.cpu, + ) + + strg_info_mr = MemoryRegion( + self.sdf, "blk_driver_storage_info", BLK_STORAGE_INFO_SZ + ) + self.driver_info_map = self.driver.create_automap( + strg_info_mr, Map.Permissions(r=True, w=True) + ) + self.virt_info_map = self.virt.create_automap( + strg_info_mr, Map.Permissions(r=True, w=False) + ) + + # used in blk/components/partitioning.c + self.driver_data_mr = MemoryRegion( + self.sdf, "blk_driver_data", self.driver_data_size, physical=True + ) + + # We can't create the request or response queues now since their size depends + # on the number of clients. We do it after connecting clients instead. + self.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), + ) + + def create_driver_virt_connection(self): + driver_q_capacity = sum( + [ + self.client_blk_configs[cc].queue_capacity + for cc in self.client_blk_configs + ] + ) + assert driver_q_capacity > 0 + driver_q_mr_sz = driver_q_capacity * 128 + + # Make maps from data region created in create_infrastructure + virt_data_map = self.virt.create_automap( + self.driver_data_mr, Map.Permissions(r=True, w=True) + ) + + # queue regions + driver_req_mr = MemoryRegion(self.sdf, "blk_driver_request", driver_q_mr_sz) + driver_resp_mr = MemoryRegion(self.sdf, "blk_driver_response", driver_q_mr_sz) + + driver_req_map = self.driver.create_automap( + driver_req_mr, Map.Permissions(r=True, w=True) + ) + driver_resp_map = self.driver.create_automap( + driver_resp_mr, Map.Permissions(r=True, w=True) + ) + virt_req_map = self.virt.create_automap( + driver_req_mr, Map.Permissions(r=True, w=True) + ) + virt_resp_map = self.virt.create_automap( + driver_resp_mr, Map.Permissions(r=True, w=True) + ) + + # Create driver config + driver_virt_conn = self.blk_connection_resource_factory( + self.driver_info_map, + driver_req_map, + driver_resp_map, + self.driver_virt_ch.id_for_pd(self.driver), + driver_q_capacity, + ) + self.driver_config = self.blk_driver_config_factory( + self.driver, BLK_PROTOCOL_MAGIC, driver_virt_conn + ) + + # Create virt's driver config + virt_driver_conn = self.blk_connection_resource_factory( + self.virt_info_map, + virt_req_map, + virt_resp_map, + self.driver_virt_ch.id_for_pd(self.virt), + driver_q_capacity, + ) + + # Store a tuple of args to the factory, since the data MR isn't assigned a paddr until + # assembly time. + self.virt_driver_config_proto = (virt_driver_conn, virt_data_map) + + def add_client( + self, + client: ProtectionDomain, + partition_number: int, + queue_capacity: Optional[int] = BlkClientOptions.queue_capacity, + data_size: Optional[int] = BlkClientOptions.data_size, + ): + self.client_blk_configs[client] = BlkClientOptions( + partition_number, queue_capacity, data_size + ) + super().add_client(client) + + def connect_clients(self): + assert self.virt is not None + assert self.driver is not None + + virt_client_struct_protos = [] + virt_rx_client_conns = [] + client_config_protos = [] + + for c in self.clients: + if c.priority >= self.virt.priority: + raise SubsystemBuildError( + f"Client {c} has a priority higher than virt's " + f"({self.virt.priority})!" + ) + cfg = self.client_blk_configs[c] + assert cfg is not None + + strg_info_mr = MemoryRegion( + self.sdf, f"blk_client_{c.name}_storage_info", BLK_STORAGE_INFO_SZ + ) + virt_strg_map = self.virt.create_automap( + strg_info_mr, Map.Permissions(r=True, w=True) + ) + client_strg_map = c.create_automap( + strg_info_mr, Map.Permissions(r=True, w=False) + ) + + queue_mr_sz = cfg.queue_capacity * 128 + req_mr = MemoryRegion(self.sdf, f"blk_client_{c.name}_request", queue_mr_sz) + resp_mr = MemoryRegion( + self.sdf, f"blk_client_{c.name}_response", queue_mr_sz + ) + data_mr = MemoryRegion( + self.sdf, f"blk_client_{c.name}_data", cfg.data_size, physical=True + ) + + client_req_map = c.create_automap(req_mr, Map.Permissions(r=True, w=True)) + client_resp_map = c.create_automap(resp_mr, Map.Permissions(r=True, w=True)) + client_data_map = c.create_automap(data_mr, Map.Permissions(r=True, w=True)) + virt_req_map = self.virt.create_automap( + req_mr, Map.Permissions(r=True, w=True) + ) + virt_resp_map = self.virt.create_automap( + resp_mr, Map.Permissions(r=True, w=True) + ) + virt_data_map = self.virt.create_automap( + data_mr, Map.Permissions(r=True, w=True) + ) + + ch = Channel( + self.sdf, + Channel.End(self.virt, can_notify=True, can_pp=False), + Channel.End(c, can_notify=True, can_pp=False), + ) + + virt_conn = self.blk_connection_resource_factory( + virt_strg_map, + virt_req_map, + virt_resp_map, + ch.id_for_pd(self.virt), + cfg.queue_capacity, + ) + client_conn = self.blk_connection_resource_factory( + client_strg_map, + client_req_map, + client_resp_map, + ch.id_for_pd(c), + cfg.queue_capacity, + ) + + # Store the args to the config struct factories now, but don't + # make the config structs until `generate_config_structs` is called. + virt_client_struct_protos.append( + (virt_data_map, virt_conn, cfg.partition_number) + ) + + client_config_protos.append( + (c, BLK_PROTOCOL_MAGIC, client_conn, client_data_map) + ) + + self.client_config_protos = client_config_protos + self.virt_client_struct_protos = virt_client_struct_protos + + # Create driver-virt queues now that we know how they should be sized. + self.create_driver_virt_connection() + + def x86_resources(self): + # Nothing needed for now? + ... + + def generate_config_structs(self): + # Assemble configs that depended on an unassigned paddr, now that + # Acacia has assigned all paddrs. + client_configs = [ + self.blk_client_config_factory(*c) for c in self.client_config_protos + ] + virt_client_structs = [ + self.blk_virt_client_config_factory(*vc) + for vc in self.virt_client_struct_protos + ] + self.virt_config = self.blk_virt_config_factory( + self.virt, + BLK_PROTOCOL_MAGIC, + self.blk_virt_driver_config_factory(*self.virt_driver_config_proto), + virt_client_structs, + ) + return ( + super().generate_config_structs() + + [self.driver_config, self.virt_config] + + client_configs + ) + + # ### connection config struct factory functions ### + + def blk_connection_resource_factory( + self, + strg_info_map: Map, + req_map: Map, + resp_map: Map, + ch_id: int, + num_buffers: int, + ) -> ConfigStruct: + fields = { + "storage_info": RegionResourceFactory(strg_info_map), + "req_queue": RegionResourceFactory(req_map), + "resp_queue": RegionResourceFactory(resp_map), + "num_buffers": num_buffers, + "id": ch_id, + } + return ConfigStruct("blk_connection_resource_t", fields=fields) + + def blk_driver_config_factory( + self, + driver_pd: ProtectionDomain, + magic: str, + virt_connection: ConfigStruct, + ) -> ConfigStruct: + fields = {"magic": magic, "virt": virt_connection} + return ConfigStruct( + "blk_driver_config_t", + target_file=driver_pd.prog_image, + section_name="blk_driver_config", + fields=fields, + ) + + def blk_virt_client_config_factory( + self, data_map: Map, conn: ConfigStruct, partition_no: int + ) -> ConfigStruct: + """ + Config telling the virt about a client. + """ + fields = { + "conn": conn, + "data": DeviceRegionResourceFactory( + RegionResourceFactory(data_map), data_map.mr.paddr + ), + "partition": partition_no, + } + return ConfigStruct("blk_virt_client_t", fields=fields) + + def blk_virt_driver_config_factory( + self, driver_conn: ConfigStruct, data_map: Map + ) -> ConfigStruct: + """ + Config telling virt about the driver. + """ + fields = { + "conn": driver_conn, + "data": DeviceRegionResourceFactory( + RegionResourceFactory(data_map), data_map.mr.paddr + ), + } + return ConfigStruct("blk_virt_client_t", fields=fields) + + def blk_virt_config_factory( + self, + virt_pd: ProtectionDomain, + magic: str, + virt_driver_config: ConfigStruct, + virt_client_config_protos: List[ConfigStruct], + ) -> ConfigStruct: + assert len(virt_client_config_protos) == len(self.clients) + fields = { + "magic": magic, + "num_clients": len(virt_client_config_protos), + "driver": virt_driver_config, + "clients": virt_client_config_protos, + } + return ConfigStruct( + "blk_virt_config_t", + target_file=virt_pd.prog_image, + section_name="blk_virt_config", + fields=fields, + ) + + def blk_client_config_factory( + self, + client_pd: ProtectionDomain, + magic, + virt_connection: ConfigStruct, + data_map: Map, + ) -> ConfigStruct: + fields = { + "magic": magic, + "virt": virt_connection, + "data": RegionResourceFactory(data_map), + } + return ConfigStruct( + "blk_client_config_t", + target_file=client_pd.prog_image, + section_name="blk_client_config", + fields=fields, + ) + + +# Driver configs +def add_driver_config(driver_name: str, config: sDDFDriverConfig): + sDDFDriverManifest().add_driver_config(sDDFBlk, driver_name, config) + + +add_driver_config( + "imx", + sDDFDriverConfig( + compatible=["fsl,imx8mq-usdhc", "fsl,imx7d-usdhc"], + regions=[DTSRegion("regs", "rw", 65536, 0)], + irqs=[DTSIRQ(0)], + ), +) + +# virtio +add_driver_config( + "virtio", + sDDFDriverConfig( + compatible=["virtio,mmio"], + regions=[ + DTSRegion("regs", "rw", 4096, 0), + DTSRegion("virtio_headers", size=65536), + DTSRegion("virtio_metadata", size=2097152), + ], + irqs=[DTSIRQ(0)], + ), +) diff --git a/acacia_sddf/board.py b/acacia_sddf/board.py new file mode 100644 index 000000000..8ac2b3d8b --- /dev/null +++ b/acacia_sddf/board.py @@ -0,0 +1,189 @@ +# 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("fsl,imx8mq-usdhc", "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", "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("virtio,mmio", "soc/virtio_mmio@10001000"), + blk=DriverDouble("virtio,mmio", "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..aecab9e30 --- /dev/null +++ b/acacia_sddf/driver_manifest.py @@ -0,0 +1,92 @@ +# 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..dda3edd2d --- /dev/null +++ b/acacia_sddf/i2c.py @@ -0,0 +1,367 @@ +# 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 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, + sdf: System, + dev_compatible: str, + dev_dt_path: str, + driver_prio: int, + virt_prio: int, + cpu: Optional[int] = None, + virt_elf: str = "i2c_virt.elf", + driver_elf: str = "i2c_driver.elf", + ): + self.sdf = sdf + self.cpu = cpu + driver = ProtectionDomain( + self.sdf, + "i2c_driver", + driver_elf, + scheduling=SchedulingProperties(driver_prio), + cpu=self.cpu, + ) + + super().__init__( + sdf, driver, "i2c", dev_compatible, dev_dt_path, magic="sDDF" + chr(0x1) + ) + + # Internal bookkeeping + self.virt = None + self.virt_elf = virt_elf + + # 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 = [] + 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")) + + self.construct_infrastructure(virt_prio) + + def construct_infrastructure(self, virt_prio): + self.virt = ProtectionDomain( + self.sdf, + "i2c_virt", + self.virt_elf, + scheduling=SchedulingProperties(virt_prio), + cpu=self.cpu, + ) + + # Make queues + 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) + ) + 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( + self.sdf, + 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( + self.sdf, + 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(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)) + 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_config] + virt_resources = [self.virt_config] + return ( + super().generate_config_structs() + + 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 + ) -> 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..549c5372f --- /dev/null +++ b/acacia_sddf/sddf.py @@ -0,0 +1,238 @@ +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause +import sys, os +from typing import List, Optional, Tuple +from abc import abstractmethod +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, + system: System, + driver_pd: ProtectionDomain, + class_name: str, + dev_compatible: str, + dev_dt_path: str, + magic: str, + ): + super().__init__(system, class_name) + self.driver = driver_pd + 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" + ) + self.create_dtb_resources() + 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.sddf_driver_config = matching_configs[0] + self.create_dtb_resources() + + def create_dtb_resources(self) -> 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`. + """ + # track fields to store in DeviceResources. tuples of vaddr, offset + self.__region_maps = [] + self.__irq_ids = [] + 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() + return + + for region in self.sddf_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( + self.sdf, region_name, mr_sz, paddr=d_paddr, cached=False + ) + else: + # This is a physical region that needs an arbitrary paddr. We make it now + # and acacia assigns a paddr upon calling `System.assemble`. We make the + # config structs in `generate_config_structs` - Acacia only calls it AFTER + # assembling, ensuring that our MRs have a paddr in the config struct. + mr = MemoryRegion(self.sdf, region_name, region.size, physical=True) + d_reg_offset = 0 + + # Second: set up map + d_map = self.driver.create_automap( + mr, region.perms if region.perms else "rw" + ) + self.__region_maps.append((d_map, d_reg_offset)) + + # 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.sddf_driver_config.irqs)) != 0: + raise RuntimeError( + f"Driver config expects {t} irqs but none found in node!" + ) + + for irq in self.sddf_driver_config.irqs: + dt_irq = irqs_from_prop[irq.dt_index] + self.__irq_ids.append(self.driver.add_irq(dt_irq)) + + def generate_config_structs(self): + """ + Returns config structs as specified by DTB and driver config. We need to make + MRs before Acacia calls `generate_config_structs` on each subsystem to ensure + physical MRs without an explicit paddr get assigned an address in time. + """ + return [ + DeviceResourcesFactory( + self.driver_magic, + self.__region_maps, + self.__irq_ids, + target_file=self.driver.prog_image, + ) + ] + + 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} + return ConfigStruct("region_resource_t", section_name=section_name, fields=fields) + + +def DeviceRegionResourceFactory(region: ConfigStruct, io_addr: int): + assert io_addr is not None + 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..c3f14033c --- /dev/null +++ b/acacia_sddf/serial.py @@ -0,0 +1,538 @@ +# 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, + sdf: System, + dev_compatible: str, + dev_dt_path: str, + 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", + ): + 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 + driver = ProtectionDomain( + sdf, + "serial_driver", + driver_elf, + scheduling=SchedulingProperties(driver_prio), + cpu=self.cpu, + ) + super().__init__( + sdf, driver, "serial", dev_compatible, dev_dt_path, magic="sDDF" + chr(0x1) + ) + 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 + + # 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( + self.sdf, + "serial_virt_tx", + self.virt_tx_elf, + 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, + scheduling=SchedulingProperties(virt_rx_prio), + cpu=self.cpu, + ) + + 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, + cached=True, + ) + + 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( + self.sdf, + Channel.End(self.driver, can_notify=True, can_pp=False), + Channel.End(self.virt_tx, can_notify=True, can_pp=False), + ) + + 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( + 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) + ) + 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( + self.sdf, + Channel.End(self.driver, can_notify=True, can_pp=False), + Channel.End(self.virt_rx, can_notify=True, can_pp=False), + ) + + 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( + 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) + ) + 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( + self.sdf, + Channel.End(self.virt_tx, can_notify=True, can_pp=False), + Channel.End(c, can_notify=True, can_pp=False), + ) + + 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( + 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) + ) + 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( + self.sdf, + Channel.End(self.virt_rx, can_notify=True, can_pp=False), + Channel.End(c, can_notify=True, can_pp=False), + ) + + 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 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_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 ( + super().generate_config_structs() + + [self.driver_config] + + 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..157076e08 --- /dev/null +++ b/acacia_sddf/timer.py @@ -0,0 +1,194 @@ +# 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, + sdf: System, + dev_compatible: str, + dev_dt_path: str, + driver_prio: int = 254, + cpu: Optional[int] = None, + driver_elf: str = "timer_driver.elf", + ): + driver = ProtectionDomain( + sdf, + "timer_driver", + driver_elf, + scheduling=SchedulingProperties(driver_prio, passive=True), + ) + self.cpu = cpu + super().__init__( + sdf, driver, "timer", dev_compatible, dev_dt_path, magic="sDDF" + chr(1) + ) + + 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 super().generate_config_structs() + 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 + 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 + # 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/blk/blk.mk b/examples/blk/blk.mk index 83d9c1791..92b622db8 100644 --- a/examples/blk/blk.mk +++ b/examples/blk/blk.mk @@ -97,29 +97,29 @@ client.elf: client.o libsddf_util.a $(SYSTEM_FILE): $(METAPROGRAM) $(IMAGES) $(DTB) ifneq ($(strip $(DTS)),) $(PYTHON) \ - $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) \ + $(METAPROGRAM) --board $(MICROKIT_BOARD) \ --dtb $(DTB) --output . --sdf $(SYSTEM_FILE) $(PARTITION_ARG) \ $${BLK_NEED_TIMER:+--need_timer} \ $${NVME:+--nvme} else $(PYTHON) \ - $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) \ + $(METAPROGRAM) --board $(MICROKIT_BOARD) \ --output . --sdf $(SYSTEM_FILE) $(PARTITION_ARG) \ $${BLK_NEED_TIMER:+--need_timer} \ $${NVME:+--nvme} endif ifdef BLK_NEED_TIMER $(OBJCOPY) --update-section .device_resources=timer_driver_device_resources.data timer_driver.elf - $(OBJCOPY) --update-section .timer_client_config=timer_client_blk_driver.data blk_driver.elf + $(OBJCOPY) --update-section .timer_client_config=blk_driver_timer_client_config.data blk_driver.elf endif $(OBJCOPY) --update-section .device_resources=blk_driver_device_resources.data blk_driver.elf - $(OBJCOPY) --update-section .blk_driver_config=blk_driver.data blk_driver.elf - $(OBJCOPY) --update-section .blk_virt_config=blk_virt.data blk_virt.elf - $(OBJCOPY) --update-section .blk_client_config=blk_client_client.data client.elf + $(OBJCOPY) --update-section .blk_driver_config=blk_driver_blk_driver_config.data blk_driver.elf + $(OBJCOPY) --update-section .blk_virt_config=blk_virt_blk_virt_config.data blk_virt.elf + $(OBJCOPY) --update-section .blk_client_config=client_blk_client_config.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 .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 touch $@ $(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) diff --git a/examples/blk/meta.py b/examples/blk/meta.py index 9ed5f28d6..ac7381e4b 100644 --- a/examples/blk/meta.py +++ b/examples/blk/meta.py @@ -4,84 +4,56 @@ import argparse from typing import List, Optional from dataclasses import dataclass -from sdfgen import SystemDescription, Sddf, DeviceTree - -sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../tools/meta") +from acacia import ( + System, + ProtectionDomain, + MemoryRegion, + Channel, + DeviceTreeBlob, + Map, + IOPort, + IrqIoapic, ) -from board import BOARDS +from acacia.arch import aarch64, x86_64, riscv64 -ProtectionDomain = SystemDescription.ProtectionDomain +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) +from acacia_sddf import BOARDS, sDDFBlk, sDDFSerial, sDDFTimer def generate( sdf_file: str, output_dir: str, - dtb: Optional[DeviceTree], + dtb: Optional[DeviceTreeBlob], need_timer: bool, nvme: bool, # hack to select NVMe or Virtio ): - uart_node = None - blk_node = None - timer_node = None - if dtb is not None: - uart_node = dtb.node(board.serial) - assert uart_node is not None - blk_node = dtb.node(board.blk) - assert blk_node is not None - timer_node = dtb.node(board.timer) - assert timer_node is not None - - 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 - ) - - serial_system = Sddf.Serial( - sdf, uart_node, serial_driver, serial_virt_tx, enable_color=False + client = ProtectionDomain(sdf, "client", "client.elf", priority=1) + serial = sDDFSerial( + sdf, + board.serial.compatible, + board.serial.node_path, + driver_prio=200, + virt_tx_prio=199, + allow_rx=False, + enable_color=False, + baud_rate=board.baud_rate if board.baud_rate else 115200, ) + serial.add_client(client) - if board.arch == SystemDescription.Arch.X86_64: - serial_port = SystemDescription.IoPort(0x3F8, 8, 0) - serial_driver.add_ioport(serial_port) + blk = sDDFBlk(sdf, board.blk.compatible, board.blk.node_path, 200, 199) + partition = int(args.partition) if args.partition else board.partition + blk.add_client(client, partition) - blk_driver = ProtectionDomain( - "blk_driver", "blk_driver.elf", priority=200, stack_size=0x2000 - ) - blk_virt = ProtectionDomain( - "blk_virt", "blk_virt.elf", priority=199, stack_size=0x2000 - ) - client = ProtectionDomain("client", "client.elf", priority=1) + blk_driver = blk.driver + blk_virt = blk.virt if need_timer: - timer_driver = ProtectionDomain( - "timer_driver", "timer_driver.elf", priority=201 - ) - timer_system = sddf.Timer(sdf, timer_node, timer_driver) - timer_system.add_client(blk_driver) - if board.arch == SystemDescription.Arch.X86_64: - hpet_irq = SystemDescription.IrqMsi( - pci_bus=0, pci_device=0, pci_func=0, vector=0, handle=0, id=0 - ) - timer_driver.add_irq(hpet_irq) - - 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) - - blk_system = Sddf.Blk(sdf, blk_node, blk_driver, blk_virt) - partition = int(args.partition) if args.partition else board.partition - blk_system.add_client(client, partition=partition) + timer = sDDFTimer(sdf, board.timer.compatible, board.timer.node_path) + timer.add_client(blk_driver) if nvme: # Queue descriptors accessed via DMA so we map these regions as uncached. - if board.arch == SystemDescription.Arch.RISCV64: + if board.arch == riscv64: dma_regions = [ ("nvme_admin_sq", 0x9EDF0000, 0x20100000, 0x1000), ("nvme_admin_cq", 0x9EDF1000, 0x20101000, 0x1000), @@ -100,126 +72,91 @@ def generate( ("nvme_prp_list", 0x5F800000, 0x20200000, 0x80000), ] for name, paddr, vaddr, size in dma_regions: - mr = SystemDescription.MemoryRegion(sdf, name, size, paddr=paddr) - sdf.add_mr(mr) - blk_driver.add_map(SystemDescription.Map(mr, vaddr, "rw", cached=False)) + mr = MemoryRegion(sdf, name, size, paddr=paddr, cached=False) + blk_driver.add_map(Map(mr, vaddr, "rw")) - if board.arch == SystemDescription.Arch.X86_64: + if board.arch == x86_64: # BAR0: MMIO (always uncached) - nvme_bar0_mr = SystemDescription.MemoryRegion( - sdf, "nvme_bar0", 0x4000, paddr=0xFEBD4000 + nvme_bar0_mr = MemoryRegion( + sdf, "nvme_bar0", 0x4000, paddr=0xFEBD4000, cached=False ) # IRQ - nvme_irq = SystemDescription.IrqIoapic(ioapic_id=0, pin=10, vector=1, id=17) + nvme_irq = IrqIoapic(ioapic_id=0, pin=10, vector=1, id=17) - elif board.arch == SystemDescription.Arch.AARCH64: + elif board.arch == aarch64: # BAR0: MMIO (always uncached) - nvme_bar0_mr = SystemDescription.MemoryRegion( - sdf, "nvme_bar0", 0x4000, paddr=0x10000000 + nvme_bar0_mr = MemoryRegion( + sdf, "nvme_bar0", 0x4000, paddr=0x10000000, cached=False ) # ECAM config page: MMIO (always uncached) - nvme_ecam_mr = SystemDescription.MemoryRegion( - sdf, "nvme_ecam", 0x1000, paddr=0x4010020000 - ) - sdf.add_mr(nvme_ecam_mr) - blk_driver.add_map( - SystemDescription.Map(nvme_ecam_mr, 0x20300000, "rw", cached=False) + nvme_ecam_mr = MemoryRegion( + sdf, "nvme_ecam", 0x1000, paddr=0x4010020000, cached=False ) + blk_driver.add_map(Map(nvme_ecam_mr, 0x20300000, "rw")) # IRQ: slot 4 INT_A -> PCI irq line (1+4)%4 = 1 -> SPI 3 -> GIC IRQ 35 - nvme_irq = SystemDescription.IrqConventional(irq=35, id=35) + nvme_irq = IrqConventional(irq=35, id=35) - else: # board.arch == SystemDescription.Arch.RISCV64: + else: # board.arch == riscv64: # BAR0: MMIO (always uncached) - nvme_bar0_mr = SystemDescription.MemoryRegion( - sdf, "nvme_bar0", 0x4000, paddr=0x40000000 + nvme_bar0_mr = MemoryRegion( + sdf, "nvme_bar0", 0x4000, paddr=0x40000000, cached=False ) # ECAM config page: MMIO (always uncached) - nvme_ecam_mr = SystemDescription.MemoryRegion( - sdf, "nvme_ecam", 0x1000, paddr=0x30020000 - ) - sdf.add_mr(nvme_ecam_mr) - blk_driver.add_map( - SystemDescription.Map(nvme_ecam_mr, 0x20300000, "rw", cached=False) + nvme_ecam_mr = MemoryRegion( + sdf, "nvme_ecam", 0x1000, paddr=0x30020000, cached=False ) + blk_driver.add_map(Map(nvme_ecam_mr, 0x20300000, "rw")) # IRQ: slot 4 INT_A -> PCI irq line (0+4)%4 = 0 -> PLIC IRQ 0x20 = 32 - nvme_irq = SystemDescription.IrqConventional(irq=32, id=32) + nvme_irq = IrqConventional(irq=32, id=32) - sdf.add_mr(nvme_bar0_mr) - blk_driver.add_map( - SystemDescription.Map(nvme_bar0_mr, 0x20000000, "rw", cached=False) - ) + blk_driver.add_map(Map(nvme_bar0_mr, 0x20000000, "rw")) blk_driver.add_irq(nvme_irq) - if board.arch == SystemDescription.Arch.X86_64: + if board.arch == x86_64: # IO ports - pci_config_addr_port = SystemDescription.IoPort(0xCF8, 4, 1) + pci_config_addr_port = IOPort(0xCF8, 4, 1) blk_driver.add_ioport(pci_config_addr_port) - pci_config_data_port = SystemDescription.IoPort(0xCFC, 4, 2) + pci_config_data_port = IOPort(0xCFC, 4, 2) blk_driver.add_ioport(pci_config_data_port) # x86 virtio regions if not nvme: - blk_requests_mr = SystemDescription.MemoryRegion( + blk_requests_mr = MemoryRegion( sdf, "virtio_requests", 65536, paddr=0x5FDF0000 ) - sdf.add_mr(blk_requests_mr) - blk_requests_map = SystemDescription.Map(blk_requests_mr, 0x20200000, "rw") + blk_requests_map = Map(blk_requests_mr, 0x20200000, "rw") blk_driver.add_map(blk_requests_map) - blk_virtio_metadata_mr = SystemDescription.MemoryRegion( + blk_virtio_metadata_mr = MemoryRegion( sdf, "virtio_metadata", 65536, paddr=0x5FFF0000 ) - sdf.add_mr(blk_virtio_metadata_mr) - blk_virtio_metadata_map = SystemDescription.Map( - blk_virtio_metadata_mr, 0x20210000, "rw" - ) + blk_virtio_metadata_map = Map(blk_virtio_metadata_mr, 0x20210000, "rw") blk_driver.add_map(blk_virtio_metadata_map) - virtio_blk_regs = SystemDescription.MemoryRegion( - sdf, "virtio_blk_regs", 0x4000, paddr=0xFE000000 - ) - sdf.add_mr(virtio_blk_regs) - virtio_blk_regs_map = SystemDescription.Map( - virtio_blk_regs, 0x6000_0000, "rw", cached=False + virtio_blk_regs = MemoryRegion( + sdf, "virtio_blk_regs", 0x4000, paddr=0xFE000000, cached=False ) + virtio_blk_regs_map = Map(virtio_blk_regs, 0x6000_0000, "rw") blk_driver.add_map(virtio_blk_regs_map) - virtio_blk_irq = SystemDescription.IrqIoapic( - ioapic_id=0, pin=11, vector=1, id=17 - ) + virtio_blk_irq = IrqIoapic(ioapic_id=0, pin=11, vector=1, id=17) blk_driver.add_irq(virtio_blk_irq) - - serial_system.add_client(client) - - pds = [serial_driver, serial_virt_tx, blk_driver, blk_virt, client] - if need_timer: - pds += [timer_driver] - for pd in pds: - sdf.add_pd(pd) - - assert blk_system.connect() - assert blk_system.serialise_config(output_dir) - assert serial_system.connect() - assert serial_system.serialise_config(output_dir) - if need_timer: - 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__": parser = argparse.ArgumentParser() parser.add_argument("--dtb", required=False) - 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) @@ -231,12 +168,6 @@ def generate( 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()) - + dtb = DeviceTreeBlob(args.dtb) if args.dtb else None + sdf = System(board.arch, board.paddr_top, dtb) generate(args.sdf, args.output, dtb, args.need_timer, args.nvme) 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..ebb1d654f 100644 --- a/examples/i2c/meta.py +++ b/examples/i2c/meta.py @@ -4,82 +4,45 @@ 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") -) -from board import BOARDS +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) +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 +def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): + client_pn532 = ProtectionDomain(sdf, "client_pn532", "client_pn532.elf", priority=1) + client_ds3231 = ProtectionDomain( + 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) - - 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 + i2c = sDDFI2C( + sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=199 ) - 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: - 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) + i2c.add_client(client_ds3231) + i2c.add_client(client_pn532) + + 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, + 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) - 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 +57,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/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..fc3231de6 100644 --- a/examples/i2c_bus_scan/meta.py +++ b/examples/i2c_bus_scan/meta.py @@ -4,78 +4,40 @@ 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") -) -from board import BOARDS +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) -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) - # 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=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) - - 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_scan) +def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): + client_scan = ProtectionDomain(sdf, "client_scan", "client_scan.elf", priority=1) - 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 + i2c = sDDFI2C( + sdf, board.i2c.compatible, board.i2c.node_path, driver_prio=200, virt_prio=199 ) - 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) + i2c.add_client(client_scan) + + 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, + 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) - 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 +52,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..ce1e25cc1 100644 --- a/examples/ina219/meta.py +++ b/examples/ina219/meta.py @@ -4,68 +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 -ProtectionDomain = SystemDescription.ProtectionDomain -MemoryRegion = SystemDescription.MemoryRegion -Map = SystemDescription.Map +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: 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) - 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) +def generate(sdf_file: str, output_dir: str, dtb: DeviceTreeBlob): + client_ina = ProtectionDomain(sdf, "client_ina", "client_ina.elf", priority=1) - 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 ) - serial_system.add_client(client_ina) - - pds = [ - serial_driver, - serial_virt_tx, - timer_driver, - i2c_driver, - i2c_virt, - client_ina, - ] - 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) + i2c.add_client(client_ina) + + 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, + 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) - 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 +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/serial/meta.py b/examples/serial/meta.py index 183fd0d40..93b89e152 100644 --- a/examples/serial/meta.py +++ b/examples/serial/meta.py @@ -4,72 +4,35 @@ 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") -) -from board import BOARDS +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) +from acacia_sddf import sDDFSerial, BOARDS -ProtectionDomain = SystemDescription.ProtectionDomain +def generate(sdf_file: str, output_dir: str): + client0 = ProtectionDomain(sdf, "client0", "client0.elf", priority=1) + client1 = ProtectionDomain(sdf, "client1", "client1.elf", priority=1) -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) - - 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( + serial = sDDFSerial( sdf, - serial_node, - serial_driver, - serial_virt_tx, - virt_rx=serial_virt_rx, + board.serial.compatible, + board.serial.node_path, + driver_prio=200, + virt_tx_prio=199, + allow_rx=True, enable_color=True, - baud_rate=baud_rate, + 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: - sdf.add_pd(pd) - assert serial_system.connect() - assert serial_system.serialise_config(output_dir) + for pd in [client0, client1]: + serial.add_client(pd) - with open(f"{output_dir}/{sdf_file}", "w+") as f: - f.write(sdf.render()) + 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__": @@ -83,13 +46,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..73880ac97 100644 --- a/examples/timer/meta.py +++ b/examples/timer/meta.py @@ -3,43 +3,30 @@ import os import sys import argparse -from sdfgen import SystemDescription, Sddf, DeviceTree import importlib - -sys.path.append( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../tools/meta") -) +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") -BOARDS = board_module.BOARDS - -ProtectionDomain = SystemDescription.ProtectionDomain +# board_module = importlib.import_module("board") +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, dtb: DeviceTree): - timer_node = None - timer_driver = ProtectionDomain("timer_driver", "timer_driver.elf", priority=253) - 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 +def generate(sdf_file: str, output_dir: str): + client = ProtectionDomain(sdf, "client", "client.elf", priority=1) - timer_system = Sddf.Timer(sdf, timer_node, timer_driver) - timer_system.add_client(client) + timer = sDDFTimer(sdf, board.timer.compatible, board.timer.node_path) + timer.add_client(client) - pds = [timer_driver, client] - for pd in pds: - sdf.add_pd(pd) + # Add HPET if x86 + if board.arch == x86_64: + timer.add_x86_hpet(sdf) - 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()) + 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__": @@ -54,12 +41,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, - ), -]