diff --git a/.reuse/dep5 b/.reuse/dep5 index 0b6cc3052..64d8988e7 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -12,6 +12,7 @@ Files: examples/i2c_bus_scan/build.zig.zon examples/serial/build.zig.zon examples/timer/build.zig.zon + examples/gpio/build.zig.zon examples/blk/build.zig.zon examples/blk/basic_data.txt examples/blk/basic_data.h @@ -42,6 +43,7 @@ Files: drivers/timer/bcm2835/config.json drivers/timer/apb_timer/config.json drivers/timer/rk3568/config.json + drivers/gpio/imx/config.json Copyright: UNSW License: BSD-2-Clause diff --git a/build.zig b/build.zig index 24761b47a..e61bb24dd 100644 --- a/build.zig +++ b/build.zig @@ -29,6 +29,11 @@ const DriverClass = struct { rk3568, }; + const Gpio = enum { + imx, + meson, + }; + const Network = enum { imx, meson, @@ -186,6 +191,35 @@ fn addTimerDriver( return driver; } +fn addGpioDriver( + b: *std.Build, + gpio_config_include: LazyPath, + util: *std.Build.Step.Compile, + class: DriverClass.Gpio, + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, +) *std.Build.Step.Compile { + const driver = addPd(b, .{ + .name = b.fmt("driver_gpio_{s}.elf", .{@tagName(class)}), + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .strip = false, + }), + }); + const source = b.fmt("drivers/gpio/{s}/gpio.c", .{@tagName(class)}); + driver.addCSourceFile(.{ + .file = b.path(source), + }); + driver.addIncludePath(gpio_config_include); + + driver.addIncludePath(b.path("include")); + driver.addIncludePath(b.path("include/microkit")); + driver.linkLibrary(util); + + return driver; +} + fn addI2cDriverDevice( b: *std.Build, util: *std.Build.Step.Compile, @@ -482,6 +516,13 @@ pub fn build(b: *std.Build) !void { // debug error if you do need a serial config but forgot to pass one in. const gpu_config_include = LazyPath{ .cwd_relative = gpu_config_include_option }; + const gpio_config_include_option = b.option([]const u8, "gpio_config_include", "Include path to gpio config header") orelse ""; + + // TODO: So ideally this is part of the meta.py file + // for now we have a config file that defines which gpio/irq is assigned to + // all of the driver channels in the gpio driver. + const gpio_config_include = LazyPath{ .cwd_relative = gpio_config_include_option }; + // Util libraries const util = b.addLibrary(.{ .name = "util", @@ -698,6 +739,12 @@ pub fn build(b: *std.Build) !void { libi2c_raw.addIncludePath(libmicrokit_include); libi2c_raw.linkLibrary(util); b.installArtifact(libi2c_raw); + // Gpio drivers + inline for (std.meta.fields(DriverClass.Gpio)) |class| { + const driver = addGpioDriver(b, gpio_config_include, util, @enumFromInt(class.value), target, optimize); + driver.linkLibrary(util_putchar_debug); + b.installArtifact(driver); + } // I2C components const i2c_virt = addPd(b, .{ diff --git a/ci/examples/gpio.py b/ci/examples/gpio.py new file mode 100755 index 000000000..e9592508f --- /dev/null +++ b/ci/examples/gpio.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# Copyright 2026, UNSW +# SPDX-License-Identifier: BSD-2-Clause + +import asyncio +from pathlib import Path +import sys + +sys.path.insert(1, Path(__file__).parents[2].as_posix()) + +from ci.lib.backends import * +from ci.lib.runner import run_single_example, matrix_product +from ci.common import TestConfig +from ci import common, matrix + +TEST_MATRIX = matrix_product( + example=["gpio"], + board=matrix.EXAMPLES["gpio"]["boards_test"], + config=matrix.EXAMPLES["gpio"]["configs"], + build_system=matrix.EXAMPLES["gpio"]["build_systems"], +) + + +def backend_fn(test_config: TestConfig, loader_img: Path) -> HardwareBackend: + backend = common.backend_fn(test_config, loader_img) + return backend + + +async def test(backend: HardwareBackend, test_config: TestConfig): + async with asyncio.timeout(60): + await wait_for_output(backend, b" ... is present!\r\n") + + +if __name__ == "__main__": + run_single_example( + test, + TEST_MATRIX, + backend_fn, + ) diff --git a/ci/matrix.py b/ci/matrix.py index 2ac96a15e..3129b1d61 100644 --- a/ci/matrix.py +++ b/ci/matrix.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: BSD-2-Clause from __future__ import annotations + from typing import TYPE_CHECKING, Any, Literal, TypedDict NO_OUTPUT_DEFAULT_TIMEOUT_S: int = 60 @@ -29,7 +30,7 @@ "hifive_p550": dict(uboot_image_started=b"Starting kernel ..."), } -EXAMPLES: dict[str, _ExampleMatrixType] = { +EXAMPLES: dict[Literal[str], _ExampleMatrixType] = { "blk": { "configs": ["debug", "release"], "build_systems": ["make", "zig"], @@ -89,6 +90,14 @@ "star64", ], }, + "gpio": { + "configs": ["debug", "release", "benchmark"], + "build_systems": ["make", "zig"], + "boards_build": [ + "maaxboard", + ], + "boards_test": [], + }, "serial": { "configs": ["debug", "release"], "build_systems": ["make", "zig"], diff --git a/drivers/gpio/imx/config.json b/drivers/gpio/imx/config.json new file mode 100644 index 000000000..18d999a6d --- /dev/null +++ b/drivers/gpio/imx/config.json @@ -0,0 +1,26 @@ +{ + "compatible": [ + "fsl,imx8mq-gpio", + "fsl,imx35-gpio" + ], + "resources": { + "regions": [ + { + "name": "regs", + "perms": "rw", + "size": 65536, + "dt_index": 0 + } + ], + "irqs": [ + { + "dt_index": 0, + "channel_id": 60 + }, + { + "dt_index": 1, + "channel_id": 61 + } + ] + } +} diff --git a/drivers/gpio/imx/gpio.c b/drivers/gpio/imx/gpio.c new file mode 100644 index 000000000..0d4deba42 --- /dev/null +++ b/drivers/gpio/imx/gpio.c @@ -0,0 +1,382 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include "gpio.h" + +// #define DEBUG_DRIVER + +#ifdef DEBUG_DRIVER +#define LOG_DRIVER(...) do{ sddf_dprintf("GPIO DRIVER|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) +#else +#define LOG_DRIVER(...) do{}while(0) +#endif + +#define LOG_DRIVER_ERR(...) do{ sddf_printf("GPIO DRIVER|ERROR: "); sddf_printf(__VA_ARGS__); }while(0) + +__attribute__((__section__(".device_resources"))) device_resources_t device_resources; + +volatile imx_gpio_regs_t *gpio_regs; + +/* Map pin to client channels */ +static int pin_subscriber[PINS_PER_BANK]; + +static void print_reg(uint32_t value) +{ + char buffer[40]; + int pos = 0; + + for (int i = 31; i >= 0; i--) { + uint32_t bit = (value >> i) & 1U; + buffer[pos++] = bit ? '1' : '0'; + + if (i % 8 == 0 && i != 0) { + buffer[pos++] = ' '; + } + } + + buffer[pos++] = '\n'; + buffer[pos] = '\0'; + + LOG_DRIVER("%s", buffer); +} + +static inline void init_pin_subscribers() +{ + for (int i = 0; i < PINS_PER_BANK; ++i) { + pin_subscriber[i] = -1; + } +} + +static void print_irq_pin_subscribers() +{ + LOG_DRIVER("IRQ Pin Subscribers:\n"); + + for (int i = 0; i < PINS_PER_BANK; ++i) { + if (pin_subscriber[i] != -1) { + LOG_DRIVER("Pin %d -> Subscriber %d\n", i, pin_subscriber[i]); + } + } +} + +static inline microkit_msginfo error_response(gpio_error_t error_code) +{ + uint32_t e = error_code | BIT(SDDF_GPIO_RESPONSE_ERROR_BIT); + return microkit_msginfo_new(e, 0); +} + +static inline bool check_irq_permission(microkit_channel ch) +{ + return gpio_driver_channel_mappings[ch].irq > 0; +} + +static void handle_gpio_irq(int ch, int start_pin, int end_pin) +{ + uint32_t clear_mask = 0; + + /* Go through pin and build up a mask of IRQs to clear. */ + for (int pin = start_pin; pin < end_pin; pin++) { + /* If the pin is unmasked and the IRQ was related to this pin, add it to the mask and notify + * the client subscribed to that pin. */ + if ((gpio_regs->imr & BIT(pin)) && (gpio_regs->isr & BIT(pin))) { + clear_mask |= BIT(pin); + microkit_notify(pin_subscriber[pin]); + } + } + + gpio_regs->imr &= ~clear_mask; + + // We want it to be cleared before the microkit acknowledges so we don't enter notified again. + microkit_deferred_irq_ack(ch); +} + +void notified(microkit_channel ch) +{ + /* + * We have two potential interrupts to deal with, one for the lower 16 pins, and another for the + * the upper 16 pins. + * There exists single interrupt lines for the device, however they do not appear to leave the GPIO + * block and go the interrupt controller, so we do not make use of them. It seems that Linux does + * not either. + */ + if (ch == device_resources.irqs[0].id) { + handle_gpio_irq(ch, 0, PINS_PER_BANK / 2); + } else if (ch == device_resources.irqs[1].id) { + handle_gpio_irq(ch, PINS_PER_BANK / 2, PINS_PER_BANK); + } else { + LOG_DRIVER("unexpected notification from channel %u\n", ch); + } +} + +static inline microkit_msginfo set(int pin, uint32_t value) +{ + if (value) { + gpio_regs->dr |= BIT(pin); + } else { + gpio_regs->dr &= ~BIT(pin); + } + + return microkit_msginfo_new(0, 0); +} + +static inline microkit_msginfo get(int pin) +{ + uint32_t value = (gpio_regs->psr >> pin) & BIT(0); + + return microkit_msginfo_new(value, 0); +} + +static inline microkit_msginfo set_direction_output(int pin, uint32_t value) +{ + if (value) { + gpio_regs->dr |= BIT(pin); + } else { + gpio_regs->dr &= ~BIT(pin); + } + + // This instruction already reads back to ensure previous instructions completion + gpio_regs->gdir |= BIT(pin); + + return microkit_msginfo_new(0, 0); +} + +static inline microkit_msginfo set_direction_input(int pin) +{ + gpio_regs->gdir &= ~BIT(pin); + + return microkit_msginfo_new(0, 0); +} + +static inline microkit_msginfo get_direction(int pin) +{ + uint32_t dir = (gpio_regs->gdir >> pin) & BIT(0); + + return microkit_msginfo_new(dir, 0); +} + +static inline microkit_msginfo set_config(int pin, uint32_t value, uint32_t argument) +{ + return error_response(SDDF_GPIO_EOPNOTSUPP); +} + +static inline microkit_msginfo irq_enable(int pin) +{ + // Clear all noise that happened before the interrupt started + gpio_regs->isr = BIT(pin); + // This instruction already reads back to ensure previous instructions completion + gpio_regs->imr |= BIT(pin); + + return microkit_msginfo_new(0, 0); +} + +// The semantic of disable also means unchecking the status register +static inline microkit_msginfo irq_disable(int pin) +{ + gpio_regs->imr &= ~BIT(pin); + + // Since it's the same peripheral a read back will mean it completes + (void)gpio_regs->imr; + + // Now that we have unmasked we uncheck the status register + // so that if we go to notified we don't process this irq if + // it was set before we unmasked + + gpio_regs->isr = BIT(pin); + + return microkit_msginfo_new(0, 0); +} + +static inline microkit_msginfo irq_set_type(int pin, uint32_t type) +{ + uint32_t shift = (pin % 16) * 2; + uint32_t icr_val = (pin < 16) ? ((gpio_regs->icr1 >> shift) & 0x3u) : ((gpio_regs->icr2 >> shift) & 0x3u); + + bool both = false; + + switch (type) { + case SDDF_IRQ_TYPE_EDGE_RISING: + icr_val = ICR_RISING_EDGE; + break; + case SDDF_IRQ_TYPE_EDGE_FALLING: + icr_val = ICR_FALLING_EDGE; + break; + case SDDF_IRQ_TYPE_LEVEL_HIGH: + icr_val = ICR_HIGH_LEVEL; + break; + case SDDF_IRQ_TYPE_LEVEL_LOW: + icr_val = ICR_LOW_LEVEL; + break; + case SDDF_IRQ_TYPE_EDGE_BOTH: + both = true; + break; + default: + return error_response(SDDF_GPIO_EINVAL); + } + + if (pin < 16) { + gpio_regs->icr1 = (gpio_regs->icr1 & ~(0x3u << shift)) | (icr_val << shift); + } else { + gpio_regs->icr2 = (gpio_regs->icr2 & ~(0x3u << shift)) | (icr_val << shift); + } + + // These instructions already read back to ensure previous instructions completion + if (both) { + gpio_regs->edge_sel |= BIT(pin); + } else { + gpio_regs->edge_sel &= ~BIT(pin); + } + + return microkit_msginfo_new(0, 0); +} + +microkit_msginfo protected(microkit_channel ch, microkit_msginfo msginfo) +{ + uint32_t label = microkit_msginfo_get_label(msginfo); + uint32_t interface_function = label & SDDF_REQUEST_INTERFACE_MASK; + uint32_t value = gpio_decode_value(label); + + // Check what pin it has + int pin = gpio_driver_channel_mappings[ch].pin; + + // Unexpected channel + if (pin < 0) { + return error_response(SDDF_GPIO_EPERM); + } + + switch (interface_function) { + case SDDF_GPIO_SET: { + return set(pin, value); + } + case SDDF_GPIO_GET: { + return get(pin); + } + case SDDF_GPIO_DIRECTION_OUTPUT: { + return set_direction_output(pin, value); + } + case SDDF_GPIO_DIRECTION_INPUT: { + return set_direction_input(pin); + } + case SDDF_GPIO_GET_DIRECTION: { + return get_direction(pin); + } + case SDDF_GPIO_SET_CONFIG: { + uint32_t argument = microkit_mr_get(0); + return set_config(pin, value, argument); + } + case SDDF_GPIO_IRQ_ENABLE: { + if (check_irq_permission(ch)) { + return irq_enable(pin); + } + return error_response(SDDF_GPIO_EPERM); + } + case SDDF_GPIO_IRQ_DISABLE: { + if (check_irq_permission(ch)) { + return irq_disable(pin); + } + return error_response(SDDF_GPIO_EPERM); + } + case SDDF_GPIO_IRQ_SET_TYPE: { + if (check_irq_permission(ch)) { + return irq_set_type(pin, value); + } + return error_response(SDDF_GPIO_EPERM); + } + default: + LOG_DRIVER("Unknown request %lu to gpio from channel %u\n", microkit_msginfo_get_label(msginfo), ch); + return error_response(SDDF_GPIO_EOPNOTSUPP); + } +} + +void validate_gpio_config() +{ + for (int ch = 0; ch < MICROKIT_MAX_CHANNELS; ch++) { + int pin = gpio_driver_channel_mappings[ch].pin; + int irq = gpio_driver_channel_mappings[ch].irq; + + // Irq without pin check + if (pin < 0 && irq >= 0) { + LOG_DRIVER_ERR("Pin must be set if IRQ is set! (ch=%d, irq=%d)\n", ch, irq); + assert(false); + } + + // Nothing to configure + if (pin < 0) { + continue; + } + + // Check a client hasn't claimed the channels we use for device interrupts + if (device_resources.irqs[0].id == ch) { + LOG_DRIVER_ERR("Client can't claim channel used for device irqs : %d\n", ch); + assert(false); + } else if (device_resources.irqs[1].id == ch) { + LOG_DRIVER_ERR("Client can't claim channel used for device irqs : %d\n", ch); + assert(false); + } + + // Check pin is valid number + if (pin >= PINS_PER_BANK) { + LOG_DRIVER_ERR("Invalid pin number : %d\n", pin); + assert(false); + } + + // Unique-pin check + int seen = 0; + for (int ch_2 = 0; ch_2 < MICROKIT_MAX_CHANNELS; ch_2++) { + if (gpio_driver_channel_mappings[ch_2].pin == pin) { + seen++; + } + } + if (seen != 1) { + LOG_DRIVER_ERR("pin %d mapped %d times (must be exactly once)\n", pin, seen); + assert(false); + } + + if (irq < 0) { + continue; + } + + // For fast lookups in notify + pin_subscriber[pin] = ch; + + // Since we can only bind each pin to one designated interrupt we don't validate the irq picked + // Other then it being above 0 + } +} + +void disable_all_interrupts() +{ + gpio_regs->imr = 0; + + microkit_irq_ack(device_resources.irqs[0].id); + microkit_irq_ack(device_resources.irqs[1].id); +} + +void init(void) +{ + LOG_DRIVER("Starting.\n"); + + assert(device_resources_check_magic(&device_resources)); + + assert(device_resources.num_irqs == 2); + assert(device_resources.num_regions == 1); + + gpio_regs = (imx_gpio_regs_t *)device_resources.regions[0].region.vaddr; + + init_pin_subscribers(); + + validate_gpio_config(); + + disable_all_interrupts(); + + print_irq_pin_subscribers(); + + LOG_DRIVER("Finished.\n"); +} diff --git a/drivers/gpio/imx/gpio.h b/drivers/gpio/imx/gpio.h new file mode 100644 index 000000000..6156a9802 --- /dev/null +++ b/drivers/gpio/imx/gpio.h @@ -0,0 +1,28 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +struct imx_gpio_regs { + uint32_t dr; /* 0x00 Data Register */ + uint32_t gdir; /* 0x04 Direction Register */ + uint32_t psr; /* 0x08 Pad Status Register */ + uint32_t icr1; /* 0x0C Interrupt Config 1 */ + uint32_t icr2; /* 0x10 Interrupt Config 2 */ + uint32_t imr; /* 0x14 Interrupt Mask */ + uint32_t isr; /* 0x18 Interrupt Status */ + uint32_t edge_sel; /* 0x1C Edge Select */ +}; + +typedef volatile struct imx_gpio_regs imx_gpio_regs_t; + +#define ICR_LOW_LEVEL 0x0 /* 00 */ +#define ICR_HIGH_LEVEL 0x1 /* 01 */ +#define ICR_RISING_EDGE 0x2 /* 10 */ +#define ICR_FALLING_EDGE 0x3 /* 11 */ + +#define PINS_PER_BANK 32 diff --git a/drivers/gpio/imx/gpio_driver.mk b/drivers/gpio/imx/gpio_driver.mk new file mode 100644 index 000000000..b0cc29a16 --- /dev/null +++ b/drivers/gpio/imx/gpio_driver.mk @@ -0,0 +1,29 @@ +# +# Copyright 2024, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Include this snippet in your project Makefile to build +# the Meson gpio driver +# +# NOTES: +# Generates gpio_driver.elf + +GPIO_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +gpio_driver.elf: gpio/gpio_driver.o + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +gpio/gpio_driver.o: ${GPIO_DRIVER_DIR}/gpio.c $(CONFIG_HEADER) ${CHECK_FLAGS_BOARD_MD5} |gpio + ${CC} ${CFLAGS} -c -o $@ $< + +gpio: + mkdir -p gpio + +clean:: + rm -rf gpio + +clobber:: + rm -f gpio_driver.elf + +-include gpio/gpio_driver.d diff --git a/examples/gpio/Makefile b/examples/gpio/Makefile new file mode 100644 index 000000000..abe4a892b --- /dev/null +++ b/examples/gpio/Makefile @@ -0,0 +1,34 @@ +# +# Copyright 2025, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(MICROKIT_BOARD)),) +$(error MICROKIT_BOARD must be specified) +endif + +export MICROKIT_CONFIG ?= debug +export MICROKIT_BOARD +BUILD_DIR ?= build +export SDDF := $(abspath ../..) +export BUILD_DIR := $(abspath ${BUILD_DIR}) +override MICROKIT_SDK := $(abspath ${MICROKIT_SDK}) + +IMAGE_FILE := $(BUILD_DIR)/loader.img +REPORT_FILE := $(BUILD_DIR)/report.txt + +all: ${IMAGE_FILE} + +qemu ${IMAGE_FILE} ${REPORT_FILE} clean clobber: $(IMAGE_FILE) ${BUILD_DIR}/Makefile FORCE + ${MAKE} -C ${BUILD_DIR} MICROKIT_SDK=${MICROKIT_SDK} $(notdir $@) + +${BUILD_DIR}/Makefile: gpio.mk + mkdir -p ${BUILD_DIR} + cp gpio.mk ${BUILD_DIR}/Makefile + +FORCE: diff --git a/examples/gpio/README.md b/examples/gpio/README.md new file mode 100644 index 000000000..64176d962 --- /dev/null +++ b/examples/gpio/README.md @@ -0,0 +1,90 @@ + + +# GPIO example + +We have 2 GPIOs: +- `GPIO_1` is set to output. +- `GPIO_2` is set to input. + +`GPIO_1` should be directly attached to `GPIO_2`. + +Read `gpio_config.h` for details on which physical pins are being used. + +## Building + +The following platforms are supported: +* maaxboard + +### Make + +```sh +make MICROKIT_SDK= MICROKIT_BOARD= +``` + +After building, the system image to load will be `build/loader.img`. + +### Zig + +You can also build this example with the Zig build system: +```sh +zig build -Dsdk=/path/to/sdk -Dboard= +``` + +The final bootable image will be in `zig-out/bin/loader.img`. + +## Running + +When running the example, you should see the following output: + +``` +CLIENT|INFO: Client Init! + +CLIENT|INFO: Starting GPIO example! Note GPIO1 must be connected to GPIO2! + +CLIENT|INFO: Setting direction of GPIO1 to output and intial value 0! +CLIENT|INFO: Setting direction of GPIO2 to input! +CLIENT|INFO: Checking (GPIO1's output == GPIO2's input)! +CLIENT|INFO: Setting value of GPIO1 to 1! +CLIENT|INFO: Checking (GPIO1's output == GPIO2's input)! +CLIENT|INFO: Now we will test IRQ functionality! +CLIENT|INFO: Setting type of IRQ of GPIO2 to falling edge! +CLIENT|INFO: Enabling IRQ functionality for GPIO2! +CLIENT|INFO: Setting a timeout for 1 second! +CLIENT|INFO: Setting value of GPIO1 to 0! +CLIENT|INFO: Main coroutine paused! +CLIENT|INFO: Got an interrupt from GPIO driver! +CLIENT|INFO: Got an interrupt from timer driver! +CLIENT|INFO: Main coroutine resumed! +CLIENT|INFO: Checking we recieved irq from GPIO2! +CLIENT|INFO: Setting type of IRQ of GPIO2 to rising edge! +CLIENT|INFO: Re-enabling IRQ functionality for GPIO2! +CLIENT|INFO: Setting a timeout for 1 second! +CLIENT|INFO: Setting value of GPIO1 to 1! +CLIENT|INFO: Main coroutine paused! +CLIENT|INFO: Got an interrupt from GPIO driver! +CLIENT|INFO: Got an interrupt from timer driver! +CLIENT|INFO: Main coroutine resumed! +CLIENT|INFO: Checking we recieved irq from GPIO2! +CLIENT|INFO: Re-enabling IRQ functionality for GPIO2! +CLIENT|INFO: Setting a timeout for 1 second! +CLIENT|INFO: Setting value of GPIO1 to 0! +CLIENT|INFO: Main coroutine paused! +CLIENT|INFO: Got an interrupt from timer driver! +CLIENT|INFO: Main coroutine resumed! +CLIENT|INFO: Checking we DIDN'T recieve irq from GPIO2! +CLIENT|INFO: Setting type of IRQ of GPIO2 to high level! +CLIENT|INFO: Setting a timeout for 1 second! +CLIENT|INFO: Checking we havent recieved any irq's from GPIO2! +CLIENT|INFO: Setting value of GPIO1 to 1! +CLIENT|INFO: Main coroutine paused! +CLIENT|INFO: Got an interrupt from GPIO driver! +CLIENT|INFO: Got an interrupt from timer driver! +CLIENT|INFO: Main coroutine resumed! +CLIENT|INFO: Checking we recieved irq from GPIO2! + +CLIENT|INFO: IF YOU GOT THIS FAR EVERYTHING WENT SMOOTHLY!!! +``` \ No newline at end of file diff --git a/examples/gpio/build.zig b/examples/gpio/build.zig new file mode 100644 index 000000000..c52e20ce9 --- /dev/null +++ b/examples/gpio/build.zig @@ -0,0 +1,199 @@ +// +// Copyright 2024, UNSW +// SPDX-License-Identifier: BSD-2-Clause +// +const std = @import("std"); +const LazyPath = std.Build.LazyPath; +const Step = std.Build.Step; + +const MicrokitBoard = enum { + maaxboard, +}; + +const Target = struct { + board: MicrokitBoard, + zig_target: std.Target.Query, +}; + +const targets = [_]Target{ + .{ + .board = MicrokitBoard.maaxboard, + .zig_target = std.Target.Query{ + .cpu_arch = .aarch64, + .cpu_model = .{ .explicit = &std.Target.aarch64.cpu.cortex_a53 }, + .cpu_features_add = std.Target.aarch64.featureSet(&[_]std.Target.aarch64.Feature{.strict_align}), + .os_tag = .freestanding, + .abi = .none, + }, + }, +}; + +fn findTarget(board: MicrokitBoard) std.Target.Query { + for (targets) |target| { + if (board == target.board) { + return target.zig_target; + } + } + + std.log.err("Board '{}' is not supported\n", .{board}); + std.posix.exit(1); +} + +const ConfigOptions = enum { debug, release, benchmark }; + +fn updateSectionObjcopy(b: *std.Build, section: []const u8, data_output: std.Build.LazyPath, data: []const u8, elf: []const u8) *std.Build.Step.Run { + const run_objcopy = b.addSystemCommand(&[_][]const u8{ + "llvm-objcopy", + }); + run_objcopy.addArg("--update-section"); + const data_full_path = data_output.join(b.allocator, data) catch @panic("OOM"); + run_objcopy.addPrefixedFileArg(b.fmt("{s}=", .{section}), data_full_path); + run_objcopy.addFileArg(.{ .cwd_relative = b.getInstallPath(.bin, elf) }); + + // We need the ELFs we talk about to be in the install directory first. + run_objcopy.step.dependOn(b.getInstallStep()); + + return run_objcopy; +} + +pub fn build(b: *std.Build) !void { + const optimize = b.standardOptimizeOption(.{}); + + const default_python = if (std.posix.getenv("PYTHON")) |p| p else "python3"; + const python = b.option([]const u8, "python", "Path to Python to use") orelse default_python; + + const microkit_sdk = b.option(LazyPath, "sdk", "Path to Microkit SDK") orelse { + std.log.err("Missing -Dsdk= argument", .{}); + return error.MissingSdkPath; + }; + + const microkit_config_option = b.option(ConfigOptions, "config", "Microkit config to build for") orelse .debug; + const microkit_config = @tagName(microkit_config_option); + + const microkit_board_option = b.option(MicrokitBoard, "board", "Microkit board to target") orelse { + std.log.err("Missing -Dboard= argument", .{}); + return error.MissingBoard; + }; + + const target = b.resolveTargetQuery(findTarget(microkit_board_option)); + const microkit_board = @tagName(microkit_board_option); + + const microkit_board_dir = microkit_sdk.path(b, "board").path(b, microkit_board).path(b, microkit_config); + const microkit_tool = microkit_sdk.path(b, "bin/microkit"); + const libmicrokit = microkit_board_dir.path(b, "lib/libmicrokit.a"); + const libmicrokit_include = microkit_board_dir.path(b, "include"); + const libmicrokit_linker_script = microkit_board_dir.path(b, "lib/microkit.ld"); + + const sddf_dep = b.dependency("sddf", .{ + .target = target, + .optimize = optimize, + .microkit_board_dir = microkit_board_dir, + .gpio_config_include = @as([]const u8, "include"), + }); + + const gpio_driver_class = switch (microkit_board_option) { + .maaxboard => "imx", + }; + + const timer_driver_class = switch (microkit_board_option) { + .maaxboard => "imx", + }; + + const gpio_driver = sddf_dep.artifact(b.fmt("driver_gpio_{s}.elf", .{gpio_driver_class})); + // This is required because the SDF file is expecting a different name to the artifact we + // are dealing with. + const gpio_driver_install = b.addInstallArtifact(gpio_driver, .{ .dest_sub_path = "gpio_driver.elf" }); + + const timer_driver = sddf_dep.artifact(b.fmt("driver_timer_{s}.elf", .{timer_driver_class})); + // This is required because the SDF file is expecting a different name to the artifact we + // are dealing with. + const timer_driver_install = b.addInstallArtifact(timer_driver, .{ .dest_sub_path = "timer_driver.elf" }); + + const client = b.addExecutable(.{ + .name = "client.elf", + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .strip = false, + }), + }); + + client.addCSourceFile(.{ .file = b.path("client.c") }); + + // For gpio_config.h + client.addIncludePath(b.path("include")); + + client.addIncludePath(sddf_dep.path("include")); + client.addIncludePath(sddf_dep.path("include/microkit")); + client.addIncludePath(sddf_dep.path("examples/gpio")); + client.linkLibrary(sddf_dep.artifact("util")); + client.linkLibrary(sddf_dep.artifact("util_putchar_debug")); + + // for coroutines in client + client.addIncludePath(sddf_dep.path("libco")); + client.addCSourceFile(.{ .file = sddf_dep.path("libco/libco.c") }); + + client.addIncludePath(libmicrokit_include); + client.addObjectFile(libmicrokit); + client.setLinkerScript(libmicrokit_linker_script); + + b.installArtifact(client); + + // For compiling the DTS into a DTB + const dts = sddf_dep.path(b.fmt("dts/{s}.dts", .{microkit_board})); + const dtc_cmd = b.addSystemCommand(&[_][]const u8{ "dtc", "-q", "-I", "dts", "-O", "dtb" }); + dtc_cmd.addFileInput(dts); + dtc_cmd.addFileArg(dts); + const dtb = dtc_cmd.captureStdOut(); + + // Run the metaprogram to get sDDF configuration binary files and the SDF file. + const metaprogram = b.path("meta.py"); + const run_metaprogram = b.addSystemCommand(&[_][]const u8{ + python, + }); + run_metaprogram.addFileArg(metaprogram); + run_metaprogram.addFileInput(metaprogram); + run_metaprogram.addPrefixedDirectoryArg("--sddf=", sddf_dep.path("")); + run_metaprogram.addPrefixedDirectoryArg("--dtb=", dtb); + const meta_output = run_metaprogram.addPrefixedOutputDirectoryArg("--output=", "meta_output"); + run_metaprogram.addArg("--board"); + run_metaprogram.addArg(microkit_board); + run_metaprogram.addArg("--sdf"); + run_metaprogram.addArg("gpio.system"); + + const meta_output_install = b.addInstallDirectory(.{ + .source_dir = meta_output, + .install_dir = .prefix, + .install_subdir = "meta_output", + }); + + const client_gpio_objcopy = updateSectionObjcopy(b, ".gpio_client_config", meta_output, "gpio_client_client.data", "client.elf"); + const gpio_driver_objcopy = updateSectionObjcopy(b, ".device_resources", meta_output, "gpio_driver_device_resources.data", "gpio_driver.elf"); + gpio_driver_objcopy.step.dependOn(&gpio_driver_install.step); + + const client_timer_objcopy = updateSectionObjcopy(b, ".timer_client_config", meta_output, "timer_client_client.data", "client.elf"); + const timer_driver_objcopy = updateSectionObjcopy(b, ".device_resources", meta_output, "timer_driver_device_resources.data", "timer_driver.elf"); + timer_driver_objcopy.step.dependOn(&timer_driver_install.step); + + const objcopys = &.{ + client_gpio_objcopy, + client_timer_objcopy, + gpio_driver_objcopy, + timer_driver_objcopy, + }; + + const final_image_dest = b.getInstallPath(.bin, "./loader.img"); + const microkit_tool_cmd = Step.Run.create(b, "run microkit tool"); + microkit_tool_cmd.addFileArg(microkit_tool); + microkit_tool_cmd.addArgs(&[_][]const u8{ b.getInstallPath(.{ .custom = "meta_output" }, "gpio.system"), "--search-path", b.getInstallPath(.bin, ""), "--board", microkit_board, "--config", microkit_config, "-o", final_image_dest, "-r", b.getInstallPath(.prefix, "./report.txt") }); + inline for (objcopys) |objcopy| { + microkit_tool_cmd.step.dependOn(&objcopy.step); + } + microkit_tool_cmd.step.dependOn(&meta_output_install.step); + microkit_tool_cmd.step.dependOn(b.getInstallStep()); + microkit_tool_cmd.setEnvironmentVariable("MICROKIT_SDK", microkit_sdk.getPath3(b, null).toString(b.allocator) catch @panic("OOM")); + + const microkit_step = b.step("microkit", "Compile and build the final bootable image"); + microkit_step.dependOn(µkit_tool_cmd.step); + b.default_step = microkit_step; +} diff --git a/examples/gpio/build.zig.zon b/examples/gpio/build.zig.zon new file mode 100644 index 000000000..034ae975a --- /dev/null +++ b/examples/gpio/build.zig.zon @@ -0,0 +1,15 @@ +.{ + .name = .sddf_gpio_example, + .version = "0.0.0", + + .dependencies = .{ + .sddf = .{ + .path = "../../", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + }, + .fingerprint = 0xf250b0219a621816, +} diff --git a/examples/gpio/client.c b/examples/gpio/client.c new file mode 100644 index 000000000..501a5448f --- /dev/null +++ b/examples/gpio/client.c @@ -0,0 +1,256 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_CLIENT(...) do{ sddf_printf("CLIENT|INFO: "); sddf_printf(__VA_ARGS__); }while(0) +#define LOG_CLIENT_ERR(...) do{ sddf_printf("CLIENT|ERROR: "); sddf_printf(__VA_ARGS__); }while(0) + +__attribute__((__section__(".gpio_client_config"))) gpio_client_config_t gpio_config; +__attribute__((__section__(".timer_client_config"))) timer_client_config_t timer_config; + +microkit_channel timer_channel; + +microkit_channel gpio_channel_1_output; +microkit_channel gpio_channel_2_input; + +cothread_t t_event; +cothread_t t_main; + +#define STACK_SIZE (4096) +static char t_client_main_stack[STACK_SIZE]; + +int gpio_irqs_received = 0; + +void client_main(void) +{ + LOG_CLIENT("Starting GPIO example! Note this GPIO1 must be connected to GPIO2!\n\n"); + + int ret = 0; + LOG_CLIENT("Setting direction of GPIO1 to output and intial value 0!\n"); + ret = sddf_gpio_direction_output(gpio_channel_1_output, 0); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to set direction to output. Error code : %d!\n", ret); + assert(false); + } + + LOG_CLIENT("Setting direction of GPIO2 to input!\n"); + ret = sddf_gpio_direction_input(gpio_channel_2_input); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to set direction to input. Error code : %d!\n", ret); + assert(false); + } + + LOG_CLIENT("Checking (GPIO1's output == GPIO2's input)!\n"); + ret = sddf_gpio_get(gpio_channel_2_input); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to get value. Error code : %d!\n", ret); + assert(false); + } else if (ret == 1) { + LOG_CLIENT_ERR("Failed because GPIO2's input is reading value 1!\n"); + assert(false); + } + + LOG_CLIENT("Setting value of GPIO1 to 1!\n"); + ret = sddf_gpio_set(gpio_channel_1_output, 1); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to set value. Error code : %d!\n", ret); + assert(false); + } + + LOG_CLIENT("Checking (GPIO1's output == GPIO2's input)!\n"); + ret = sddf_gpio_get(gpio_channel_2_input); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to get value. Error code : %d!\n", ret); + assert(false); + } else if (ret == 0) { + LOG_CLIENT_ERR("Failed because GPIO2's input is reading value 0!\n"); + assert(false); + } + + LOG_CLIENT("Now we will test IRQ functionality!\n"); + + LOG_CLIENT("Setting type of IRQ of GPIO2 to falling edge!\n"); + ret = sddf_gpio_irq_set_type(gpio_channel_2_input, SDDF_IRQ_TYPE_EDGE_FALLING); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to set IRQ type. Error code : %d!\n", ret); + assert(false); + } + + LOG_CLIENT("Enabling IRQ functionality for GPIO2!\n"); + ret = sddf_gpio_irq_enable(gpio_channel_2_input); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to enable IRQ. Error code : %d!\n", ret); + assert(false); + } + + // To actually test an IRQ was received from a GPIO we set a timeout so that we can check if we got an IRQ + // from the GPIO driver within a reasonable amount of time. + LOG_CLIENT("Setting a timeout for 1 second!\n"); + sddf_timer_set_timeout(timer_channel, NS_IN_S); + + LOG_CLIENT("Setting value of GPIO1 to 0!\n"); + ret = sddf_gpio_set(gpio_channel_1_output, 0); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to set value. Error code : %d!\n", ret); + assert(false); + } + + LOG_CLIENT("Main coroutine paused!\n"); + co_switch(t_event); + LOG_CLIENT("Main coroutine resumed!\n"); + + LOG_CLIENT("Checking we received irq from GPIO2!\n"); + if (gpio_irqs_received != 1) { + LOG_CLIENT_ERR("We received wrong amount of IRQs from GPIO2. Amount : %d!\n", gpio_irqs_received); + assert(false); + } + + LOG_CLIENT("Setting type of IRQ of GPIO2 to rising edge!\n"); + ret = sddf_gpio_irq_set_type(gpio_channel_2_input, SDDF_IRQ_TYPE_EDGE_RISING); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to set IRQ type. Error code : %d!\n", ret); + assert(false); + } + + LOG_CLIENT("Re-enabling IRQ functionality for GPIO2!\n"); + ret = sddf_gpio_irq_enable(gpio_channel_2_input); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to enable IRQ. Error code : %d!\n", ret); + assert(false); + } + + LOG_CLIENT("Setting a timeout for 1 second!\n"); + sddf_timer_set_timeout(timer_channel, NS_IN_S); + + LOG_CLIENT("Setting value of GPIO1 to 1!\n"); + ret = sddf_gpio_set(gpio_channel_1_output, 1); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to set value. Error code : %d!\n", ret); + assert(false); + } + + LOG_CLIENT("Main coroutine paused!\n"); + co_switch(t_event); + LOG_CLIENT("Main coroutine resumed!\n"); + + LOG_CLIENT("Checking we received irq from GPIO2!\n"); + if (gpio_irqs_received != 2) { + LOG_CLIENT_ERR("We received wrong amount of IRQs from GPIO2. Amount : %d!\n", gpio_irqs_received); + assert(false); + } + + LOG_CLIENT("Re-enabling IRQ functionality for GPIO2!\n"); + ret = sddf_gpio_irq_enable(gpio_channel_2_input); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to enable IRQ. Error code : %d!\n", ret); + assert(false); + } + + LOG_CLIENT("Setting a timeout for 1 second!\n"); + sddf_timer_set_timeout(timer_channel, NS_IN_S); + + LOG_CLIENT("Setting value of GPIO1 to 0!\n"); + ret = sddf_gpio_set(gpio_channel_1_output, 0); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to set value. Error code : %d!\n", ret); + assert(false); + } + + LOG_CLIENT("Main coroutine paused!\n"); + co_switch(t_event); + LOG_CLIENT("Main coroutine resumed!\n"); + + LOG_CLIENT("Checking we DIDN'T recieve irq from GPIO2!\n"); + if (gpio_irqs_received != 2) { + LOG_CLIENT_ERR("We received wrong amount of IRQs from GPIO2. Amount : %d!\n", gpio_irqs_received); + assert(false); + } + + LOG_CLIENT("Setting type of IRQ of GPIO2 to high level!\n"); + ret = sddf_gpio_irq_set_type(gpio_channel_2_input, SDDF_IRQ_TYPE_LEVEL_HIGH); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to set IRQ type. Error code : %d!\n", ret); + assert(false); + } + + LOG_CLIENT("Setting a timeout for 1 second!\n"); + sddf_timer_set_timeout(timer_channel, NS_IN_S); + + LOG_CLIENT("Checking we havent received any irq's from GPIO2!\n"); + if (gpio_irqs_received != 2) { + LOG_CLIENT_ERR("We received wrong amount of IRQs from GPIO2. Amount : %d!\n", gpio_irqs_received); + assert(false); + } + + LOG_CLIENT("Setting value of GPIO1 to 1!\n"); + ret = sddf_gpio_set(gpio_channel_1_output, 1); + if (ret < 0) { + LOG_CLIENT_ERR("Failed to set value. Error code : %d!\n", ret); + assert(false); + } + + LOG_CLIENT("Main coroutine paused!\n"); + co_switch(t_event); + LOG_CLIENT("Main coroutine resumed!\n"); + + LOG_CLIENT("Checking we received irq from GPIO2!\n\n"); + if (gpio_irqs_received != 3) { + LOG_CLIENT_ERR("We received wrong amount of IRQs from GPIO2. Amount : %d!\n", gpio_irqs_received); + assert(false); + } + + LOG_CLIENT("IF YOU GOT THIS FAR EVERYTHING WENT SMOOTHLY!!!\n"); + + while (1) {} +} + +void init(void) +{ + LOG_CLIENT("Client Init!\n\n"); + + assert(gpio_config_check_magic(&gpio_config)); + assert(timer_config_check_magic(&timer_config)); + + gpio_channel_1_output = gpio_config.driver_channel_ids[0]; + gpio_channel_2_input = gpio_config.driver_channel_ids[1]; + + timer_channel = timer_config.driver_id; + + // Define the event loop/notified thread as the active co-routine + t_event = co_active(); + + // derive main entry point + t_main = co_derive((void *)t_client_main_stack, STACK_SIZE, client_main); + + co_switch(t_main); +} + +void notified(microkit_channel ch) +{ + if (ch == gpio_channel_1_output) { + LOG_CLIENT_ERR("We should not have received IRQ from this gpio channel! (channel : %d)\n", ch); + assert(false); + } else if (ch == gpio_channel_2_input) { + LOG_CLIENT("Got an interrupt from GPIO driver!\n"); + gpio_irqs_received++; + } else if (ch == timer_channel) { + LOG_CLIENT("Got an interrupt from timer driver!\n"); + co_switch(t_main); + } else { + LOG_CLIENT_ERR("Unknown channel?!\n"); + assert(false); + } +} diff --git a/examples/gpio/gpio.mk b/examples/gpio/gpio.mk new file mode 100644 index 000000000..0ef02a59a --- /dev/null +++ b/examples/gpio/gpio.mk @@ -0,0 +1,90 @@ +# +# Copyright 2025, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# +# This Makefile is copied into the build directory +# and operated on from there. +# + +ifeq ($(strip $(MICROKIT_SDK)),) +$(error MICROKIT_SDK must be specified) +endif + +ifeq ($(strip $(TOOLCHAIN)),) + TOOLCHAIN := clang +endif + +PYTHONPATH := ${SDDF}/tools/meta:${PYTHONPATH} +export PYTHONPATH + +SUPPORTED_BOARDS := \ + maaxboard + +include ${SDDF}/tools/make/board/common.mk + +SDDF_CUSTOM_LIBC := 1 +TOP:= ${SDDF}/examples/gpio +METAPROGRAM := $(TOP)/meta.py +UTIL := $(SDDF)/util +LIBCO := $(SDDF)/libco +GPIO_DRIVER := $(SDDF)/drivers/gpio/$(GPIO_DRIV_DIR) +TIMER_DRIVER := $(SDDF)/drivers/timer/${TIMER_DRIV_DIR} +SYSTEM_FILE := gpio.system +DTS := $(SDDF)/dts/$(MICROKIT_BOARD).dts +DTB := $(MICROKIT_BOARD).dtb +CONFIGS_DIR := $(TOP)/include +CONFIG_HEADER := $(CONFIGS_DIR)/gpio_config.h + +IMAGES := gpio_driver.elf client.elf timer_driver.elf + + +IMAGE_FILE = loader.img +REPORT_FILE = report.txt +SYSTEM_FILE = gpio.system + +CFLAGS += -Wall -Wno-unused-function -Werror -Wno-unused-command-line-argument \ + -I$(BOARD_DIR)/include \ + -I$(SDDF)/include \ + -I$(SDDF)/include/microkit \ + -I$(LIBCO) \ + -I$(CONFIGS_DIR) \ + $(CFLAGS_ARCH) +LDFLAGS := -L$(BOARD_DIR)/lib +LIBS := --start-group -lmicrokit -Tmicrokit.ld libsddf_util_debug.a --end-group +all: $(IMAGE_FILE) + +include ${GPIO_DRIVER}/gpio_driver.mk +include ${SDDF}/util/util.mk +include ${LIBCO}/libco.mk + +${IMAGES}: libsddf_util_debug.a + +# @Tristan: so it recompiles when the config file changes +-include client.d + +client.o: ${TOP}/client.c $(CONFIG_HEADER) + $(CC) -c $(CFLAGS) $< -o client.o +client.elf: client.o libco.a + $(LD) $(LDFLAGS) $^ $(LIBS) -o client.elf + +$(SYSTEM_FILE): $(METAPROGRAM) $(IMAGES) $(DTB) + $(PYTHON) $(METAPROGRAM) --sddf $(SDDF) --board $(MICROKIT_BOARD) --dtb $(DTB) --output . --sdf $(SYSTEM_FILE) + $(OBJCOPY) --update-section .device_resources=gpio_driver_device_resources.data gpio_driver.elf + $(OBJCOPY) --update-section .gpio_client_config=gpio_client_client.data client.elf + $(OBJCOPY) --update-section .timer_client_config=timer_client_client.data client.elf + +$(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) + $(MICROKIT_TOOL) $(SYSTEM_FILE) --search-path $(BUILD_DIR) --board $(MICROKIT_BOARD) --config $(MICROKIT_CONFIG) -o $(IMAGE_FILE) -r $(REPORT_FILE) + +${IMAGES}: libsddf_util_debug.a +.PHONY: all compile clean + +clean:: + rm -f client.o + find . -name '*.[do]' |xargs --no-run-if-empty rm + +clobber:: clean + rm -f client.elf ${IMAGE_FILE} ${REPORT_FILE} + +include ${TIMER_DRIVER}/timer_driver.mk diff --git a/examples/gpio/include/gpio_config.h b/examples/gpio/include/gpio_config.h new file mode 100644 index 000000000..f3d4dce7d --- /dev/null +++ b/examples/gpio/include/gpio_config.h @@ -0,0 +1,97 @@ +/* + * Copyright 2024, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#pragma once + +/* +NUMBERING SCHEMES + +IMX: +- For pins we use 0-31 as thats how many pins per bank +- For irqs write a value > 0 because irq lines are shared. +*/ + +#define PIN_1 (15) // IMX gpio1_15 // IMX physical 32 +#define PIN_2 (13) // IMX gpio1_13 // IMX physical 33 + +#define IRQ_1 (1) + +#define NUM_DRIVER_CHANNELS 62 + +#define PIN_UNUSED (-1) +#define IRQ_UNUSED (-1) + +#define UNUSED_CH(n) [n] = { PIN_UNUSED, IRQ_UNUSED } + +typedef struct { + int pin; + int irq; +} GPIO_driver_channel_t; + +// ideally this whole setup should probably be in the meta.py file +static const GPIO_driver_channel_t gpio_driver_channel_mappings[NUM_DRIVER_CHANNELS] = { + [0] = { PIN_1, IRQ_UNUSED }, // need to claim these channels in meta.py + [1] = { PIN_2, IRQ_1 }, + UNUSED_CH(2), + UNUSED_CH(3), + UNUSED_CH(4), + UNUSED_CH(5), + UNUSED_CH(6), + UNUSED_CH(7), + UNUSED_CH(8), + UNUSED_CH(9), + UNUSED_CH(10), + UNUSED_CH(11), + UNUSED_CH(12), + UNUSED_CH(13), + UNUSED_CH(14), + UNUSED_CH(15), + UNUSED_CH(16), + UNUSED_CH(17), + UNUSED_CH(18), + UNUSED_CH(19), + UNUSED_CH(20), + UNUSED_CH(21), + UNUSED_CH(22), + UNUSED_CH(23), + UNUSED_CH(24), + UNUSED_CH(25), + UNUSED_CH(26), + UNUSED_CH(27), + UNUSED_CH(28), + UNUSED_CH(29), + UNUSED_CH(30), + UNUSED_CH(31), + UNUSED_CH(32), + UNUSED_CH(33), + UNUSED_CH(34), + UNUSED_CH(35), + UNUSED_CH(36), + UNUSED_CH(37), + UNUSED_CH(38), + UNUSED_CH(39), + UNUSED_CH(40), + UNUSED_CH(41), + UNUSED_CH(42), + UNUSED_CH(43), + UNUSED_CH(44), + UNUSED_CH(45), + UNUSED_CH(46), + UNUSED_CH(47), + UNUSED_CH(48), + UNUSED_CH(49), + UNUSED_CH(50), + UNUSED_CH(51), + UNUSED_CH(52), + UNUSED_CH(53), + UNUSED_CH(54), + UNUSED_CH(55), + UNUSED_CH(56), + UNUSED_CH(57), + UNUSED_CH(58), + UNUSED_CH(59), + UNUSED_CH(60), + UNUSED_CH(61), +}; \ No newline at end of file diff --git a/examples/gpio/meta.py b/examples/gpio/meta.py new file mode 100644 index 000000000..de649dcd5 --- /dev/null +++ b/examples/gpio/meta.py @@ -0,0 +1,81 @@ +# Copyright 2025, UNSW +# SPDX-License-Identifier: BSD-2-Clause +import argparse +import sys +import os +from typing import List +from dataclasses import dataclass +from sdfgen import SystemDescription, Sddf, DeviceTree +from importlib.metadata import version + +sys.path.append( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../tools/meta") +) +from board import BOARDS + +assert version("sdfgen").split(".")[1] == "28", "Unexpected sdfgen version" + +ProtectionDomain = SystemDescription.ProtectionDomain +Irq = SystemDescription.Irq +MemoryRegion = SystemDescription.MemoryRegion +Map = SystemDescription.Map + + + +def generate(sdf_file: str, output_dir: str, dtb: DeviceTree): + gpio_driver = ProtectionDomain("gpio_driver", "gpio_driver.elf", priority=254) + client = ProtectionDomain("client", "client.elf", priority=1) + + gpio_node = dtb.node(board.gpio) + assert gpio_node is not None + + gpio_system = Sddf.Gpio(sdf, gpio_node, gpio_driver) + + # These need to be different to the ones hardcoded in config.json or this file + driver_channel_ids = [0, 1] + gpio_system.add_client(client, driver_channel_ids=driver_channel_ids) + + # We need a timer driver for the example + timer_driver = ProtectionDomain("timer_driver", "timer_driver.elf", priority=254) + timer_node = dtb.node(board.timer) + assert timer_node is not None + timer_system = Sddf.Timer(sdf, timer_node, timer_driver) + timer_system.add_client(client) + + pds = [gpio_driver, timer_driver, client] + for pd in pds: + sdf.add_pd(pd) + + # TODO: currently there is no check to see if driver_channel_ids hasn't chosen a channel used + # by a device irq + # But it will still fail really deep inside of gpio_system.connect() and not compile + + assert gpio_system.connect() + assert gpio_system.serialise_config(output_dir) + + assert timer_system.connect() + assert timer_system.serialise_config(output_dir) + + with open(f"{output_dir}/{sdf_file}", "w+") as f: + f.write(sdf.render()) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--dtb", required=True) + parser.add_argument("--sddf", required=True) + parser.add_argument("--board", required=True, choices=[b.name for b in BOARDS]) + parser.add_argument("--output", required=True) + parser.add_argument("--sdf", required=True) + + args = parser.parse_args() + + board = next(filter(lambda b: b.name == args.board, BOARDS)) + + sdf = SystemDescription(board.arch, board.paddr_top) + sddf = Sddf(args.sddf) + + with open(args.dtb, "rb") as f: + dtb = DeviceTree(f.read()) + + generate(args.sdf, args.output, dtb) diff --git a/flake.lock b/flake.lock index 622eca798..421a4f07e 100644 --- a/flake.lock +++ b/flake.lock @@ -116,17 +116,17 @@ "zig-overlay": "zig-overlay" }, "locked": { - "lastModified": 1764823094, - "narHash": "sha256-Eeef5J0y31NH/F5Z0fayJ+4mroYfb8JPadu3cw6cNhc=", + "lastModified": 1770792752, + "narHash": "sha256-7FwM1WXyXR9WJJOdUa68zL+VKRMkf1ok24bOZsTDgHg=", "owner": "au-ts", "repo": "microkit_sdf_gen", - "rev": "13686a373b9189c7b1d778cd749d7d8bbd8a63f6", + "rev": "4508df1ff7b797eae363108655d9fe0673c7c79a", "type": "github" }, "original": { "owner": "au-ts", - "ref": "0.28.1", "repo": "microkit_sdf_gen", + "rev": "4508df1ff7b797eae363108655d9fe0673c7c79a", "type": "github" } }, diff --git a/flake.nix b/flake.nix index 367183e54..85b0b8e05 100644 --- a/flake.nix +++ b/flake.nix @@ -9,7 +9,7 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; zig-overlay.url = "github:mitchellh/zig-overlay"; zig-overlay.inputs.nixpkgs.follows = "nixpkgs"; - sdfgen.url = "github:au-ts/microkit_sdf_gen/0.28.1"; + sdfgen.url = "github:au-ts/microkit_sdf_gen/4508df1ff7b797eae363108655d9fe0673c7c79a"; sdfgen.inputs.nixpkgs.follows = "nixpkgs"; }; diff --git a/include/sddf/gpio/client.h b/include/sddf/gpio/client.h new file mode 100644 index 000000000..11509226f --- /dev/null +++ b/include/sddf/gpio/client.h @@ -0,0 +1,256 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +static inline uint32_t gpio_make_label(uint32_t interface, uint32_t value) +{ + return (interface & SDDF_REQUEST_INTERFACE_MASK) | gpio_encode_value(value); +} + +static inline int gpio_check_err(uint32_t label) +{ + if (label & BIT(SDDF_GPIO_RESPONSE_ERROR_BIT)) { + return -((int)(label & SDDF_GPIO_RESPONSE_VALUE_MASK)); + } + return 0; +} + +// NOTE: GPIO driver is passive + +// GPIO_BASED + +/** + * Request the direction of GPIO line associated with channel (from gpio_config.h). + * + * @param microkit channel of gpio driver. + * @return negative error code or direction of GPIO pin (SDDF_GPIO_line_direction_t). + */ +static int sddf_gpio_get_direction(uint32_t channel) +{ + uint32_t label = gpio_make_label(SDDF_GPIO_GET_DIRECTION, 0); + microkit_msginfo msginfo = sddf_ppcall(channel, seL4_MessageInfo_new(label, 0, 0, 0)); + label = microkit_msginfo_get_label(msginfo); + + int err = gpio_check_err(label); + if (err) { + return err; + } + + return (label & BIT(0)) ? SDDF_GPIO_LINE_DIRECTION_OUT : SDDF_GPIO_LINE_DIRECTION_IN; +} + +/** + * Request for direction of GPIO line associated with channel (from gpio_config.h) to be an input. + * + * @param microkit channel of gpio driver. + * @return negative error code or 0. + */ +static int sddf_gpio_direction_input(uint32_t channel) +{ + uint32_t label = gpio_make_label(SDDF_GPIO_DIRECTION_INPUT, 0); + microkit_msginfo msginfo = sddf_ppcall(channel, seL4_MessageInfo_new(label, 0, 0, 0)); + label = microkit_msginfo_get_label(msginfo); + + int err = gpio_check_err(label); + if (err) { + return err; + } + return 0; +} + +/** + * Request for direction of GPIO line associated with channel (from gpio_config.h) to be an output with requested intial value. + * + * @param microkit channel of gpio driver. + * @value + * @return negative error code or 0. + */ +static int sddf_gpio_direction_output(uint32_t channel, uint32_t value) +{ + uint32_t label = gpio_make_label(SDDF_GPIO_DIRECTION_OUTPUT, value); + microkit_msginfo msginfo = sddf_ppcall(channel, seL4_MessageInfo_new(label, 0, 0, 0)); + label = microkit_msginfo_get_label(msginfo); + + int err = gpio_check_err(label); + if (err) { + return err; + } + return 0; +} + +/** + * Request the value of GPIO line associated with channel (from gpio_config.h). + * + * @param microkit channel of gpio driver. + * @return negative error code or value of line. + */ +static int sddf_gpio_get(uint32_t channel) +{ + uint32_t label = gpio_make_label(SDDF_GPIO_GET, 0); + microkit_msginfo msginfo = sddf_ppcall(channel, seL4_MessageInfo_new(label, 0, 0, 0)); + label = microkit_msginfo_get_label(msginfo); + + int err = gpio_check_err(label); + if (err) { + return err; + } + return (label & BIT(0)) ? 1 : 0; +} + +/** + * Request for value of GPIO line associated with channel (from gpio_config.h) to be requested logical level value. + * This usually updates the output latch so may not actually drive the GPIO line if its still in input mode. + * + * @param microkit channel of gpio driver. + * @param value + * @return negative error code or 0. + */ +static int sddf_gpio_set(uint32_t channel, uint32_t value) +{ + uint32_t label = gpio_make_label(SDDF_GPIO_SET, value); + microkit_msginfo msginfo = sddf_ppcall(channel, seL4_MessageInfo_new(label, 0, 0, 0)); + label = microkit_msginfo_get_label(msginfo); + + int err = gpio_check_err(label); + if (err) { + return err; + } + return 0; +} + +/** + * Request the configuration of GPIO line associated with channel (from gpio_config.h) to requested config (+ argument) value(s). + * + * @param microkit channel of gpio driver. + * @param configuration + * @param optional argument of config (usually a continuous value rather than flag) + * + * @return negative error code or 0. + */ +static int sddf_gpio_set_config(uint32_t channel, uint32_t config, uint32_t argument) +{ + uint32_t label = gpio_make_label(SDDF_GPIO_SET_CONFIG, config); + sddf_set_mr(0, argument); + microkit_msginfo msginfo = sddf_ppcall(channel, seL4_MessageInfo_new(label, 0, 0, 1)); + label = microkit_msginfo_get_label(msginfo); + + int err = gpio_check_err(label); + if (err) { + return err; + } + return 0; +} + +// IRQ_BASED + +/** + * Request for IRQ line associated with channel (from gpio_config.h) to be enabled. + * + * @param microkit channel of gpio driver. + * @return negative error code or 0. + */ +static int sddf_gpio_irq_enable(uint32_t channel) +{ + uint32_t label = gpio_make_label(SDDF_GPIO_IRQ_ENABLE, 0); + microkit_msginfo msginfo = sddf_ppcall(channel, seL4_MessageInfo_new(label, 0, 0, 0)); + label = microkit_msginfo_get_label(msginfo); + + int err = gpio_check_err(label); + if (err) { + return err; + } + return 0; +} + +/** + * Request for IRQ line associated with channel (from gpio_config.h) to be disabled. + * + * @param microkit channel of gpio driver. + * @return negative error code or 0. + */ +static int sddf_gpio_irq_disable(uint32_t channel) +{ + uint32_t label = gpio_make_label(SDDF_GPIO_IRQ_DISABLE, 0); + microkit_msginfo msginfo = sddf_ppcall(channel, seL4_MessageInfo_new(label, 0, 0, 0)); + label = microkit_msginfo_get_label(msginfo); + + int err = gpio_check_err(label); + if (err) { + return err; + } + return 0; +} + +/** + * Request for type of IRQ line associated with channel (from gpio_config.h) to be requested type . + * + * @param microkit channel of gpio driver. + * @param SDDF_GPIO_irq_line_status_t type. + * @return negative error code or 0. + */ +static int sddf_gpio_irq_set_type(uint32_t channel, uint32_t type) +{ + uint32_t label = gpio_make_label(SDDF_GPIO_IRQ_SET_TYPE, type); + microkit_msginfo msginfo = sddf_ppcall(channel, seL4_MessageInfo_new(label, 0, 0, 0)); + label = microkit_msginfo_get_label(msginfo); + + int err = gpio_check_err(label); + if (err) { + return err; + } + return 0; +} + +// NOTE: there is no sddf_gpio_get_config function this is because: +// - Most GPIO controllers don’t let you read back “config” bits +// - Clients can shadow if they really want it + +// TODO: changes need to be made to current gpio_config.h file +// static inline int sddf_gpio_get_multiple(uint32_t channel, uint32_t *mask, uint32_t *bits) { +// return -1; +// } + +// TODO: changes need to be made to current gpio_config.h file +// static inline int sddf_gpio_set_multiple(uint32_t channel, uint32_t *mask, uint32_t *bits) { +// return -1; +// } + +// tbd... +// static inline int sddf_gpio_set_rv(uint32_t channel, int value) { +// return -1; +// } + +// tbd... +// static inline int sddf_gpio_set_multiple_rv(uint32_t channel, unsigned int *mask, uint32_t *bits) { +// return -1; +// } + +// tbd... +// static inline int sddf_gpio_en_hw_timestamp(uint32_t channel, uint32_t flags) { +// return -1; +// } + +// tbd... +// static inline int sddf_gpio_dis_hw_timestamp(uint32_t channel, uint32_t flags) { +// return -1; +// } + +// The case could be made that theres enough of a semantic difference to have irq_unmask and irq_mask +// on top of enable and disable + +// tbd... +// static inline int sddf_gpio_irq_unmask(uint32_t channel) { +// return -1; +// } + +// tbd... +// static inline int sddf_gpio_irq_mask(uint32_t channel) { +// return -1; +// } diff --git a/include/sddf/gpio/config.h b/include/sddf/gpio/config.h new file mode 100644 index 000000000..a47b47999 --- /dev/null +++ b/include/sddf/gpio/config.h @@ -0,0 +1,32 @@ +/* + * Copyright 2025, UNSW + * + * SPDX-License-Identifier: BSD-2-Clause + */ +#pragma once + +#include +#include +#include + +#define MAX_CHANNELS 62 +#define SDDF_GPIO_MAGIC_LEN 5 +static char SDDF_GPIO_MAGIC[SDDF_GPIO_MAGIC_LEN] = { 's', 'D', 'D', 'F', 0x8 }; + +typedef struct gpio_client_config { + char magic[SDDF_GPIO_MAGIC_LEN]; + uint8_t num_driver_channel_ids; + uint8_t driver_channel_ids[MAX_CHANNELS]; +} gpio_client_config_t; + +static bool gpio_config_check_magic(void *config) +{ + char *magic = (char *)config; + for (int i = 0; i < SDDF_GPIO_MAGIC_LEN; i++) { + if (magic[i] != SDDF_GPIO_MAGIC[i]) { + return false; + } + } + + return true; +} diff --git a/include/sddf/gpio/protocol.h b/include/sddf/gpio/protocol.h new file mode 100644 index 000000000..cf63d07c5 --- /dev/null +++ b/include/sddf/gpio/protocol.h @@ -0,0 +1,78 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +// Shared functionality/definitions between gpio drivers and clients + +typedef enum { + // GPIO based + SDDF_GPIO_SET, + SDDF_GPIO_GET, + SDDF_GPIO_DIRECTION_OUTPUT, + SDDF_GPIO_DIRECTION_INPUT, + SDDF_GPIO_GET_DIRECTION, + SDDF_GPIO_SET_CONFIG, + // IRQ based + SDDF_GPIO_IRQ_ENABLE, + SDDF_GPIO_IRQ_DISABLE, + SDDF_GPIO_IRQ_SET_TYPE +} SDDF_GPIO_interface_t; + +typedef enum { + SDDF_GPIO_LINE_DIRECTION_OUT = 0, + SDDF_GPIO_LINE_DIRECTION_IN = 1, +} SDDF_GPIO_line_direction_t; + +typedef enum { + SDDF_IRQ_TYPE_NONE = 0x00, + SDDF_IRQ_TYPE_EDGE_RISING = 0x01, + SDDF_IRQ_TYPE_EDGE_FALLING = 0x02, + SDDF_IRQ_TYPE_EDGE_BOTH = (SDDF_IRQ_TYPE_EDGE_FALLING | SDDF_IRQ_TYPE_EDGE_RISING), + SDDF_IRQ_TYPE_LEVEL_HIGH = 0x04, + SDDF_IRQ_TYPE_LEVEL_LOW = 0x08, + SDDF_IRQ_TYPE_LEVEL_MASK = (SDDF_IRQ_TYPE_LEVEL_LOW | SDDF_IRQ_TYPE_LEVEL_HIGH), +} SDDF_GPIO_irq_line_status_t; + +#define SDDF_REQUEST_INTERFACE_MASK \ + BIT_MASK_RANGE(9, 0) + +#define GPIO_REQUEST_VALUE_MASK \ + BIT_MASK_RANGE(19, 10) + +#define GPIO_VALUE_SHIFT 10 +#define GPIO_VALUE_WIDTH 10 + +/** + * Encode a raw 10-bit value into bits [19:10] of a label. + */ +static inline uint32_t gpio_encode_value(uint32_t val) +{ + // shift up, then mask to that [19:10] window + return (val << GPIO_VALUE_SHIFT) & BIT_MASK_RANGE(GPIO_VALUE_SHIFT + GPIO_VALUE_WIDTH - 1, GPIO_VALUE_SHIFT); +} + +/** + * Decode bits [19:10] from a 32-bit label into a 10-bit value. + */ +static inline uint32_t gpio_decode_value(uint32_t label) +{ + // mask to [19:10], then shift down + return (label & BIT_MASK_RANGE(GPIO_VALUE_SHIFT + GPIO_VALUE_WIDTH - 1, GPIO_VALUE_SHIFT)) >> GPIO_VALUE_SHIFT; +} + +#define SDDF_GPIO_RESPONSE_ERROR_BIT 19 +#define SDDF_GPIO_RESPONSE_VALUE_MASK BIT_MASK_RANGE(18, 0) + +// TODO: come up with interface for set_configs config + +typedef enum { + SDDF_GPIO_EOPNOTSUPP = 1, + SDDF_GPIO_EINVAL = 2, + SDDF_GPIO_EPERM = 3, +} gpio_error_t; diff --git a/include/sddf/util/util.h b/include/sddf/util/util.h index 478ecdbfb..8c8eb7acf 100644 --- a/include/sddf/util/util.h +++ b/include/sddf/util/util.h @@ -16,6 +16,10 @@ #define BIT(nr) (1UL << (nr)) #endif +// [high, low] +#define BIT_MASK_RANGE(high, low) \ + (((1UL << ((high) - (low) + 1)) - 1) << (low)) + #ifdef __GNUC__ #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) diff --git a/tools/make/board/maaxboard.mk b/tools/make/board/maaxboard.mk index 5c9372dfb..7af64403c 100644 --- a/tools/make/board/maaxboard.mk +++ b/tools/make/board/maaxboard.mk @@ -13,5 +13,6 @@ NET_DRIV_DIR := ${PLATFORM} ETH_DRIV := eth_driver_${PLATFORM}.elf TIMER_DRIV_DIR := ${PLATFORM} UART_DRIV_DIR := ${PLATFORM} +GPIO_DRIV_DIR := ${PLATFORM} CPU := cortex-a53 diff --git a/tools/meta/board.py b/tools/meta/board.py index f65ccc854..68930b2ac 100644 --- a/tools/meta/board.py +++ b/tools/meta/board.py @@ -17,6 +17,7 @@ class Board: partition: int = 0 blk: Optional[str] = None baud_rate: Optional[int] = None + gpio: Optional[str] = None # Keep this list in alphabetical order by board name @@ -75,6 +76,7 @@ class Board: timer="soc@0/bus@30000000/timer@302d0000", ethernet="soc@0/bus@30800000/ethernet@30be0000", blk="soc@0/bus@30800000/mmc@30b40000", + gpio="soc@0/bus@30000000/gpio@30200000", partition=2, ), Board(