diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml index 54deab4e9..23effb609 100644 --- a/.github/workflows/examples.yaml +++ b/.github/workflows/examples.yaml @@ -23,6 +23,7 @@ env: MICROKIT_VERSION: 2.3.0 MICROKIT_URL: https://github.com/seL4/microkit/releases/download/2.3.0/ SDFGEN_VERSION: 0.35.0 + CAKE_JOB_ID: 3440 jobs: build_linux_x86_64: @@ -51,6 +52,39 @@ jobs: env: PYTHON: ${{ github.workspace }}/venv/bin/python + build_pancake_linux_x86_64: + name: Build Pancake (Linux x86-64) + runs-on: ubuntu-24.04 + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Download Microkit SDK + run: | + wget ${{ env.MICROKIT_URL }}/microkit-sdk-${{ env.MICROKIT_VERSION }}-linux-x86-64.tar.gz + tar xf microkit-sdk-${{ env.MICROKIT_VERSION }}-linux-x86-64.tar.gz + - name: Install dependencies (via apt) + run: sudo apt update && sudo apt install -y make llvm lld imagemagick device-tree-compiler + - name: Setup pyenv + run: | + python3 -m venv venv + ./venv/bin/pip install --upgrade sdfgen==${{ env.SDFGEN_VERSION }} + - name: Setup systems-ci + uses: au-ts/systems-ci@main + with: + path: systems-ci + - name: Setup Pancake compiler + run: | + wget https://cakeml.org/regression/artefacts/${{ env.CAKE_JOB_ID }}/cake-x64-64.tar.gz + tar -xf cake-x64-64.tar.gz + cd cake-x64-64 + make + echo "$(pwd):$PATH" >> $GITHUB_PATH + - name: Build examples + run: ./ci/build.py ${PWD}/microkit-sdk-${{ env.MICROKIT_VERSION }} $(nproc) --pancake --example=serial + shell: bash + env: + PYTHON: ${{ github.workspace }}/venv/bin/python + build_linux_x86_64_nix: name: Build (Linux x86-64 Nix) runs-on: [self-hosted, Linux, X64] @@ -76,6 +110,31 @@ jobs: ci_build/**/sel4_32.elf if-no-files-found: error + build_pancake_linux_x86_64_nix: + name: Build Pancake (Linux x86-64 Nix) + runs-on: [self-hosted, Linux, X64] + steps: + - name: Checkout repository + uses: actions/checkout@v6 + - name: Setup systems-ci + uses: au-ts/systems-ci@main + with: + path: systems-ci + - name: Setup Nix + uses: cachix/install-nix-action@v27 + - name: Get Nix dependencies + run: nix develop -c bash -c 'echo Hello World' + - name: Build examples + run: nix develop --ignore-environment -c bash -c 'source systems-ci/setup.sh && CI=1 ./ci/build.py ${PWD}/microkit-sdk-${{ env.MICROKIT_VERSION }} $(nproc) --pancake --example=serial' + - name: Archive image artifacts + uses: actions/upload-artifact@v7 + with: + name: loader-images-pancake + path: | + ci_build/**/loader.img + ci_build/**/sel4_32.elf + if-no-files-found: error + build_macos_arm64_nix: name: Build (macOS ARM64 Nix) runs-on: [self-hosted, macos, ARM64] diff --git a/ci/build.py b/ci/build.py index 3027b353c..bef998fa6 100755 --- a/ci/build.py +++ b/ci/build.py @@ -26,6 +26,11 @@ def build_make(args: argparse.Namespace, test_config: common.TestConfig): build_dir = common.example_build_path(test_config) example_dir = get_example_dir(test_config.example) + if args.pancake: + pancake_args = ["PANCAKE_SERIAL_DRIVER=1"] + else: + pancake_args = [] + subprocess.run( [ "make", @@ -35,7 +40,8 @@ def build_make(args: argparse.Namespace, test_config: common.TestConfig): f"MICROKIT_SDK={args.microkit_sdk}", f"MICROKIT_BOARD={test_config.board}", f"MICROKIT_CONFIG={test_config.config}", - ], + ] + + pancake_args, check=True, ) @@ -137,6 +143,9 @@ def build(args: argparse.Namespace, test_config: common.TestConfig): action="store_true", help="Do not remove any pre-existing CI build directory before building", ) + parser.add_argument( + "--pancake", action="store_true", help="Use Pancake implementations" + ) filters = parser.add_argument_group(title="filters") filters.add_argument( diff --git a/ci/cakeml.nix b/ci/cakeml.nix new file mode 100644 index 000000000..2722ed046 --- /dev/null +++ b/ci/cakeml.nix @@ -0,0 +1,49 @@ +{ + stdenv, + fetchzip, + pancakeVersion ? 3440, +}: + +stdenv.mkDerivation (rec { + pname = "pancake"; + version = "3440"; + + src = + let + hostArch = stdenv.hostPlatform.qemuArch; + + cakemlPlatformNameAndHash = + { + aarch64 = { + arch = "arm8-64"; + hash = "sha256-1xVVQFunYSxMvGkmApWLfZ+s+hvQ2B5a7sJsoE1K5Qg="; + }; + x86_64 = { + arch = "x64-64"; + hash = "sha256-uNt5HhpBixfEItqdfWuDxt/RKq2kySyh7Fbk08tmSx4="; + }; + } + .${hostArch} or (throw "Unsupported architecture: ${hostArch}"); + in + fetchzip { + url = "https://cakeml.org/regression/artefacts/${toString pancakeVersion}/cake-${cakemlPlatformNameAndHash.arch}.tar.gz"; + hash = cakemlPlatformNameAndHash.hash; + }; + + buildPhase = '' + runHook preBuild + + make + + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + + mkdir -p $out/bin + cp cake $out/bin/ + + runHook postInstall + ''; +}) diff --git a/drivers/serial/arm/serial_driver.mk b/drivers/serial/arm/serial_driver.mk index 45ff32788..3f1ccc33a 100644 --- a/drivers/serial/arm/serial_driver.mk +++ b/drivers/serial/arm/serial_driver.mk @@ -8,11 +8,30 @@ SERIAL_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +ifeq ($(PANCAKE_SERIAL_DRIVER),1) +DRIVER_PNK = \ + ${UTIL}/util.pnk \ + ${SDDF}/include/sddf/serial/queue.pnk \ + ${SERIAL_DRIVER_DIR}/uart.pnk + +serial_driver.elf: serial/arm/serial_driver_pnk.o serial/arm/serial_driver.o util/pancake_ffi.o + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +serial/arm/serial_driver_pnk.o: serial/arm/serial_driver_pnk.S |serial/arm + $(CC) -c $(CFLAGS) -o $@ $< + +serial/arm/serial_driver_pnk.S: $(DRIVER_PNK) |serial/arm + cat $(DRIVER_PNK) | cpp -P | $(PANCAKE_COMPILER) $(PANCAKE_FLAGS) > $@ + +serial/arm/serial_driver.o: ${SERIAL_DRIVER_DIR}/uart.c |serial/arm $(SDDF_LIBC_INCLUDE) + $(CC) -c $(CFLAGS) -DPANCAKE_SERIAL_DRIVER -I${SERIAL_DRIVER_DIR}/include -o $@ $< +else serial_driver.elf: serial/arm/serial_driver.o $(LD) $(LDFLAGS) $< $(LIBS) -o $@ serial/arm/serial_driver.o: ${SERIAL_DRIVER_DIR}/uart.c |serial/arm $(SDDF_LIBC_INCLUDE) $(CC) -c $(CFLAGS) -I${SERIAL_DRIVER_DIR}/include -o $@ $< +endif serial/arm: mkdir -p $@ @@ -20,6 +39,6 @@ serial/arm: -include serial/arm/serial_driver.d clean:: - rm -f serial/arm/serial_driver.[do] + rm -f serial/arm/serial_driver.[do] serial/arm/serial_driver_pnk.[oS] clobber:: clean rm -rf serial_driver.elf serial diff --git a/drivers/serial/arm/uart.c b/drivers/serial/arm/uart.c index 07f381d81..6b5033516 100644 --- a/drivers/serial/arm/uart.c +++ b/drivers/serial/arm/uart.c @@ -15,6 +15,43 @@ __attribute__((__section__(".serial_driver_config"))) serial_driver_config_t con __attribute__((__section__(".device_resources"))) device_resources_t device_resources; +#ifdef PANCAKE_SERIAL_DRIVER +static char cml_memory[1024 * 20]; +extern void *cml_heap; +extern void *cml_stack; +extern void *cml_stackend; + +extern void cml_main(void); + +void cml_exit(int arg) +{ + microkit_dbg_puts("ERROR! We should not be getting here\n"); +} + +void cml_err(int arg) +{ + if (arg == 3) { + microkit_dbg_puts("Memory not ready for entry. You may have not run the init code yet, or be trying to enter " + "during an FFI call.\n"); + } + cml_exit(arg); +} + +void cml_clear() +{ + microkit_dbg_puts("Trying to clear cache\n"); +} + +void init_pancake_mem() +{ + unsigned long cml_heap_sz = 1024 * 10; + unsigned long cml_stack_sz = 1024 * 10; + cml_heap = cml_memory; + cml_stack = cml_heap + cml_heap_sz; + cml_stackend = cml_stack + cml_stack_sz; +} +#endif /* PANCAKE_SERIAL_DRIVER */ + serial_queue_handle_t rx_queue_handle; serial_queue_handle_t tx_queue_handle; @@ -39,6 +76,7 @@ static void set_baud(long bps) uart_regs->fbrd = baud_div_frac; } +#ifndef PANCAKE_SERIAL_DRIVER static void tx_provide(void) { bool transferred = false; @@ -104,6 +142,7 @@ static void handle_irq(void) uart_int_reg = uart_regs->mis; } } +#endif /* PANCAKE_SERIAL_DRIVER */ static void uart_setup(void) { @@ -165,8 +204,27 @@ void init(void) serial_queue_init(&rx_queue_handle, config.rx.queue.vaddr, config.rx.data.size, config.rx.data.vaddr); } serial_queue_init(&tx_queue_handle, config.tx.queue.vaddr, config.tx.data.size, config.tx.data.vaddr); + +#ifdef PANCAKE_SERIAL_DRIVER + init_pancake_mem(); + + uintptr_t *pnk_mem = (uintptr_t *)cml_heap; + + pnk_mem[0] = (uintptr_t)uart_regs; + pnk_mem[1] = device_resources.irqs[0].id; + pnk_mem[2] = config.rx.id; + pnk_mem[3] = config.tx.id; + pnk_mem[4] = (uintptr_t)&rx_queue_handle; + pnk_mem[5] = (uintptr_t)&tx_queue_handle; + pnk_mem[1024] = config.rx_enabled; + + cml_main(); +#endif /* PANCAKE_SERIAL_DRIVER */ } +#ifdef PANCAKE_SERIAL_DRIVER +extern void notified(sddf_channel ch); +#else void notified(sddf_channel ch) { if (ch == device_resources.irqs[0].id) { @@ -181,3 +239,4 @@ void notified(sddf_channel ch) sddf_dprintf("UART|LOG: received notification on unexpected channel: %u\n", ch); } } +#endif /* PANCAKE_SERIAL_DRIVER */ diff --git a/drivers/serial/arm/uart.pnk b/drivers/serial/arm/uart.pnk new file mode 100644 index 000000000..6d37e5b30 --- /dev/null +++ b/drivers/serial/arm/uart.pnk @@ -0,0 +1,258 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define UART_REG_BASE pnk_mem(0) + +#define IRQ_CH pnk_mem(1) +#define RX_CH pnk_mem(2) +#define TX_CH pnk_mem(3) + +#define SERIAL_RX_QUEUE_HANDLE pnk_mem(4) +#define SERIAL_TX_QUEUE_HANDLE pnk_mem(5) + +#define RX_ENABLED pnk_mem(1024) + +#define PL011_DR 0 /* 0x000 Data Register */ +#define PL011_RSR_ECR 4 /* 0x004 Receive Status Register/Error Clear Register */ +#define PL011_FR 24 /* 0x018 Flag Register */ +#define PL011_ILPR 32 /* 0x020 IrDA Low-Power Counter Register */ +#define PL011_IBRD 36 /* 0x024 Integer Baud Rate Register */ +#define PL011_FBRD 40 /* 0x028 Fractional Baud Rate Register */ +#define PL011_LCR_H 44 /* 0x02c Line Control Register */ +#define PL011_TCR 48 /* 0x030 Control Register */ +#define PL011_IFLS 52 /* 0x034 Interrupt FIFO Level Select Register */ +#define PL011_IMSC 56 /* 0x038 Interrupt Mask Set/Clear Register */ +#define PL011_RIS 60 /* 0x03C Raw Interrupt Status Register */ +#define PL011_MIS 64 /* 0x040 Masked Interrupt Status Register */ +#define PL011_ICR 68 /* 0x044 Interrupt Clear Register */ +#define PL011_DMACR 72 /* 0x048 DMA Control Register */ + +/* Data Register bits */ +#define PL011_DR_DATA_MASK 255 /* Read or write from/to these bits to rx or tx. */ + +/* Flag Register bits */ +#define PL011_FR_TXFE BIT(7) /* Transmit FIFO empty. */ +#define PL011_FR_RXFF BIT(6) /* Receive FIFO full. */ +#define PL011_FR_TXFF BIT(5) /* Transmit FIFO full. */ +#define PL011_FR_RXFE BIT(4) /* Receive FIFO empty. */ +#define PL011_FR_UART_BUSY BIT(3) /* Uart busy transmitting data. */ + +/* Line Control Register bits */ +#define PL011_LCR_WLEN_MASK 3 /* Word length. b00 = 5, b01 = 6, b10 = 7, b11 = 8. */ +#define PL011_LCR_WLEN_SHFT 5 +#define PL011_LCR_FIFO_EN BIT(4) /* Enable tx and rx FIFOs. */ +#define PL011_LCR_2_STP_BITS BIT(3) /* Set this bit to 1 to tx two stop bits. */ +#define PL011_LCR_PARTY_EVEN BIT(2) /* Even parity select. */ +#define PL011_LCR_PARTY_EN BIT(1) /* Enable parity checks and addition. */ + +/* Control Register */ +#define PL011_CR_RX_EN BIT(9) /* Enable rx. */ +#define PL011_CR_TX_EN BIT(8) /* Enable tx. */ +#define PL011_CR_UART_EN BIT(0) /* Enable the uart. */ + +/* Interrupt FIFO Level Select Register */ +#define PL011_IFLS_RX_MASK 7 /* Rx interrupt level select. b000 = 1/8, b001 = 1/4, b010 = 1/2, b011 = 3/4, b100 = 7/8. */ +#define PL011_IFLS_RX_SHFT 3 +#define PL011_IFLS_TX_MASK 7 /* Tx interrupt level select. b000 = 1/8, b001 = 1/4, b010 = 1/2, b011 = 3/4, b100 = 7/8. */ +#define PL011_IFLS_TX_SHFT 0 + +/* Interrupt Mask Set/Clear Register */ +#define PL011_IMSC_RX_TIMEOUT BIT(6) /* Enable rx timeout interrupt. Occurs when the rx FIFO is not empty, and no more data is received during a 32-bit period. */ +#define PL011_IMSC_TX_INT BIT(5) /* Enable tx interrupt when FIFO drops below programmed threshold. */ +#define PL011_IMSC_RX_INT BIT(4) /* Enable rx interrupt when FIFO exceeds programmed threshold. */ + +/* Masked Interrupt Status Register */ +#define PL011_IMSC_RX_TIMEOUT BIT(6) /* Rx timeout interrupt. Occurs when the rx FIFO is not empty, and no more data is received during a 32-bit period. */ +#define PL011_IMSC_TX_INT BIT(5) /* Tx interrupt when FIFO drops below programmed threshold. */ +#define PL011_IMSC_RX_INT BIT(4) /* Rx interrupt when FIFO exceeds programmed threshold. */ + +inline fun uart_read_reg(1 offset) +{ + var reg_addr = UART_REG_BASE + offset; + var val = 0; + !ld32 val, reg_addr; + return val; +} + +inline fun uart_write_reg(1 offset, 1 val) +{ + var reg_addr = UART_REG_BASE + offset; + !st32 reg_addr, val; + return 0; +} + +inline fun uart_read_fr() +{ + return uart_read_reg(PL011_FR); +} + +inline fun uart_read_dr() +{ + var dr = uart_read_reg(PL011_DR); + return dr & PL011_DR_DATA_MASK; +} + +inline fun uart_write_dr(1 c) +{ + uart_write_reg(PL011_DR, c); + return 0; +} + +inline fun uart_read_imsc() +{ + return uart_read_reg(PL011_IMSC); +} + +inline fun uart_write_imsc(1 val) +{ + uart_write_reg(PL011_IMSC, val); + return 0; +} + +inline fun uart_read_mis() +{ + return uart_read_reg(PL011_MIS); +} + +fun tx_provide() +{ + var tx_queue_handle = SERIAL_TX_QUEUE_HANDLE; + + var transferred = false; + while (true) { + var fr = uart_read_fr(); + if (fr & PL011_FR_TXFF) { + break; + } + + var {1, 1} res = serial_dequeue(tx_queue_handle); + if (res.0) { + break; + } + uart_write_dr(res.1 & MAX_UINT8); + transferred = true; + } + + var head = serial_queue_get_head(tx_queue_handle); + var is_empty = serial_queue_empty(tx_queue_handle, head); + /* If there is data remaining to be sent, enable interrupt when fifo is no longer full */ + if (!is_empty) { + var imsc = uart_read_imsc(); + imsc = imsc | PL011_IMSC_TX_INT; + uart_write_imsc(imsc); + } else { + var imsc = uart_read_imsc(); + imsc = imsc & bitwise_not(PL011_IMSC_TX_INT); + uart_write_imsc(imsc); + } + + if (transferred) { + var require_signal = serial_require_consumer_signal(tx_queue_handle); + if (require_signal) { + serial_cancel_consumer_signal(tx_queue_handle); + sddf_notify(TX_CH); + } + } + + return 0; +} + +fun rx_return() +{ + var rx_queue_handle = SERIAL_RX_QUEUE_HANDLE; + + var reprocess = true; + var enqueued = false; + while (reprocess) { + while (true) { + var fr = uart_read_fr(); + if (fr & PL011_FR_RXFE) { + break; + } + + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if (is_full) { + break; + } + + var c = uart_read_dr(); + var err = serial_enqueue(rx_queue_handle, c & MAX_UINT8); + enqueued = true; + } + + var fr = uart_read_fr(); + if (!(fr & PL011_FR_RXFE)) { + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if (is_full) { + var imsc = uart_read_imsc(); + /* Disable rx interrupts until virtualisers queue is no longer full. */ + imsc = imsc & bitwise_not(PL011_IMSC_RX_TIMEOUT | PL011_IMSC_RX_INT); + uart_write_imsc(imsc); + serial_request_consumer_signal(rx_queue_handle); + } + } + + reprocess = false; + + fr = uart_read_fr(); + if (!(fr & PL011_FR_RXFE)) { + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if (!is_full) { + serial_cancel_consumer_signal(rx_queue_handle); + var imsc = uart_read_imsc(); + imsc = imsc | (PL011_IMSC_RX_TIMEOUT | PL011_IMSC_RX_INT); + uart_write_imsc(imsc); + reprocess = true; + } + } + } + + if (enqueued) { + sddf_notify(RX_CH); + } + + return 0; +} + +fun handle_irq() { + var uart_int_reg = uart_read_mis(); + while (uart_int_reg & (PL011_IMSC_RX_TIMEOUT | PL011_IMSC_RX_INT) || uart_int_reg & PL011_IMSC_TX_INT) { + var rx_enabled = RX_ENABLED; + if (rx_enabled && uart_int_reg & (PL011_IMSC_RX_TIMEOUT | PL011_IMSC_RX_INT)) { + rx_return(); + } + if (uart_int_reg & PL011_IMSC_TX_INT) { + tx_provide(); + } + uart_int_reg = uart_read_mis(); + } + return 0; +} + +export fun notified(1 ch) +{ + if (ch == IRQ_CH) { + handle_irq(); + sddf_deferred_irq_ack(ch); + } else { + if (ch == TX_CH) { + tx_provide(); + } else { + if (ch == RX_CH) { + var imsc = uart_read_imsc(); + imsc = imsc | (PL011_IMSC_RX_TIMEOUT | PL011_IMSC_RX_INT); + uart_write_imsc(imsc); + rx_return(); + } else { + debug_print(ch); + } + } + } + + return 0; +} diff --git a/drivers/serial/imx/serial_driver.mk b/drivers/serial/imx/serial_driver.mk index 4af3b7680..c5659b44a 100644 --- a/drivers/serial/imx/serial_driver.mk +++ b/drivers/serial/imx/serial_driver.mk @@ -8,11 +8,30 @@ SERIAL_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +ifeq ($(PANCAKE_SERIAL_DRIVER),1) +DRIVER_PNK = \ + ${UTIL}/util.pnk \ + ${SDDF}/include/sddf/serial/queue.pnk \ + ${SERIAL_DRIVER_DIR}/uart.pnk + +serial_driver.elf: serial/imx/serial_driver_pnk.o serial/imx/serial_driver.o util/pancake_ffi.o + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +serial/imx/serial_driver_pnk.o: serial/imx/serial_driver_pnk.S |serial/imx + $(CC) -c $(CFLAGS) -o $@ $< + +serial/imx/serial_driver_pnk.S: $(DRIVER_PNK) |serial/imx + cat $(DRIVER_PNK) | cpp -P | $(PANCAKE_COMPILER) $(PANCAKE_FLAGS) > $@ + +serial/imx/serial_driver.o: ${SERIAL_DRIVER_DIR}/uart.c |serial/imx $(SDDF_LIBC_INCLUDE) + $(CC) -c $(CFLAGS) -DPANCAKE_SERIAL_DRIVER -I${SERIAL_DRIVER_DIR}/include -o $@ $< +else serial_driver.elf: serial/imx/serial_driver.o $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ serial/imx/serial_driver.o: ${SERIAL_DRIVER_DIR}/uart.c |serial/imx $(SDDF_LIBC_INCLUDE) $(CC) -c $(CFLAGS) -I${SERIAL_DRIVER_DIR}/include -o $@ $< +endif -include serial_driver.d @@ -20,7 +39,7 @@ serial/imx: mkdir -p $@ clean:: - rm -f serial/imx/serial_driver.[do] + rm -f serial/imx/serial_driver.[do] serial/imx/serial_driver_pnk.[oS] clobber:: rm -rf serial diff --git a/drivers/serial/imx/uart.c b/drivers/serial/imx/uart.c index 8cb063471..e0dbfe7a0 100644 --- a/drivers/serial/imx/uart.c +++ b/drivers/serial/imx/uart.c @@ -16,6 +16,43 @@ __attribute__((__section__(".device_resources"))) device_resources_t device_reso __attribute__((__section__(".serial_driver_config"))) serial_driver_config_t config; +#ifdef PANCAKE_SERIAL_DRIVER +static char cml_memory[1024 * 20]; +extern void *cml_heap; +extern void *cml_stack; +extern void *cml_stackend; + +extern void cml_main(void); + +void cml_exit(int arg) +{ + microkit_dbg_puts("ERROR! We should not be getting here\n"); +} + +void cml_err(int arg) +{ + if (arg == 3) { + microkit_dbg_puts("Memory not ready for entry. You may have not run the init code yet, or be trying to enter " + "during an FFI call.\n"); + } + cml_exit(arg); +} + +void cml_clear() +{ + microkit_dbg_puts("Trying to clear cache\n"); +} + +void init_pancake_mem() +{ + unsigned long cml_heap_sz = 1024 * 10; + unsigned long cml_stack_sz = 1024 * 10; + cml_heap = cml_memory; + cml_stack = cml_heap + cml_heap_sz; + cml_stackend = cml_stack + cml_stack_sz; +} +#endif /* PANCAKE_SERIAL_DRIVER */ + serial_queue_handle_t rx_queue_handle; serial_queue_handle_t tx_queue_handle; @@ -40,6 +77,7 @@ static void set_baud(long bps) uart_regs->bmr = bmr; } +#ifndef PANCAKE_SERIAL_DRIVER static void tx_provide(void) { bool transferred = false; @@ -112,6 +150,7 @@ static void handle_irq(void) uart_cr1 = uart_regs->cr1; } } +#endif /* PANCAKE_SERIAL_DRIVER */ static void uart_setup(void) { @@ -170,8 +209,27 @@ void init(void) serial_queue_init(&rx_queue_handle, config.rx.queue.vaddr, config.rx.data.size, config.rx.data.vaddr); } serial_queue_init(&tx_queue_handle, config.tx.queue.vaddr, config.tx.data.size, config.tx.data.vaddr); + +#ifdef PANCAKE_SERIAL_DRIVER + init_pancake_mem(); + + uintptr_t *pnk_mem = (uintptr_t *)cml_heap; + + pnk_mem[0] = (uintptr_t)uart_regs; + pnk_mem[1] = device_resources.irqs[0].id; + pnk_mem[2] = config.rx.id; + pnk_mem[3] = config.tx.id; + pnk_mem[4] = (uintptr_t)&rx_queue_handle; + pnk_mem[5] = (uintptr_t)&tx_queue_handle; + pnk_mem[1024] = config.rx_enabled; + + cml_main(); +#endif /* PANCAKE_SERIAL_DRIVER */ } +#ifdef PANCAKE_SERIAL_DRIVER +extern void notified(sddf_channel ch); +#else void notified(sddf_channel ch) { if (ch == device_resources.irqs[0].id) { @@ -186,3 +244,4 @@ void notified(sddf_channel ch) sddf_dprintf("UART|LOG: received notification on unexpected channel: %u\n", ch); } } +#endif /* PANCAKE_SERIAL_DRIVER */ diff --git a/drivers/serial/imx/uart.pnk b/drivers/serial/imx/uart.pnk new file mode 100644 index 000000000..2468d6aa1 --- /dev/null +++ b/drivers/serial/imx/uart.pnk @@ -0,0 +1,259 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define UART_REG_BASE pnk_mem(0) + +#define IRQ_CH pnk_mem(1) +#define RX_CH pnk_mem(2) +#define TX_CH pnk_mem(3) + +#define SERIAL_RX_QUEUE_HANDLE pnk_mem(4) +#define SERIAL_TX_QUEUE_HANDLE pnk_mem(5) + +#define RX_ENABLED pnk_mem(1024) + +#define RXD_OFFSET 0 +#define TXD_OFFSET 64 +#define CR1_OFFSET 128 +#define CR2_OFFSET 132 +#define CR3_OFFSET 136 +#define CR4_OFFSET 140 +#define FCR_OFFSET 144 +#define SR1_OFFSET 148 +#define SR2_OFFSET 152 +#define ESC_OFFSET 156 +#define TIM_OFFSET 160 +#define BIR_OFFSET 164 +#define BMR_OFFSET 168 +#define BRC_OFFSET 172 +#define ONEMS_OFFSET 176 +#define TS_OFFSET 180 + +/* Control Register 1 bits */ +#define UART_CR1_UART_EN BIT(0) /* Enables the UART. */ +#define UART_CR1_RX_READY_INT BIT(9) /* Enables an interrupt when the rx FIFO contains data. */ +#define UART_CR1_TX_READY_INT BIT(13) /* Enables the tx ready interrupt when the transmitter has one or more slots available in the tx FIFO. */ + +/* Control Register 2 bits */ +#define UART_CR2_RX_EN BIT(1) /* Enables the receiver. */ +#define UART_CR2_TX_EN BIT(2) /* Enables the transmitter. */ + +/* Status Register 1 bits */ +#define UART_SR1_RX_RDY BIT(9) /* Rx FIFO is above the threshold. Automatically cleared when FIFO goes below the set threshold. */ +#define UART_SR1_FRM_ERR BIT(10) /* Frame error is detected. Write 1 to it to clear. */ +#define UART_SR1_TX_RDY BIT(13) /* Tx FIFO emptied below target threshold. Automatically cleared when FIFO is filled. */ +#define UART_SR1_PARITY_ERR BIT(15) /* Parity error was detected. Write 1 to it to clear. */ + +#define UART_SR1_ABNORMAL (UART_SR1_PARITY_ERR | UART_SR1_FRM_ERR) + +/* Test Register bits */ +#define UART_TST_TX_FIFO_FULL BIT(4) /* Tx FIFO is full. */ +#define UART_TST_RX_FIFO_EMPTY BIT(5) /* Rx FIFO is empty. */ + + +inline fun uart_read_rxd() +{ + var reg_addr = UART_REG_BASE + RXD_OFFSET; + var rxd = 0; + !ld32 rxd, reg_addr; + return rxd; +} + +inline fun uart_write_txd(1 data) +{ + var reg_addr = UART_REG_BASE + TXD_OFFSET; + !st32 reg_addr, data; + return 0; +} + +inline fun uart_read_cr1() +{ + var reg_addr = UART_REG_BASE + CR1_OFFSET; + var cr1 = 0; + !ld32 cr1, reg_addr; + return cr1; +} + +inline fun uart_write_cr1(1 cr1) +{ + var reg_addr = UART_REG_BASE + CR1_OFFSET; + !st32 reg_addr, cr1; + return 0; +} + +inline fun uart_read_sr1() +{ + var reg_addr = UART_REG_BASE + SR1_OFFSET; + var sr1 = 0; + !ld32 sr1, reg_addr; + return sr1; +} + +inline fun uart_write_sr1(1 sr1) +{ + var reg_addr = UART_REG_BASE + SR1_OFFSET; + !st32 reg_addr, sr1; + return 0; +} + +inline fun uart_read_ts() +{ + var reg_addr = UART_REG_BASE + TS_OFFSET; + var ts = 0; + !ld32 ts, reg_addr; + return ts; +} + +export fun tx_provide() +{ + var tx_queue_handle = SERIAL_TX_QUEUE_HANDLE; + + var transferred = false; + while (true) { + var ts = uart_read_ts(); + if (ts & UART_TST_TX_FIFO_FULL) { + break; + } + + var {1, 1} res = serial_dequeue(tx_queue_handle); + if (res.0) { + break; + } + uart_write_txd(res.1 & MAX_UINT8); + transferred = true; + break; + } + + var head = serial_queue_get_head(tx_queue_handle); + var is_empty = serial_queue_empty(tx_queue_handle, head); + /* If there is data remaining to be sent, enable interrupt when fifo is no longer full */ + if (!is_empty) { + var cr1 = uart_read_cr1(); + cr1 = cr1 | UART_CR1_TX_READY_INT; + uart_write_cr1(cr1); + } else { + var cr1 = uart_read_cr1(); + cr1 = cr1 & bitwise_not(UART_CR1_TX_READY_INT); + uart_write_cr1(cr1); + } + + if (transferred) { + var require_signal = serial_require_consumer_signal(tx_queue_handle); + if (require_signal) { + serial_cancel_consumer_signal(tx_queue_handle); + sddf_notify(TX_CH); + } + } + + return 0; +} + +fun rx_return() +{ + var rx_queue_handle = SERIAL_RX_QUEUE_HANDLE; + + var reprocess = true; + var enqueued = false; + while (reprocess) { + while (true) { + var ts = uart_read_ts(); + if (ts & UART_TST_RX_FIFO_EMPTY) { + break; + } + + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if (is_full) { + break; + } + + var rxd = uart_read_rxd(); + var c = rxd & MAX_UINT8; + serial_enqueue(rx_queue_handle, c); + enqueued = true; + } + + var ts = uart_read_ts(); + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if ((!(ts & UART_TST_RX_FIFO_EMPTY)) && is_full) { + var cr1 = uart_read_cr1(); + cr1 = cr1 & bitwise_not(UART_CR1_RX_READY_INT); + uart_write_cr1(cr1); + serial_request_consumer_signal(rx_queue_handle); + } + + reprocess = false; + + ts = uart_read_ts(); + tail = serial_queue_get_tail(rx_queue_handle); + is_full = serial_queue_full(rx_queue_handle, tail); + if ((!(ts & UART_TST_RX_FIFO_EMPTY)) && (!is_full)) { + serial_cancel_consumer_signal(rx_queue_handle); + var cr1 = uart_read_cr1(); + cr1 = cr1 | UART_CR1_RX_READY_INT; + uart_write_cr1(cr1); + reprocess = true; + } + } + + if (enqueued) { + sddf_notify(RX_CH); + } + + return 0; +} + +fun handle_irq() +{ + var uart_sr1 = uart_read_sr1(); + var uart_cr1 = uart_read_cr1(); + while ((uart_sr1 & UART_SR1_ABNORMAL) || (uart_sr1 & UART_SR1_RX_RDY) || + ((uart_cr1 & UART_CR1_TX_READY_INT) && (uart_sr1 & UART_SR1_TX_RDY))) { + if (RX_ENABLED && (uart_sr1 & UART_SR1_RX_RDY)) { + rx_return(); + } + + if ((uart_cr1 & UART_CR1_TX_READY_INT) && (uart_sr1 & UART_SR1_TX_RDY)) { + tx_provide(); + } + + if (uart_sr1 & UART_SR1_ABNORMAL) { + debug_print(uart_sr1); + uart_cr1 = uart_read_cr1(); + uart_write_sr1(uart_cr1 | UART_SR1_ABNORMAL); + } + + uart_sr1 = uart_read_sr1(); + uart_cr1 = uart_read_cr1(); + } + + return 0; +} + + +export fun notified(1 ch) +{ + if (ch == IRQ_CH) { + handle_irq(); + sddf_deferred_irq_ack(ch); + } else { + if (ch == TX_CH) { + tx_provide(); + } else { + if (ch == RX_CH) { + var cr1 = uart_read_cr1(); + cr1 = cr1 | UART_CR1_RX_READY_INT; + uart_write_cr1(cr1); + + rx_return(); + } else { + debug_print(ch); + } + } + } + + return 0; +} diff --git a/drivers/serial/meson/serial_driver.mk b/drivers/serial/meson/serial_driver.mk index c17780a6a..461f59687 100644 --- a/drivers/serial/meson/serial_driver.mk +++ b/drivers/serial/meson/serial_driver.mk @@ -8,11 +8,30 @@ SERIAL_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +ifeq ($(PANCAKE_SERIAL_DRIVER),1) +DRIVER_PNK = \ + ${UTIL}/util.pnk \ + ${SDDF}/include/sddf/serial/queue.pnk \ + ${SERIAL_DRIVER_DIR}/uart.pnk + +serial_driver.elf: serial/meson/serial_driver_pnk.o serial/meson/serial_driver.o util/pancake_ffi.o libsddf_util_debug.a + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +serial/meson/serial_driver_pnk.o: serial/meson/serial_driver_pnk.S |serial/meson + $(CC) -c $(CFLAGS) -o $@ $< + +serial/meson/serial_driver_pnk.S: $(DRIVER_PNK) |serial/meson + cat $(DRIVER_PNK) | cpp -P | $(PANCAKE_COMPILER) $(PANCAKE_FLAGS) > $@ + +serial/meson/serial_driver.o: ${SERIAL_DRIVER_DIR}/uart.c |serial/meson $(SDDF_LIBC_INCLUDE) + $(CC) -c $(CFLAGS) -DPANCAKE_SERIAL_DRIVER -I${SERIAL_DRIVER_DIR}/include -o $@ $< +else serial_driver.elf: serial/meson/serial_driver.o libsddf_util_debug.a $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ serial/meson/serial_driver.o: ${SERIAL_DRIVER_DIR}/uart.c |serial/meson $(SDDF_LIBC_INCLUDE) $(CC) -c $(CFLAGS) -I${SERIAL_DRIVER_DIR}/include -o $@ $< +endif serial/meson: mkdir -p $@ @@ -20,7 +39,7 @@ serial/meson: -include serial/meson/serial_driver.d clean:: - rm -f serial/meson/serial_driver.[do] + rm -f serial/meson/serial_driver.[do] serial/meson/serial_driver_pnk.[oS] clobber:: rm -rf serial diff --git a/drivers/serial/meson/uart.c b/drivers/serial/meson/uart.c index a28afb1be..620f30bbc 100644 --- a/drivers/serial/meson/uart.c +++ b/drivers/serial/meson/uart.c @@ -15,6 +15,43 @@ __attribute__((__section__(".device_resources"))) device_resources_t device_reso __attribute__((__section__(".serial_driver_config"))) serial_driver_config_t config; +#ifdef PANCAKE_SERIAL_DRIVER +static char cml_memory[1024 * 20]; +extern void *cml_heap; +extern void *cml_stack; +extern void *cml_stackend; + +extern void cml_main(void); + +void cml_exit(int arg) +{ + microkit_dbg_puts("ERROR! We should not be getting here\n"); +} + +void cml_err(int arg) +{ + if (arg == 3) { + microkit_dbg_puts("Memory not ready for entry. You may have not run the init code yet, or be trying to enter " + "during an FFI call.\n"); + } + cml_exit(arg); +} + +void cml_clear() +{ + microkit_dbg_puts("Trying to clear cache\n"); +} + +void init_pancake_mem() +{ + unsigned long cml_heap_sz = 1024 * 10; + unsigned long cml_stack_sz = 1024 * 10; + cml_heap = cml_memory; + cml_stack = cml_heap + cml_heap_sz; + cml_stackend = cml_stack + cml_stack_sz; +} +#endif /* PANCAKE_SERIAL_DRIVER */ + serial_queue_handle_t rx_queue_handle; serial_queue_handle_t tx_queue_handle; @@ -71,6 +108,7 @@ static void set_baud(unsigned long baud) uart_regs->reg5 = baud_register; } +#ifndef PANCAKE_SERIAL_DRIVER static void tx_provide(void) { bool transferred = false; @@ -143,6 +181,7 @@ static void handle_irq(void) uart_cr = uart_regs->cr; } } +#endif /* PANCAKE_SERIAL_DRIVER */ static void uart_setup(void) { @@ -203,8 +242,27 @@ void init(void) serial_queue_init(&rx_queue_handle, config.rx.queue.vaddr, config.rx.data.size, config.rx.data.vaddr); } serial_queue_init(&tx_queue_handle, config.tx.queue.vaddr, config.tx.data.size, config.tx.data.vaddr); + +#ifdef PANCAKE_SERIAL_DRIVER + init_pancake_mem(); + + uintptr_t *pnk_mem = (uintptr_t *)cml_heap; + + pnk_mem[0] = (uintptr_t)uart_regs; + pnk_mem[1] = device_resources.irqs[0].id; + pnk_mem[2] = config.rx.id; + pnk_mem[3] = config.tx.id; + pnk_mem[4] = (uintptr_t)&rx_queue_handle; + pnk_mem[5] = (uintptr_t)&tx_queue_handle; + pnk_mem[1024] = config.rx_enabled; + + cml_main(); +#endif /* PANCAKE_SERIAL_DRIVER */ } +#ifdef PANCAKE_SERIAL_DRIVER +extern void notified(sddf_channel ch); +#else void notified(sddf_channel ch) { if (ch == device_resources.irqs[0].id) { @@ -219,3 +277,4 @@ void notified(sddf_channel ch) sddf_dprintf("UART|LOG: received notification on unexpected channel: %u\n", ch); } } +#endif /* PANCAKE_SERIAL_DRIVER */ diff --git a/drivers/serial/meson/uart.pnk b/drivers/serial/meson/uart.pnk new file mode 100644 index 000000000..62fa35bcf --- /dev/null +++ b/drivers/serial/meson/uart.pnk @@ -0,0 +1,225 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define UART_REG_BASE pnk_mem(0) + +#define IRQ_CH pnk_mem(1) +#define RX_CH pnk_mem(2) +#define TX_CH pnk_mem(3) + +#define SERIAL_RX_QUEUE_HANDLE pnk_mem(4) +#define SERIAL_TX_QUEUE_HANDLE pnk_mem(5) + +#define RX_ENABLED pnk_mem(1024) + +#define WFIFO_OFFSET 0 +#define RFIFO_OFFSET 4 +#define CR_OFFSET 8 +#define SR_OFFSET 12 + +/* AML_UART_CONTROL bits */ +#define AML_UART_CLEAR_ERR BIT(24) /* Clear error. */ +#define AML_UART_RX_INT_EN BIT(27) /* Rx byte interrupt. Set to 1 to enable interrupt generation when a byte is written to rx FIFO. */ +#define AML_UART_TX_INT_EN BIT(28) /* Tx byte interrupt. Set to 1 to enable interrupt generation when a byte is read from tx FIFO. */ + +/* AML_UART_STATUS bits */ +#define AML_UART_PARITY_ERR BIT(16) /* Parity error. Clear by writing bit 24 to control register. */ +#define AML_UART_FRAME_ERR BIT(17) /* Frame error. Clear by writing bit 24 to control register. */ +#define AML_UART_TX_FIFO_WERR BIT(18) /* This bit is set if the FIFO is written to when full. */ +#define AML_UART_RX_EMPTY BIT(20) /* rx FIFO empty. */ +#define AML_UART_TX_FULL BIT(21) /* tx FIFO full. */ +#define AML_UART_TX_BUSY BIT(25) /* tx state machine is busy. */ +#define AML_UART_RX_BUSY BIT(26) /* rx state machine is busy. */ + +#define UART_INTR_ABNORMAL (AML_UART_PARITY_ERR | AML_UART_FRAME_ERR | AML_UART_TX_FIFO_WERR) + + +inline fun uart_read_sr() +{ + var reg_addr = UART_REG_BASE + SR_OFFSET; + var sr = 0; + !ld32 sr, reg_addr; + return sr; +} + +inline fun uart_read_cr() +{ + var reg_addr = UART_REG_BASE + CR_OFFSET; + var cr = 0; + !ld32 cr, reg_addr; + return cr; +} + +inline fun uart_write_cr(1 cr) +{ + var reg_addr = UART_REG_BASE + CR_OFFSET; + !st32 reg_addr, cr; + return 0; +} + +inline fun uart_read_char() +{ + var reg_addr = UART_REG_BASE + RFIFO_OFFSET; + var c = 0; + !ld32 c, reg_addr; + return c; +} + +inline fun uart_write_char(1 c) +{ + var reg_addr = UART_REG_BASE + WFIFO_OFFSET; + !st32 reg_addr, c; + return 0; +} + +export fun tx_provide() +{ + var tx_queue_handle = SERIAL_TX_QUEUE_HANDLE; + + var transferred = false; + while (true) { + var sr = uart_read_sr(); + if (sr & AML_UART_TX_FULL) { + break; + } + + var {1, 1} res = serial_dequeue(tx_queue_handle); + if (res.0) { + break; + } + uart_write_char(res.1 & MAX_UINT8); + transferred = true; + } + + var head = serial_queue_get_head(tx_queue_handle); + var is_empty = serial_queue_empty(tx_queue_handle, head); + /* If there is data remaining to be sent, enable interrupt when fifo is no longer full */ + if (!is_empty) { + var cr = uart_read_cr(); + cr = cr | AML_UART_TX_INT_EN; + uart_write_cr(cr); + } else { + var cr = uart_read_cr(); + cr = cr & bitwise_not(AML_UART_TX_INT_EN); + uart_write_cr(cr); + } + + if (transferred) { + var require_signal = serial_require_consumer_signal(tx_queue_handle); + if (require_signal) { + serial_cancel_consumer_signal(tx_queue_handle); + sddf_notify(TX_CH); + } + } + + return 0; +} + +fun rx_return() +{ + var rx_queue_handle = SERIAL_RX_QUEUE_HANDLE; + + var reprocess = true; + var enqueued = false; + while (reprocess) { + while (true) { + var sr = uart_read_sr(); + if (sr & AML_UART_RX_EMPTY) { + break; + } + + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if (is_full) { + break; + } + + var c = uart_read_char(); + serial_enqueue(rx_queue_handle, c & MAX_UINT8); + enqueued = true; + } + + var sr = uart_read_sr(); + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if ((!(sr & AML_UART_RX_EMPTY)) && is_full) { + /* Disable rx interrupts until virtualisers queue is no longer full. */ + var cr = uart_read_cr(); + cr = cr & bitwise_not(AML_UART_RX_INT_EN); + uart_write_cr(cr); + serial_request_consumer_signal(rx_queue_handle); + } + + reprocess = false; + + sr = uart_read_sr(); + tail = serial_queue_get_tail(rx_queue_handle); + is_full = serial_queue_full(rx_queue_handle, tail); + if ((!(sr & AML_UART_RX_EMPTY)) && (!is_full)) { + serial_cancel_consumer_signal(rx_queue_handle); + var cr = uart_read_cr(); + cr = cr | AML_UART_RX_INT_EN; + uart_write_cr(cr); + reprocess = true; + } + } + + if (enqueued) { + sddf_notify(RX_CH); + } + + return 0; +} + +fun handle_irq() +{ + var uart_sr = uart_read_sr(); + var uart_cr = uart_read_cr(); + while ((uart_sr & UART_INTR_ABNORMAL) || (!(uart_sr & AML_UART_RX_EMPTY)) || + ((uart_cr & AML_UART_TX_INT_EN) && (!(uart_sr & AML_UART_TX_FULL)))) { + if (RX_ENABLED && !(uart_sr & AML_UART_RX_EMPTY)) { + rx_return(); + } + + if ((uart_cr & AML_UART_TX_INT_EN) && (!(uart_sr & AML_UART_TX_FULL))) { + tx_provide(); + } + + if (uart_sr & UART_INTR_ABNORMAL) { + debug_print(uart_sr); + uart_cr = uart_read_cr(); + uart_write_cr(uart_cr | AML_UART_CLEAR_ERR); + } + + uart_sr = uart_read_sr(); + uart_cr = uart_read_cr(); + } + + return 0; +} + +export fun notified(1 ch) +{ + if (ch == IRQ_CH) { + handle_irq(); + sddf_deferred_irq_ack(ch); + } else { + if (ch == TX_CH) { + tx_provide(); + } else { + if (ch == RX_CH) { + var cr = uart_read_cr(); + cr = cr | AML_UART_RX_INT_EN; + uart_write_cr(cr); + + rx_return(); + } else { + debug_print(ch); + } + } + } + + return 0; +} diff --git a/drivers/serial/ns16550a/serial_driver.mk b/drivers/serial/ns16550a/serial_driver.mk index 41f76b626..542ad8ac4 100644 --- a/drivers/serial/ns16550a/serial_driver.mk +++ b/drivers/serial/ns16550a/serial_driver.mk @@ -8,11 +8,30 @@ SERIAL_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +ifeq ($(PANCAKE_SERIAL_DRIVER),1) +DRIVER_PNK = \ + ${UTIL}/util.pnk \ + ${SDDF}/include/sddf/serial/queue.pnk \ + ${SERIAL_DRIVER_DIR}/uart.pnk + +serial_driver.elf: serial/ns16550a/serial_driver_pnk.o serial/ns16550a/serial_driver.o util/pancake_ffi.o libsddf_util_debug.a + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +serial/ns16550a/serial_driver_pnk.o: serial/ns16550a/serial_driver_pnk.S |serial/ns16550a + $(CC) -c $(CFLAGS) -o $@ $< + +serial/ns16550a/serial_driver_pnk.S: $(DRIVER_PNK) |serial/ns16550a + cat $(DRIVER_PNK) | cpp -P | $(PANCAKE_COMPILER) $(PANCAKE_FLAGS) > $@ + +serial/ns16550a/serial_driver.o: ${SERIAL_DRIVER_DIR}/uart.c |serial/ns16550a $(SDDF_LIBC_INCLUDE) + $(CC) -c $(CFLAGS) -DPANCAKE_SERIAL_DRIVER -I${SERIAL_DRIVER_DIR}/include -o $@ $< +else serial_driver.elf: serial/ns16550a/serial_driver.o $(LD) $(LDFLAGS) $< $(LIBS) -o $@ serial/ns16550a/serial_driver.o: ${SERIAL_DRIVER_DIR}/uart.c |serial/ns16550a $(SDDF_LIBC_INCLUDE) $(CC) -c $(CFLAGS) -I${SERIAL_DRIVER_DIR}/include -o $@ $< +endif serial/ns16550a: mkdir -p $@ @@ -20,6 +39,6 @@ serial/ns16550a: -include serial/ns16550a/serial_driver.d clean:: - rm -f serial/ns16550a/serial_driver.[do] + rm -f serial/ns16550a/serial_driver.[do] serial/ns16550a/serial_driver_pnk.[oS] clobber:: clean rm -rf serial_driver.elf serial diff --git a/drivers/serial/ns16550a/uart.c b/drivers/serial/ns16550a/uart.c index e00fec3e4..f36401f70 100644 --- a/drivers/serial/ns16550a/uart.c +++ b/drivers/serial/ns16550a/uart.c @@ -17,6 +17,43 @@ __attribute__((__section__(".serial_driver_config"))) serial_driver_config_t con __attribute__((__section__(".device_resources"))) device_resources_t device_resources; +#ifdef PANCAKE_SERIAL_DRIVER +static char cml_memory[1024 * 20]; +extern void *cml_heap; +extern void *cml_stack; +extern void *cml_stackend; + +extern void cml_main(void); + +void cml_exit(int arg) +{ + microkit_dbg_puts("ERROR! We should not be getting here\n"); +} + +void cml_err(int arg) +{ + if (arg == 3) { + microkit_dbg_puts("Memory not ready for entry. You may have not run the init code yet, or be trying to enter " + "during an FFI call.\n"); + } + cml_exit(arg); +} + +void cml_clear() +{ + microkit_dbg_puts("Trying to clear cache\n"); +} + +void init_pancake_mem() +{ + unsigned long cml_heap_sz = 1024 * 10; + unsigned long cml_stack_sz = 1024 * 10; + cml_heap = cml_memory; + cml_stack = cml_heap + cml_heap_sz; + cml_stackend = cml_stack + cml_stack_sz; +} +#endif /* PANCAKE_SERIAL_DRIVER */ + serial_queue_handle_t rx_queue_handle; serial_queue_handle_t tx_queue_handle; @@ -37,6 +74,7 @@ volatile uintptr_t uart_base; #error "unknown platform reg-io-width" #endif +#ifndef PANCAKE_SERIAL_DRIVER static inline bool tx_fifo_not_full(void) { #if UART_DW_APB_REGISTERS && !defined(CONFIG_PLAT_HIFIVE_P550) @@ -87,6 +125,7 @@ static inline bool rx_has_data(void) { return !!(*REG_PTR(UART_LSR) & UART_LSR_DR); } +#endif /* PANCAKE_SERIAL_DRIVER */ static void set_baud(unsigned long baud) { @@ -116,6 +155,7 @@ static void set_baud(unsigned long baud) *REG_PTR(UART_LCR) = lcr_val; } +#ifndef PANCAKE_SERIAL_DRIVER static void tx_provide(void) { bool transferred = false; @@ -190,6 +230,7 @@ static void handle_irq(void) tx_provide(); } } +#endif void init(void) { @@ -241,8 +282,35 @@ void init(void) */ (void)*REG_PTR(UART_USR); #endif + +#ifdef PANCAKE_SERIAL_DRIVER + init_pancake_mem(); + + uintptr_t *pnk_mem = (uintptr_t *)cml_heap; + + pnk_mem[0] = (uintptr_t)uart_base; + pnk_mem[1] = device_resources.irqs[0].id; + pnk_mem[2] = config.rx.id; + pnk_mem[3] = config.tx.id; + pnk_mem[4] = (uintptr_t)&rx_queue_handle; + pnk_mem[5] = (uintptr_t)&tx_queue_handle; + pnk_mem[1024] = config.rx_enabled; + pnk_mem[1025] = REG_IO_WIDTH; + pnk_mem[1026] = REG_SHIFT; + pnk_mem[1027] = UART_DW_APB_REGISTERS; +#if defined(CONFIG_PLAT_HIFIVE_P550) + pnk_mem[1028] = 1; +#else + pnk_mem[1028] = 0; +#endif /* CONFIG_PLAT_HIFIVE_P550 */ + + cml_main(); +#endif /* PANCAKE_SERIAL_DRIVER */ } +#ifdef PANCAKE_SERIAL_DRIVER +extern void notified(sddf_channel ch); +#else void notified(sddf_channel ch) { if (ch == device_resources.irqs[0].id) { @@ -256,3 +324,4 @@ void notified(sddf_channel ch) LOG_DRIVER_ERR("received notification on unexpected channel\n"); } } +#endif /* PANCAKE_SERIAL_DRIVER */ diff --git a/drivers/serial/ns16550a/uart.pnk b/drivers/serial/ns16550a/uart.pnk new file mode 100644 index 000000000..7ba46a7f1 --- /dev/null +++ b/drivers/serial/ns16550a/uart.pnk @@ -0,0 +1,375 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define UART_REG_BASE pnk_mem(0) + +#define IRQ_CH pnk_mem(1) +#define RX_CH pnk_mem(2) +#define TX_CH pnk_mem(3) + +#define SERIAL_RX_QUEUE_HANDLE pnk_mem(4) +#define SERIAL_TX_QUEUE_HANDLE pnk_mem(5) + +#define RX_ENABLED pnk_mem(1024) +#define REG_IO_WIDTH pnk_mem(1025) +#define REG_SHIFT pnk_mem(1026) +#define DW_APB_REGISTERS pnk_mem(1027) +#define PLAT_HIFIVE_P550 pnk_mem(1028) + +/* These register offsets are given as their offset in terms of reg-io-width; + * e.g. for reg-io-width of 4 (32 bit accesses) the true offset for 0x1 is 0x4. + * These drivers are also weird, as multiple registers are mapped to the same + * memory and their access depends on the current state of the UART. + */ + +/* UART Receive Buffer Register (R) */ +#define UART_RBR 0 + +/* UART Transmit Holding Register (W) */ +#define UART_THR 0 + +/* UART Divisor Low Latch Register (R/W) */ +#define UART_DLL 0 + +/* UART Divisor High Latch Register (R/W) */ +#define UART_DLH 1 + +/* UART Interrupt Enable Register (R/W) */ +#define UART_IER 1 +/* UART Interrupt Enable Register (R/W) */ +/* Enable Received Data Available Interrupt, if FIFOs are enabled, the + * Character Timeout Interrupt. */ +#define UART_IER_ERBFI BIT(0) +/* Enable Transmit Holding Register Empty Interrupt */ +#define UART_IER_ETBEI BIT(1) + +/* UART Interrupt Identity Register (R) */ +#define UART_IIR 2 +/* Interrupt ID Mask. + * According to the DW_APB_UART manual, this is bits 3:0, i.e. should be 0b1111 + * The UART_IER_ERBFI (received data available interrupt) also enables the + * character timeout interrupt, i.e. 0b1100 for the IID. + * Furthermore, the manual specifies that the priorities put the received data + * interrupt *above* the character timeout interrupt. + * + * However, QEMU seems to disagree, and places the timeout interrupt above the + * received data interrupt. + * + * It appears that Zephyr just ignores it, which seems suspicious. But so does + * Linux. + * + * ?? + */ +#define UART_IIR_IID_MASK 7 +/* Interrupt ID = Transmit Holding Register Indicator */ +#define UART_IIR_IID_THRI 2 +/* Interrupt ID = Received Data Available */ +#define UART_IIR_IID_RDI 4 + +/* UART FIFO Control Register (W) */ +#define UART_FCR 2 +/* FIFO Enable */ +#define UART_FCR_FIFOE BIT(0) +/* Receive FIFO Reset */ +#define UART_FCR_RFIFOR BIT(1) +/* Transmit FIFO Reset */ +#define UART_FCR_XFIFOR BIT(2) + +/* UART Line Control Register (RW) */ +#define UART_LCR 3 +/* Divisor Latch Access Bit */ +#define UART_LCR_DLAB BIT(7) + +/* UART Modem Control Register (RW) */ +#define UART_MCR 4 +/* Data Terminal Ready */ +#define UART_MCR_DTR BIT(0) +/* Request to Send */ +#define UART_MCR_RTS BIT(1) + +/* UART Line Status Register (R) */ +#define UART_LSR 5 +/* Data Ready */ +#define UART_LSR_DR BIT(0) +/* Parity Error Bit */ +#define UART_LSR_PE BIT(2) +/* Framing Error Bit */ +#define UART_LSR_FE BIT(3) +/* Transmit Holding Register Empty */ +#define UART_LSR_THRE BIT(5) +/* Transmit FIFO and Transmit Shift register Empty */ +#define UART_LSR_TEMT BIT(6) +/* Recv FIFO Error Bit */ +#define UART_LSR_RFE BIT(7) + +/* + * These registers are special to the DW APB UART implementation. + */ + +/* UART Status Register (0x7C >> 2 = 0x1f) */ +#define UART_USR 31 +/* UART BUSY */ +#define UART_USR_BUSY BIT(0) +/* Transmit FIFO Not Full */ +#define UART_USR_TFNF BIT(1) + +inline fun uart_read_reg(1 offset) +{ + var reg_shift = REG_SHIFT; + var io_width = REG_IO_WIDTH; + + var shifted_offset = offset; + if (reg_shift == 2) { + shifted_offset = offset * 4; + } + + var reg_addr = UART_REG_BASE + shifted_offset; + var val = 0; + + if (io_width == 1) { + !ld8 val, reg_addr; + return val; + } + + if (io_width == 4) { + !ld32 val, reg_addr; + return val; + } + + return 0; +} + +inline fun uart_write_reg(1 offset, 1 val) +{ + var reg_shift = REG_SHIFT; + var io_width = REG_IO_WIDTH; + + var shifted_offset = offset; + if (reg_shift == 2) { + shifted_offset = offset * 4; + } + + var reg_addr = UART_REG_BASE + shifted_offset; + + if (io_width == 1) { + !st8 reg_addr, val; + return 0; + } + + if (io_width == 4) { + !st32 reg_addr, val; + return 0; + } + + return 0; +} + +inline fun tx_fifo_not_full() +{ + var dw_apb_regs = DW_APB_REGISTERS; + var plat_hifive_p550 = PLAT_HIFIVE_P550; + if (dw_apb_regs && !plat_hifive_p550) { + /** + * On DesignWare APB-derived 16550a-like IPs, they provide a TFNF bit in + * the UART Status Register (USR). + */ + var usr = uart_read_reg(UART_USR); + return !(!(usr & UART_USR_TFNF)); + } else { + /** + * On a standard NS16550a UART IP, we don't have a "transmit FIFO (not) full" + * indicator bit. Instead we have a FIFO empty / holding register empty bit. + * This means that despite *notionally* having a TX FIFO, it is never filled + * more than 1 bit at a time. This makes the TX rather slow. If we only use + * this on our Star64, we see interleaved serial output in debug mode [1]. + * + * Ref [2] also describes this similar issue, and demonstrates the "intended" + * use is to write a FIFO-size (i.e. 16 characters) at once to the UART THR. + * This is also what Zephyr [3] and Linux [4] do. + * + * However, as we run on top of seL4, we may be preempted at any time. + * Specifically, another process may use a debug print through OpenSBI's + * driver [5] which will write to the UART THR. Because a TX overrun on the + * NS16550a will corrupt what had previously been written (as opposed to + * not doing anything), we can't detect whether or not we would corrupt + * the serial output. + * + * It would be possible, if OpenSBI's driver handled "polling uart in an IRQ + * context" like Linux does [6], where it wants for the THR to empty both + * before *and* after emitting a character. However, this is not the case. + * + * Hence, on standard ns16550a we are likely to see interleaved serial output + * because of how slow waiting for an interrupt for *every* output character + * is. + * + * [1]: https://github.com/au-ts/sddf/issues/411#issuecomment-2864845777 + * [2]: https://www.ele.uva.es/~jesus/UltimatePutchar.pdf + * [3]: https://github.com/zephyrproject-rtos/zephyr/blob/zephyr-v3.5.0/drivers/serial/uart_ns16550.c#L773-L798 + * [4]: https://github.com/torvalds/linux/blob/v6.14/drivers/tty/serial/8250/8250_port.c#L1794-L1855 + * [5]: https://github.com/riscv-software-src/opensbi/blob/v1.6/lib/utils/serial/uart8250.c#L74-L80 + * [6]: https://github.com/torvalds/linux/commit/f2d937f3bf00665ccf048b3b6616ef95859b0945 + */ + var lsr = uart_read_reg(UART_LSR); + return !(!(lsr & UART_LSR_THRE)); + } +} + +inline fun rx_has_data() +{ + var lsr = uart_read_reg(UART_LSR); + return !(!(lsr & UART_LSR_DR)); +} + +fun tx_provide() +{ + var tx_queue_handle = SERIAL_TX_QUEUE_HANDLE; + + var transferred = false; + + while (true) { + var fifo_not_full = tx_fifo_not_full(); + if (!fifo_not_full) { + break; + } + + var {1, 1} res = serial_dequeue(tx_queue_handle); + if (res.0) { + break; + } + uart_write_reg(UART_THR, res.1 & MAX_UINT8); + transferred = true; + } + + var head = serial_queue_get_head(tx_queue_handle); + var is_empty = serial_queue_empty(tx_queue_handle, head); + /* If we still have data to be sent after filling the FIFO, enable TX empty IRQ */ + if (!is_empty) { + var ier = uart_read_reg(UART_IER); + ier = ier | UART_IER_ETBEI; + uart_write_reg(UART_IER, ier); + } else { + var ier = uart_read_reg(UART_IER); + ier = ier & bitwise_not(UART_IER_ETBEI); + uart_write_reg(UART_IER, ier); + } + + if (transferred) { + var require_signal = serial_require_consumer_signal(tx_queue_handle); + if (require_signal) { + serial_cancel_consumer_signal(tx_queue_handle); + sddf_notify(TX_CH); + } + } + + return 0; +} + +fun rx_return() +{ + var rx_queue_handle = SERIAL_RX_QUEUE_HANDLE; + + var reprocess = true; + var enqueued = false; + while (reprocess) { + while (true) { + var has_data = rx_has_data(); + if (!has_data) { + break; + } + + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if (is_full) { + break; + } + + var c = uart_read_reg(UART_RBR); + var err = serial_enqueue(rx_queue_handle, c & MAX_UINT8); + // assert(!err); + enqueued = true; + } + + /* If we have more RX device data available, but no space in the queue with the virtualiser, + * we disable RX IRQs until space becomes available. */ + var has_data = rx_has_data(); + if (has_data) { + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if (is_full) { + var ier = uart_read_reg(UART_IER); + ier = ier & bitwise_not(UART_IER_ERBFI); + uart_write_reg(UART_IER, ier); + serial_request_consumer_signal(rx_queue_handle); + } + } + + reprocess = false; + + /* While RX data is still available, we enable the RX IRQ and continue processing */ + has_data = rx_has_data(); + if (has_data) { + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if (!is_full) { + serial_cancel_consumer_signal(rx_queue_handle); + var ier = uart_read_reg(UART_IER); + ier = ier | UART_IER_ERBFI; + uart_write_reg(UART_IER, ier); + reprocess = true; + } + } + } + + if (enqueued) { + sddf_notify(RX_CH); + } + + return 0; +} + +fun handle_irq() +{ + /* Reading this register auto-clears the error bits. + So we have to do this before rx_return() / tx_provide() which needs to + read LSR for THRE/DR. */ + var line_status = uart_read_reg(UART_LSR); + if (line_status & (UART_LSR_PE | UART_LSR_FE | UART_LSR_RFE)) { + debug_print(line_status); + } + + /* IRQ ID is a priority-based *single* indication, not a bitvector */ + var irq_id = uart_read_reg(UART_IIR); + irq_id = irq_id & UART_IIR_IID_MASK; + var rx_enabled = RX_ENABLED; + if (rx_enabled && (irq_id == UART_IIR_IID_RDI)) { + rx_return(); + } else { + if (irq_id == UART_IIR_IID_THRI) { + tx_provide(); + } + } + + return 0; +} + +export fun notified(1 ch) +{ + if (ch == IRQ_CH) { + handle_irq(); + sddf_deferred_irq_ack(ch); + } else { + if (ch == TX_CH) { + tx_provide(); + } else { + if (ch == RX_CH) { + rx_return(); + } else { + debug_print(ch); + } + } + } + + return 0; +} diff --git a/drivers/serial/pc99/serial_driver.mk b/drivers/serial/pc99/serial_driver.mk index f57daf6cc..b8125e81d 100644 --- a/drivers/serial/pc99/serial_driver.mk +++ b/drivers/serial/pc99/serial_driver.mk @@ -11,11 +11,30 @@ SERIAL_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +ifeq ($(PANCAKE_SERIAL_DRIVER),1) +DRIVER_PNK = \ + ${UTIL}/util.pnk \ + ${SDDF}/include/sddf/serial/queue.pnk \ + ${SERIAL_DRIVER_DIR}/uart.pnk + +serial_driver.elf: serial/pc99/serial_driver_pnk.o serial/pc99/serial_driver.o util/pancake_ffi.o libsddf_util_debug.a + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +serial/pc99/serial_driver_pnk.o: serial/pc99/serial_driver_pnk.S |serial/pc99 + $(CC) -c $(CFLAGS) -o $@ $< + +serial/pc99/serial_driver_pnk.S: $(DRIVER_PNK) |serial/pc99 + cat $(DRIVER_PNK) | cpp -P | $(PANCAKE_COMPILER) $(PANCAKE_FLAGS) > $@ + +serial/pc99/serial_driver.o: ${SERIAL_DRIVER_DIR}/uart.c |serial/pc99 $(SDDF_LIBC_INCLUDE) + $(CC) -c $(CFLAGS) -DPANCAKE_SERIAL_DRIVER -I${SERIAL_DRIVER_DIR}/include -o $@ $< +else serial_driver.elf: serial/pc99/serial_driver.o libsddf_util_debug.a $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ serial/pc99/serial_driver.o: ${SERIAL_DRIVER_DIR}/uart.c |serial/pc99 $(SDDF_LIBC_INCLUDE) $(CC) -c $(CFLAGS) -I${SERIAL_DRIVER_DIR}/include -o $@ $< +endif serial/pc99: mkdir -p $@ @@ -23,7 +42,7 @@ serial/pc99: -include serial/pc99/serial_driver.d clean:: - rm -f serial/pc99/serial_driver.[do] + rm -f serial/pc99/serial_driver.[do] serial/pc99/serial_driver_pnk.[oS] clobber:: rm -rf serial \ No newline at end of file diff --git a/drivers/serial/pc99/uart.c b/drivers/serial/pc99/uart.c index 4687bbd74..d911e9400 100644 --- a/drivers/serial/pc99/uart.c +++ b/drivers/serial/pc99/uart.c @@ -19,6 +19,43 @@ __attribute__((__section__(".device_resources"), retain, used)) device_resources_t device_resources; __attribute__((__section__(".serial_driver_config"))) serial_driver_config_t config; +#ifdef PANCAKE_SERIAL_DRIVER +static char cml_memory[1024 * 20]; +extern void *cml_heap; +extern void *cml_stack; +extern void *cml_stackend; + +extern void cml_main(void); + +void cml_exit(int arg) +{ + microkit_dbg_puts("ERROR! We should not be getting here\n"); +} + +void cml_err(int arg) +{ + if (arg == 3) { + microkit_dbg_puts("Memory not ready for entry. You may have not run the init code yet, or be trying to enter " + "during an FFI call.\n"); + } + cml_exit(arg); +} + +void cml_clear() +{ + microkit_dbg_puts("Trying to clear cache\n"); +} + +void init_pancake_mem() +{ + unsigned long cml_heap_sz = 1024 * 10; + unsigned long cml_stack_sz = 1024 * 10; + cml_heap = cml_memory; + cml_stack = cml_heap + cml_heap_sz; + cml_stackend = cml_stack + cml_stack_sz; +} +#endif /* PANCAKE_SERIAL_DRIVER */ + // @billn Need a way to express io port in sdfgen config structure #define IOPORT_ID 0 #define IOPORT_BASE 0x3f8 @@ -64,6 +101,7 @@ uint8_t read(uint16_t port_offset) return microkit_x86_ioport_read_8((IOPORT_ID), IOPORT_BASE + port_offset); } +#ifndef PANCAKE_SERIAL_DRIVER int tx_ready(void) { return read(SERIAL_LSR) & SERIAL_LSR_TRANSMITTER_EMPTY; @@ -73,6 +111,7 @@ int rx_ready(void) { return read(SERIAL_LSR) & SERIAL_LSR_DATA_READY; } +#endif /* PANCAKE_SERIAL_DRIVER */ void init(void) { @@ -102,8 +141,24 @@ void init(void) read(SERIAL_RBR); /* clear receiver port */ read(SERIAL_LSR); /* clear line status port */ read(SERIAL_MSR); /* clear modem status port */ + +#ifdef PANCAKE_SERIAL_DRIVER + init_pancake_mem(); + + uintptr_t *pnk_mem = (uintptr_t *)cml_heap; + + pnk_mem[2] = config.rx.id; + pnk_mem[3] = config.tx.id; + pnk_mem[4] = (uintptr_t)&rx_queue_handle; + pnk_mem[5] = (uintptr_t)&tx_queue_handle; + + cml_main(); +#endif /* PANCAKE_SERIAL_DRIVER */ } +#ifdef PANCAKE_SERIAL_DRIVER +extern void notified(sddf_channel ch); +#else static void tx_provide(void) { bool transferred = false; @@ -156,3 +211,4 @@ void notified(microkit_channel ch) sddf_dprintf("UART|LOG: received notification on unexpected channel: %u\n", ch); } } +#endif /* PANCAKE_SERIAL_DRIVER */ diff --git a/drivers/serial/pc99/uart.pnk b/drivers/serial/pc99/uart.pnk new file mode 100644 index 000000000..16cf95457 --- /dev/null +++ b/drivers/serial/pc99/uart.pnk @@ -0,0 +1,166 @@ +/* + * Copyright 2026, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define RX_CH pnk_mem(2) +#define TX_CH pnk_mem(3) + +#define SERIAL_RX_QUEUE_HANDLE pnk_mem(4) +#define SERIAL_TX_QUEUE_HANDLE pnk_mem(5) +#define IOPORT_DATA pnk_mem(6) +#define IOPORT_DATA_PTR pnk_mem_ptr(6) + +// @billn Need a way to express io port in sdfgen config structure +#define IOPORT_ID 0 +#define IOPORT_BASE 1016 +// @billn need some sort of "machine description" format for x86 sdfgen to automatically pull in IRQ +#define IRQ_ID 1 + +/* + * Port offsets + * W - write + * R - read + * RW - read and write + * DLAB - Alternate register function bit + */ + +#define SERIAL_THR 0 /* Transmitter Holding Buffer (W ) DLAB = 0 */ +#define SERIAL_RBR 0 /* Receiver Buffer (R ) DLAB = 0 */ +#define SERIAL_DLL 0 /* Divisor Latch Low Byte (RW) DLAB = 1 */ +#define SERIAL_IER 1 /* Interrupt Enable Register (RW) DLAB = 0 */ +#define SERIAL_DLH 1 /* Divisor Latch High Byte (RW) DLAB = 1 */ +#define SERIAL_IIR 2 /* Interrupt Identification (R ) */ +#define SERIAL_FCR 2 /* FIFO Control Register (W ) */ +#define SERIAL_LCR 3 /* Line Control Register (RW) */ +#define SERIAL_MCR 4 /* Modem Control Register (RW) */ +#define SERIAL_LSR 5 /* Line Status Register (R ) */ +#define SERIAL_MSR 6 /* Modem Status Register (R ) */ +#define SERIAL_SR 7 /* Scratch Register (RW) */ +#define SERIAL_DLAB BIT(7) +#define SERIAL_LSR_DATA_READY BIT(0) +#define SERIAL_LSR_TRANSMITTER_EMPTY BIT(5) + +#define MODEM_STATUS 0 +#define TX_HOLD_REG_EMPTY 1 +#define RX_DATA_AVAIL 2 +#define RX_LINE_STS 3 + +inline fun write(1 port_offset, 1 v) +{ + microkit_x86_ioport_write_8(IOPORT_ID, IOPORT_BASE + port_offset, v); + return 0; +} + +inline fun read(1 port_offset) +{ + var data_ptr = IOPORT_DATA_PTR; + microkit_x86_ioport_read_8(data_ptr, IOPORT_ID, IOPORT_BASE + port_offset); + var ret = 0; + !ld8 ret, data_ptr; + return ret; +} + +inline fun tx_ready() +{ + var ret = read(SERIAL_LSR); + ret = ret & SERIAL_LSR_TRANSMITTER_EMPTY; + return ret; +} + +inline fun rx_ready() +{ + var ret = read(SERIAL_LSR); + ret = ret & SERIAL_LSR_DATA_READY; + return ret; +} + +fun tx_provide() +{ + var tx_queue_handle = SERIAL_TX_QUEUE_HANDLE; + + var transferred = false; + while (true) { + var {1, 1} res = serial_dequeue(tx_queue_handle); + if (res.0) { + break; + } + while (true) { + var ready = tx_ready(); + if (ready) { + break; + } + } + write(SERIAL_THR, res.1 & MAX_UINT8); + transferred = true; + } + + if (transferred) { + var require_signal = serial_require_consumer_signal(tx_queue_handle); + if (require_signal) { + serial_cancel_consumer_signal(tx_queue_handle); + sddf_notify(TX_CH); + } + } + + return 0; +} + +fun rx_return() +{ + var rx_queue_handle = SERIAL_RX_QUEUE_HANDLE; + + var enqueued = false; + while (true) { + var ready = rx_ready(); + if (!ready) { + break; + } + + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if (is_full) { + break; + } + + var c = read(SERIAL_RBR); + serial_enqueue(rx_queue_handle, c & MAX_UINT8); + enqueued = true; + } + + if (enqueued) { + sddf_notify(RX_CH); + } + + return 0; +} + +fun handle_irq() { + var iir = read(SERIAL_IIR); + iir = iir >> 1; + if (iir & RX_DATA_AVAIL) { + rx_return(); + } + + return 0; +} + +export fun notified(1 ch) +{ + if (ch == TX_CH) { + tx_provide(); + } else { + if (ch == RX_CH) { + rx_return(); + } else { + if (ch == IRQ_ID) { + handle_irq(); + sddf_deferred_irq_ack(IRQ_ID); + } else { + debug_print(ch); + } + } + } + + return 0; +} diff --git a/drivers/serial/zynqmp/serial_driver.mk b/drivers/serial/zynqmp/serial_driver.mk index 78eb9d05e..5cc12dca7 100644 --- a/drivers/serial/zynqmp/serial_driver.mk +++ b/drivers/serial/zynqmp/serial_driver.mk @@ -9,11 +9,30 @@ SERIAL_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) +ifeq ($(PANCAKE_SERIAL_DRIVER),1) +DRIVER_PNK = \ + ${UTIL}/util.pnk \ + ${SDDF}/include/sddf/serial/queue.pnk \ + ${SERIAL_DRIVER_DIR}/uart.pnk + +serial_driver.elf: serial/zynqmp/serial_driver_pnk.o serial/zynqmp/serial_driver.o util/pancake_ffi.o libsddf_util_debug.a + $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ + +serial/zynqmp/serial_driver_pnk.o: serial/zynqmp/serial_driver_pnk.S |serial/zynqmp + $(CC) -c $(CFLAGS) -o $@ $< + +serial/zynqmp/serial_driver_pnk.S: $(DRIVER_PNK) |serial/zynqmp + cat $(DRIVER_PNK) | cpp -P | $(PANCAKE_COMPILER) $(PANCAKE_FLAGS) > $@ + +serial/zynqmp/serial_driver.o: ${SERIAL_DRIVER_DIR}/uart.c |serial/zynqmp $(SDDF_LIBC_INCLUDE) + $(CC) -c $(CFLAGS) -DPANCAKE_SERIAL_DRIVER -I${SERIAL_DRIVER_DIR}/include -o $@ $< +else serial_driver.elf: serial/zynqmp/serial_driver.o $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ serial/zynqmp/serial_driver.o: ${SERIAL_DRIVER_DIR}/uart.c |serial/zynqmp $(SDDF_LIBC_INCLUDE) $(CC) -c $(CFLAGS) -I${SERIAL_DRIVER_DIR}/include -o $@ $< +endif -include serial_driver.d @@ -21,7 +40,7 @@ serial/zynqmp: mkdir -p $@ clean:: - rm -f serial/zynqmp/serial_driver.[do] + rm -f serial/zynqmp/serial_driver.[do] serial/zynqmp/serial_driver_pnk.[oS] clobber:: rm -rf serial diff --git a/drivers/serial/zynqmp/uart.c b/drivers/serial/zynqmp/uart.c index 0f24a3374..74015d4bb 100644 --- a/drivers/serial/zynqmp/uart.c +++ b/drivers/serial/zynqmp/uart.c @@ -25,6 +25,43 @@ __attribute__((__section__(".serial_driver_config"))) serial_driver_config_t config; __attribute__((__section__(".device_resources"))) device_resources_t device_resources; +#ifdef PANCAKE_SERIAL_DRIVER +static char cml_memory[1024 * 20]; +extern void *cml_heap; +extern void *cml_stack; +extern void *cml_stackend; + +extern void cml_main(void); + +void cml_exit(int arg) +{ + microkit_dbg_puts("ERROR! We should not be getting here\n"); +} + +void cml_err(int arg) +{ + if (arg == 3) { + microkit_dbg_puts("Memory not ready for entry. You may have not run the init code yet, or be trying to enter " + "during an FFI call.\n"); + } + cml_exit(arg); +} + +void cml_clear() +{ + microkit_dbg_puts("Trying to clear cache\n"); +} + +void init_pancake_mem() +{ + unsigned long cml_heap_sz = 1024 * 10; + unsigned long cml_stack_sz = 1024 * 10; + cml_heap = cml_memory; + cml_stack = cml_heap + cml_heap_sz; + cml_stackend = cml_stack + cml_stack_sz; +} +#endif /* PANCAKE_SERIAL_DRIVER */ + bool waiting_for_tx_to_finish = false; serial_queue_handle_t rx_queue_handle; @@ -35,6 +72,7 @@ volatile uintptr_t uart_base; #define REG_PTR(off) ((volatile uint32_t *)(uart_base + off)) +#ifndef PANCAKE_SERIAL_DRIVER static void tx_provide(void) { if (waiting_for_tx_to_finish) { @@ -130,6 +168,7 @@ static void handle_irq(void) rx_return(); } } +#endif /* PANCAKE_SERIAL_DRIVER */ static void compute_clk_divs(uint64_t clock_hz, uint64_t baudrate, uint16_t *cd, uint8_t *bdiv) { @@ -272,8 +311,27 @@ void init(void) serial_queue_init(&rx_queue_handle, config.rx.queue.vaddr, config.rx.data.size, config.rx.data.vaddr); } serial_queue_init(&tx_queue_handle, config.tx.queue.vaddr, config.tx.data.size, config.tx.data.vaddr); + +#ifdef PANCAKE_SERIAL_DRIVER + init_pancake_mem(); + + uintptr_t *pnk_mem = (uintptr_t *)cml_heap; + + pnk_mem[0] = (uintptr_t)uart_base; + pnk_mem[1] = device_resources.irqs[0].id; + pnk_mem[2] = config.rx.id; + pnk_mem[3] = config.tx.id; + pnk_mem[4] = (uintptr_t)&rx_queue_handle; + pnk_mem[5] = (uintptr_t)&tx_queue_handle; + pnk_mem[1024] = config.rx_enabled; + + cml_main(); +#endif /* PANCAKE_SERIAL_DRIVER */ } +#ifdef PANCAKE_SERIAL_DRIVER +extern void notified(sddf_channel ch); +#else void notified(sddf_channel ch) { if (ch == device_resources.irqs[0].id) { @@ -287,3 +345,4 @@ void notified(sddf_channel ch) sddf_dprintf("UART|LOG: received notification on unexpected channel: %u\n", ch); } } +#endif /* PANCAKE_SERIAL_DRIVER */ diff --git a/drivers/serial/zynqmp/uart.pnk b/drivers/serial/zynqmp/uart.pnk new file mode 100644 index 000000000..3b857fe85 --- /dev/null +++ b/drivers/serial/zynqmp/uart.pnk @@ -0,0 +1,265 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define UART_REG_BASE pnk_mem(0) + +#define IRQ_CH pnk_mem(1) +#define RX_CH pnk_mem(2) +#define TX_CH pnk_mem(3) + +#define SERIAL_RX_QUEUE_HANDLE pnk_mem(4) +#define SERIAL_TX_QUEUE_HANDLE pnk_mem(5) + +#define RX_ENABLED pnk_mem(1024) +#define RX_ENABLED_PTR pnk_mem_ptr(1024) +#define WAITING_FOR_TX pnk_mem(1025) +#define WAITING_FOR_TX_PTR pnk_mem_ptr(1025) + +/* Register offsets for the UART. */ +#define ZYNQMP_UART_CR 0 /* Control Register */ +#define ZYNQMP_UART_MR 4 /* Mode Register */ +#define ZYNQMP_UART_IER 8 /* Interrupt Enable */ +#define ZYNQMP_UART_IDR 12 /* Interrupt Disable */ +#define ZYNQMP_UART_IMR 16 /* Interrupt Mask */ +#define ZYNQMP_UART_ISR 20 /* Interrupt Status */ +#define ZYNQMP_UART_BAUDGEN 24 /* Baud Rate Generator */ +#define ZYNQMP_UART_RXTOUT 28 /* RX Timeout */ +#define ZYNQMP_UART_RXWM 32 /* RX FIFO Trigger Level */ +#define ZYNQMP_UART_MODEMCR 36 /* Modem Control */ +#define ZYNQMP_UART_MODEMSR 40 /* Modem Status */ +#define ZYNQMP_UART_SR 44 /* Channel Status */ +#define ZYNQMP_UART_FIFO 48 /* FIFO */ +#define ZYNQMP_UART_BAUDDIV 52 /* Baud Rate Divider */ + +#define ZYNQMP_UART_CHANNEL_STS_RXEMPTY BIT(1) +#define ZYNQMP_UART_CHANNEL_STS_RXFULL BIT(2) +#define ZYNQMP_UART_CHANNEL_STS_TXEMPTY BIT(3) +#define ZYNQMP_UART_CHANNEL_STS_TXFULL BIT(4) +#define ZYNQMP_UART_CHANNEL_STS_TXACTIVE BIT(11) +#define ZYNQMP_UART_CHANNEL_STS_TXNFULL BIT(14) + +#define ZYNQMP_UART_CR_TX_DIS_SHIFT 5 +#define ZYNQMP_UART_CR_RX_DIS_SHIFT 3 +#define ZYNQMP_UART_CR_TX_EN_SHIFT 4 +#define ZYNQMP_UART_CR_RX_EN_SHIFT 2 +#define ZYNQMP_UART_CR_TX_RST_SHIFT 1 +#define ZYNQMP_UART_CR_RX_RST_SHIFT 0 +#define ZYNQMP_UART_CR_TX_EN BIT(ZYNQMP_UART_CR_TX_EN_SHIFT) +#define ZYNQMP_UART_CR_TX_DIS BIT(ZYNQMP_UART_CR_TX_DIS_SHIFT) +#define ZYNQMP_UART_CR_TX_RST BIT(ZYNQMP_UART_CR_TX_RST_SHIFT) +#define ZYNQMP_UART_CR_RX_EN BIT(ZYNQMP_UART_CR_RX_EN_SHIFT) +#define ZYNQMP_UART_CR_RX_DIS BIT(ZYNQMP_UART_CR_RX_DIS_SHIFT) +#define ZYNQMP_UART_CR_RX_RST BIT(ZYNQMP_UART_CR_RX_RST_SHIFT) + +#define ZYNQMP_UART_IXR_TXEMPTY 8 /**< TX FIFO empty interrupt. */ +#define ZYNQMP_UART_IXR_RXOVR 1 /**< RX FIFO trigger interrupt. */ +#define ZYNQMP_UART_IXR_MASK 8191 /**< Valid bit mask */ + +inline fun uart_read_sr() +{ + var reg_addr = UART_REG_BASE + ZYNQMP_UART_SR; + var sr = 0; + !ld32 sr, reg_addr; + return sr; +} + +inline fun uart_read_isr() +{ + var reg_addr = UART_REG_BASE + ZYNQMP_UART_ISR; + var isr = 0; + !ld32 isr, reg_addr; + return isr; +} + +inline fun uart_write_isr(1 val) +{ + var reg_addr = UART_REG_BASE + ZYNQMP_UART_ISR; + !st32 reg_addr, val; + return 0; +} + +inline fun uart_write_ier(1 val) +{ + var reg_addr = UART_REG_BASE + ZYNQMP_UART_IER; + !st32 reg_addr, val; + return 0; +} + +inline fun uart_write_idr(1 val) +{ + var reg_addr = UART_REG_BASE + ZYNQMP_UART_IDR; + !st32 reg_addr, val; + return 0; +} + +inline fun uart_read_char() +{ + var reg_addr = UART_REG_BASE + ZYNQMP_UART_FIFO; + var c = 0; + !ld32 c, reg_addr; + return c & 255; +} + +inline fun uart_write_char(1 c) +{ + var reg_addr = UART_REG_BASE + ZYNQMP_UART_FIFO; + !st32 reg_addr, c; + return 0; +} + +fun tx_provide() +{ + var waiting = WAITING_FOR_TX; + if (waiting) { + /* Wait for TX FIFO empty IRQ before doing more work. */ + return 0; + } + + var tx_queue_handle = SERIAL_TX_QUEUE_HANDLE; + + var transferred = false; + /* Send characters until the TX FIFO is full. */ + while (true) { + var sr = uart_read_sr(); + if (sr & ZYNQMP_UART_CHANNEL_STS_TXNFULL) { + break; + } + + var {1, 1} res = serial_dequeue(tx_queue_handle); + if (res.0) { + break; + } + uart_write_char(res.1 & MAX_UINT8); + transferred = true; + } + + if (transferred) { + /* If work has been done, ensure that the TX FIFO empty IRQ status is cleared + * as the status bits are sticky to prevent stray interrupts. */ + uart_write_isr(ZYNQMP_UART_IXR_TXEMPTY); + } + + var head = serial_queue_get_head(tx_queue_handle); + var is_empty = serial_queue_empty(tx_queue_handle, head); + /* If there is more work to be done, raise a TX FIFO empty interrupt */ + if (!is_empty) { + uart_write_ier(ZYNQMP_UART_IXR_TXEMPTY); + var waiting_ptr = WAITING_FOR_TX_PTR; + !st32 waiting_ptr, true; + } + + if (transferred) { + var require_signal = serial_require_consumer_signal(tx_queue_handle); + if (require_signal) { + serial_cancel_consumer_signal(tx_queue_handle); + sddf_notify(TX_CH); + } + } + + return 0; +} + +fun rx_return() +{ + var rx_queue_handle = SERIAL_RX_QUEUE_HANDLE; + + var reprocess = true; + var enqueued = false; + while (reprocess) { + /* Read from RX FIFO until it is empty. */ + while (true) { + var sr = uart_read_sr(); + if (sr & ZYNQMP_UART_CHANNEL_STS_RXEMPTY) { + break; + } + + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if (is_full) { + break; + } + + var c = uart_read_char(); + var err = serial_enqueue(rx_queue_handle, c & MAX_UINT8); + enqueued = true; + } + + var sr = uart_read_sr(); + if (!(sr & ZYNQMP_UART_CHANNEL_STS_RXEMPTY)) { + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if (is_full) { + /* There's still data to receive but the RX queue is full. */ + serial_request_consumer_signal(rx_queue_handle); + } + } + + reprocess = false; + + sr = uart_read_sr(); + if (!(sr & ZYNQMP_UART_CHANNEL_STS_RXEMPTY)) { + var tail = serial_queue_get_tail(rx_queue_handle); + var is_full = serial_queue_full(rx_queue_handle, tail); + if (!is_full) { + /* There's more space available in the queue. */ + serial_cancel_consumer_signal(rx_queue_handle); + reprocess = true; + } + } + } + + if (enqueued) { + sddf_notify(RX_CH); + } + + return 0; +} + +fun handle_irq() +{ + /* Read and clear the IRQ status bits so we don't get infinitely interrupted. */ + var irq_status = uart_read_isr(); + uart_write_isr(irq_status); + + if (irq_status & ZYNQMP_UART_IXR_TXEMPTY) { + /* We previously requested the device to raise an IRQ when the TX FIFO is empty because it became full + while doing work, now continue. */ + var waiting_ptr = WAITING_FOR_TX_PTR; + !st32 waiting_ptr, false; + + /* Switch off the TX FIFO empty IRQ, only turn it on again when needed. */ + uart_write_idr(ZYNQMP_UART_IXR_TXEMPTY); + + /* Continue sending data from sDDF queue. */ + tx_provide(); + } + + if (irq_status & ZYNQMP_UART_IXR_RXOVR) { + /* The RX FIFO level has hit the watermark, in this case it is 1 byte. Process RX FIFO. */ + rx_return(); + } + + return 0; +} + +export fun notified(1 ch) +{ + if (ch == IRQ_CH) { + handle_irq(); + sddf_deferred_irq_ack(ch); + } else { + if (ch == TX_CH) { + tx_provide(); + } else { + if (ch == RX_CH) { + rx_return(); + } else { + debug_print(ch); + } + } + } + + return 0; +} diff --git a/flake.nix b/flake.nix index cd2364ad6..b89e85faf 100644 --- a/flake.nix +++ b/flake.nix @@ -73,6 +73,9 @@ ''; }); + # pancake/cakeml compiler + pancake = pkgs.callPackage ./ci/cakeml.nix { }; + genmc = pkgs.callPackage ./ci/genmc/nix/package.nix { inherit clang-complete; llvm = pkgs.llvmPackages_20.llvm; }; pythonTool = pkgs.python312.withPackages (ps: [ @@ -140,6 +143,7 @@ llvm.libllvm dtc pythonTool + pancake ]; # To avoid Nix adding compiler flags that are not available on a freestanding diff --git a/include/sddf/serial/queue.pnk b/include/sddf/serial/queue.pnk new file mode 100644 index 000000000..17f3a1f02 --- /dev/null +++ b/include/sddf/serial/queue.pnk @@ -0,0 +1,347 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + * + * Pancake Serial Queue Library + * + * Shared queue functions for serial drivers and components. + * Provides common operations for managing serial queues in Pancake. + * + * The serial queue, like all sDDF queues, is an implementation of a single-producer, + * single-consumer FIFO queue. The key assumption being that only the producer is permitted to + * modify the tail, and only the consumer is permitted to modify the head. Both components are + * permitted to read both indices. The library's atomic operations are written to ensure correctness + * under these assumptions, thus each function's description contains an explicit notes on its + * assumed caller. + */ + +// helper functions +inline fun serial_queue_get_tail(1 queue_handle) +{ + var queue = 0; + !ldw queue, queue_handle; + var tail = 0; + !ld32 tail, queue; + return tail; +} + +inline fun serial_queue_get_head(1 queue_handle) +{ + var queue = 0; + !ldw queue, queue_handle; + var head = 0; + !ld32 head, queue + 4; + return head; +} + +inline fun serial_queue_get_producer_signalled(1 queue_handle) +{ + var queue = 0; + !ldw queue, queue_handle; + var producer_signalled = 0; + !ld32 producer_signalled, queue + 8; + return producer_signalled; +} + +inline fun serial_queue_set_tail(1 queue_handle, 1 new_tail) +{ + var queue = 0; + !ldw queue, queue_handle; + !st32 queue, new_tail; + return 0; +} + +inline fun serial_queue_set_head(1 queue_handle, 1 new_head) +{ + var queue = 0; + !ldw queue, queue_handle; + !st32 queue + 4, new_head; + return 0; +} + +inline fun serial_queue_set_producer_signalled(1 queue_handle, 1 new_producer_signalled) +{ + var queue = 0; + !ldw queue, queue_handle; + !st32 queue + 8, new_producer_signalled; + return 0; +} + +inline fun serial_queue_get_capacity(1 queue_handle) +{ + var capacity = 0; + !ld32 capacity, queue_handle + 8; + return capacity; +} + +inline fun serial_queue_get_data_region(1 queue_handle) +{ + var data_region = 0; + !ldw data_region, queue_handle + 16; + return data_region; +} + +// library functions + +/** + * Return the number of bytes of data stored in the queue. This is calculated by + * using the head and tail values currently stored in the shared queue handle + * data structure. This function should only be called by the CONSUMER of the + * queue. + * + * @param queue_handle queue containing the data. + * + * @return The number bytes of data stored in the queue. + */ +inline fun serial_queue_length_consumer(1 queue_handle) +{ + /* The load-acquire of tail will be paired with the store-release + * in serial_enqueue() or serial_update_shared_tail(). + */ + var tail = serial_queue_get_tail(queue_handle); + THREAD_MEMORY_ACQUIRE(); + var head = serial_queue_get_head(queue_handle); + return tail - head; +} + +/** + * Return the number of bytes of data stored in the queue. This is calculated by + * using the head and tail values currently stored in the shared queue handle + * data structure. This function should only be called by the PRODUCER of the + * queue. + * + * @param queue_handle queue containing the data. + * + * @return The number bytes of data stored in the queue. + */ +inline fun serial_queue_length_producer(1 queue_handle) +{ + var tail = serial_queue_get_tail(queue_handle); + /* The load-acquire of head will be paired with the store-release + * in serial_dequeue() or serial_update_shared_head(). + */ + var head = serial_queue_get_head(queue_handle); + THREAD_MEMORY_ACQUIRE(); + return tail - head; +} + +/** + * Check if the queue is empty. This function should only be called by the + * CONSUMER of the queue. + * + * @param queue_handle queue to check. + * @param local_head head which points to the next character to be dequeued. + * Should be set to the value of the shared head in the queue if a local copy is + * not in use. + * + * @return true indicates the queue is empty, false otherwise. + */ +inline fun serial_queue_empty(1 queue_handle, 1 local_head) +{ + /* The load-acquire will be paired with the store-release + * in serial_enqueue() or serial_update_shared_tail(). + */ + var tail = serial_queue_get_tail(queue_handle); + THREAD_MEMORY_ACQUIRE(); + return local_head == tail; +} + +/** + * Check if the queue is full. This function should only be called by the + * PRODUCER of the queue. + * + * @param queue_handle queue to check. + * @param local_tail tail which points to the next enqueue slot. Should be set + * to the value of the shared tail in the queue if a local copy is not in use. + * + * @return true indicates the queue is full, false otherwise. + */ +inline fun serial_queue_full(1 queue_handle, 1 local_tail) +{ + /* The load-acquire will be paired with the store-release + * in serial_dequeue() or serial_update_shared_head(). + */ + var head = serial_queue_get_tail(queue_handle); + THREAD_MEMORY_ACQUIRE(); + var capacity = serial_queue_get_capacity(queue_handle); + return local_tail - head == capacity; +} + +/** + * Enqueue a character into a queue. Update the shared tail so the character is + * visible to the consumer. This function should only be called by the PRODUCER + * of the queue. + * + * @param queue_handle queue to enqueue into. + * @param character character to be enqueued. + * + * @return -1 when queue is full, 0 on success. + */ +inline fun serial_enqueue(1 queue_handle, 1 character) +{ + var tail = serial_queue_get_tail(queue_handle); + + var is_full = serial_queue_full(queue_handle, tail); + if (is_full) { + return -1; + } + + // optimisation: cache tail value + // tail = serial_queue_get_tail(queue_handle); + + var capacity = serial_queue_get_capacity(queue_handle); + var pos = tail & (capacity - 1); + var data_region = serial_queue_get_data_region(queue_handle); + !st8 data_region + pos, character; + + // optimisation: cache tail value + // tail = serial_queue_get_tail(queue_handle); + + /* The store-release will synchronise with load-acquires by the CONSUMER of the queue. */ + THREAD_MEMORY_RELEASE(); + serial_queue_set_tail(queue_handle, tail + 1); + + return 0; +} + +// TODO +// inline fun serial_enqueue_local(1 queue_handle, 1 local_tail, 1 character) + +/** + * Dequeue a character from a queue. Update the shared head so the removal of + * the character is visible to the producer. This function should only be called + * by the CONSUMER of the queue. + * + * @param queue_handle queue to dequeue from. + * @param character address of character to copy into. + * + * @return {-1, _} when queue is empty, {0, character} on success. + */ +inline fun {1, 1} serial_dequeue(1 queue_handle) +{ + var head = serial_queue_get_head(queue_handle); + + var is_empty = serial_queue_empty(queue_handle, head); + + if (is_empty) { + return <-1, 0>; + } + + var capacity = serial_queue_get_capacity(queue_handle); + var pos = head & (capacity - 1); + var data_region = serial_queue_get_data_region(queue_handle); + var character = 0; + !ld8 character, data_region + pos; + + /* The store-release will synchronise with load-acquires by the PRODUCER of the queue. */ + THREAD_MEMORY_RELEASE(); + serial_queue_set_head(queue_handle, head + 1); + + return <0, character>; +} + +// TODO +// inline fun {1, 1} serial_dequeue_locals(1 queue_handle, 1 local_head) +// inline fun serial_update_shared_tail(1 queue_handle, 1 local_tail) +// inline fun serial_update_shared_head(1 queue_handle, 1 local_tail) +// inline fun serial_queue_contiguous_length(1 queue_handle) +// inline fun serial_queue_free(1 queue_handle) +// inline fun serial_queue_contiguous_free(1 queue_handle) +// inline fun serial_queue_batch(1 queue_handle, 1 num, 1 src) +// inline fun serial_transfer_all(1 free_queue_handle, 1 active_queue_handle) +// inline fun serial_transfer_all_colour(1 free_queue_handle, +// 1 active_queue_handle, 1 col_start, +// 1 col_start_len, 1 col_end, 1 col_end_len) + +/** + * Indicate to consumer of the queue that producer requires signalling. + * + * @param queue queue handle of queue that requires signalling upon enqueuing. + */ +inline fun serial_request_consumer_signal(1 queue_handle) +{ + serial_queue_set_producer_signalled(queue_handle, 0); + /* The sc fence will synchronise with the sc fence in the consumer in the signalling protocol, + * such that at least one of the producer and the consumer can make progress + * and avoid deadlock. + * + * More information on the sDDF signalling protocol can be found in the corresponding section + * in the developer docs (docs/developing.md). + * How the serial subsystem uses the protocol can be found in the serial docs + * (docs/serial/serial.md). + * + * The sc fence ensures that: + * 1. The producer is observed to set the `producer_signalled` flag before performing its + * "re-check" of the space in the queue. + * 2. The consumer is observed to check the producer_signalled flag after it terminates updating + * the queue. + * Enforcing the ordering of these events ensures that the producer will not become deadlocked + * awaiting free space in the queue (assuming this space does become available). + * + * Once the producer breaks out of its queue processing loop, if the consumer subsequently + * makes space available there are two possible timelines: + * 1. The producer's update to the flag is observed by the consumer, causing the consumer to + * notify. + * 2. The producer's update to the flag is not observed, thus must not have occurred at the time + * of the consumer's decision to notify. If this is the case then the producer's "re-check" + * also hasn't occurred (thanks to the fences). Thus, when this "re-check" is performed the + * free space in the queue will be observable (thanks again to the fences), triggering the + * producer to "re-process" the queue. + * + * From a memory model perspective, the following shape of cppmem program + * (http://svr-pes20-cppmem.cl.cam.ac.uk/cppmem/) represents the undesired behaviour that is + * prevented by the pair of sc fences. + * + * int main() { + * atomic_int flag = 0; + * atomic_int queue = 0; + * {{{ + * // producer + * { + * flag.store(1, relaxed); + * atomic_thread_fence(seq_cst); + * queue.load(relaxed).readsvalue(0); + * } + * ||| + * // consumer + * { + * queue.store(1, relaxed); + * atomic_thread_fence(seq_cst); + * flag.load(relaxed).readsvalue(0); + * } + * }}}; + * } + */ + fence_seq_cst(); + return 0; +} + +/** + * Indicate that producer has been signalled. + * + * @param queue queue handle of the queue that has been signalled. + */ +inline fun serial_cancel_consumer_signal(1 queue_handle) +{ + serial_queue_set_producer_signalled(queue_handle, 1); + /* It is not necessary to insert a sc fence here, unlike in serial_request_consumer_signal(), + * as the signalling protocol requires that a cancellation is always followed by a request. + */ + return 0; +} + +/** + * Producer of the queue requires signalling. + * + * @param queue queue handle of the queue to check. + */ +inline fun serial_require_consumer_signal(1 queue_handle) +{ + /* The sc fence will synchronise with the sc fence in the producer in the signalling protocol, + * such that at least one of the producer and the consumer can make progress + * and avoid deadlock. + */ + fence_seq_cst(); + var producer_signalled = serial_queue_get_producer_signalled(queue_handle); + return !producer_signalled; +} diff --git a/tools/make/board/common.mk b/tools/make/board/common.mk index 5a415d695..7f7b3bbbd 100644 --- a/tools/make/board/common.mk +++ b/tools/make/board/common.mk @@ -27,6 +27,7 @@ endif include ${SDDF}/tools/make/board/${MICROKIT_BOARD}.mk include ${SDDF}/tools/make/toolchain/${TOOLCHAIN}.mk +include ${SDDF}/tools/make/toolchain/pancake.mk MICROKIT_TOOL ?= $(MICROKIT_SDK)/bin/microkit diff --git a/tools/make/toolchain/pancake.mk b/tools/make/toolchain/pancake.mk new file mode 100644 index 000000000..fe8c72944 --- /dev/null +++ b/tools/make/toolchain/pancake.mk @@ -0,0 +1,21 @@ +# +# Copyright 2026, UNSW +# +# SPDX-License-Identifier: BSD-2-Clause +# +PANCAKE_COMPILER ?= cake + +ifeq ($(ARCH),aarch64) + PANCAKE_TARGET := arm8 +else ifeq ($(ARCH),riscv64) + PANCAKE_TARGET := riscv +else ifeq ($(ARCH),x86_64) + PANCAKE_TARGET := x64 +else +$(error Unsupported ARCH given) +endif + +PANCAKE_FLAGS += \ + --pancake \ + --target=$(PANCAKE_TARGET) \ + --main_return=true diff --git a/util/pancake_ffi.c b/util/pancake_ffi.c new file mode 100644 index 000000000..18a850694 --- /dev/null +++ b/util/pancake_ffi.c @@ -0,0 +1,75 @@ +/* + * Copyright 2026, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +void ffisddf_irq_ack(unsigned char *c, long clen, unsigned char *a, long alen) +{ + sddf_irq_ack(clen); +} + +void ffisddf_notify(unsigned char *c, long clen, unsigned char *a, long alen) +{ + sddf_notify(clen); +} + +void ffisddf_deferred_irq_ack(unsigned char *c, long clen, unsigned char *a, long alen) +{ + sddf_deferred_irq_ack(clen); +} + +void ffisddf_deferred_notify(unsigned char *c, long clen, unsigned char *a, long alen) +{ + sddf_deferred_notify(clen); +} + +#if defined(CONFIG_ARCH_X86_64) +void ffimicrokit_x86_ioport_write_8(unsigned char *c, long clen, unsigned char *a, long alen) +{ + microkit_x86_ioport_write_8(clen, (seL4_Word)c, (seL4_Word)a); +} + +void ffimicrokit_x86_ioport_read_8(unsigned char *c, long clen, unsigned char *a, long alen) +{ + seL4_Uint8 ret = microkit_x86_ioport_read_8(clen, (seL4_Word)a); + *(seL4_Uint8 *)c = ret; +} +#endif + +void fficache_clean(unsigned char *c, long clen, unsigned char *a, long alen) +{ + cache_clean((unsigned long)c, (unsigned long)a); +} + +void fficache_clean_and_invalidate(unsigned char *c, long clen, unsigned char *a, long alen) +{ + cache_clean_and_invalidate((unsigned long)c, (unsigned long)a); +} + +void ffiTHREAD_MEMORY_RELEASE(unsigned char *c, long clen, unsigned char *a, long alen) +{ + THREAD_MEMORY_RELEASE(); +} + +void ffiTHREAD_MEMORY_ACQUIRE(unsigned char *c, long clen, unsigned char *a, long alen) +{ + THREAD_MEMORY_ACQUIRE(); +} + +void ffidebug_print(unsigned char *c, long clen, unsigned char *a, long alen) +{ + /* clen = debug value to print, alen = context/location id */ + sddf_dprintf("[DEBUG] Location %ld: Value = %ld (0x%lx)\n", alen, clen, clen); +} + +void ffifence_seq_cst(unsigned char *c, long clen, unsigned char *a, long alen) +{ + __atomic_thread_fence(__ATOMIC_SEQ_CST); +} diff --git a/util/util.pnk b/util/util.pnk new file mode 100644 index 000000000..d00856ab8 --- /dev/null +++ b/util/util.pnk @@ -0,0 +1,52 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* Helper constants */ +#define MAX_UINT32 4294967295 +#define MAX_UINT16 65535 +#define MAX_UINT8 255 + +/* Some helper macros */ +#define BIT(nr) (1 << nr) // TODO: add parentheses prevented by a parse error +#define pnk_mem_ptr(idx) (@base + (idx) * @biw) +#define pnk_mem(idx) (lds 1 pnk_mem_ptr(idx)) +#define bitwise_not(n) ((n) ^ -1) + +/* Macros for FFI calls */ +#define sddf_irq_ack(channel) \ + @sddf_irq_ack(0, channel, 0, 0) + +#define sddf_notify(channel) \ + @sddf_notify(0, channel, 0, 0) + +#define sddf_deferred_irq_ack(channel) \ + @sddf_deferred_irq_ack(0, channel, 0, 0) + +#define sddf_deferred_notify(channel) \ + @sddf_deferred_notify(0, channel, 0, 0) + +#define microkit_x86_ioport_write_8(ioport_id, port_addr, data) \ + @microkit_x86_ioport_write_8(port_addr, ioport_id, data, 0) + +#define microkit_x86_ioport_read_8(dst_addr, ioport_id, port_addr) \ + @microkit_x86_ioport_read_8(dst_addr, ioport_id, port_addr, 0) + +#define cache_clean(start, end) \ + @cache_clean(start, 0, end, 0) + +#define cache_clean_and_invalidate(start, end) \ + @cache_clean_and_invalidate(start, 0, end, 0) + +#define THREAD_MEMORY_RELEASE() \ + @THREAD_MEMORY_RELEASE(0, 0, 0, 0) + +#define THREAD_MEMORY_ACQUIRE() \ + @THREAD_MEMORY_ACQUIRE(0, 0, 0, 0) + +#define fence_seq_cst() \ + @fence_seq_cst(0, 0, 0, 0) + +#define debug_print(value) \ + @debug_print(0, value, 0, __LINE__)