From 8850cdab7573a580a9b426bdb7b229a82fcfa960 Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Fri, 14 Aug 2026 16:50:28 +1000 Subject: [PATCH 01/21] util: helper definitions for pancake components Signed-off-by: Kurt Wu --- util/pancake_ffi.c | 42 ++++++++++++++++++++++++++++++++++++++++++ util/util.pnk | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 util/pancake_ffi.c create mode 100644 util/util.pnk diff --git a/util/pancake_ffi.c b/util/pancake_ffi.c new file mode 100644 index 00000000..17653204 --- /dev/null +++ b/util/pancake_ffi.c @@ -0,0 +1,42 @@ +#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); +} + +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); +} diff --git a/util/util.pnk b/util/util.pnk new file mode 100644 index 00000000..e97974a9 --- /dev/null +++ b/util/util.pnk @@ -0,0 +1,43 @@ +/* + * Copyright 2025, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* Helper constants */ +#define MAX_INT32 4294967295 +#define MAX_INT16 65535 +#define MAX_INT8 255 + +/* Some helper macros */ +#define pnk_mem_ptr(idx) (@base + (idx) * @biw) +#define pnk_mem(idx) (lds 1 pnk_mem_ptr(idx)) + +#define get_idx_array(addr, idx) lds 1 (addr + idx * @biw) + +/* 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 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 debug_print(c, clen, a, alen) \ + @debug_print(c,clen,a,alen) From f8c78a752e413048c1388fb18bcec9b50999399a Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Fri, 14 Aug 2026 16:50:59 +1000 Subject: [PATCH 02/21] serial: add serial queue in pancake Signed-off-by: Kurt Wu --- include/sddf/serial/queue.pnk | 164 ++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 include/sddf/serial/queue.pnk diff --git a/include/sddf/serial/queue.pnk b/include/sddf/serial/queue.pnk new file mode 100644 index 00000000..3ac70234 --- /dev/null +++ b/include/sddf/serial/queue.pnk @@ -0,0 +1,164 @@ +/* + * 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. + */ + +fun serial_queue_get_head(1 queue_ptr) +{ + var head = 0; + !ld32 head, queue_ptr + 4; + return head; +} + +fun serial_queue_get_tail(1 queue_ptr) +{ + var tail = 0; + !ld32 tail, queue_ptr; + return tail; +} + +fun serial_queue_set_head(1 queue_ptr, 1 new_head) +{ + !st32 queue_ptr + 4, new_head; + return 0; +} + +fun serial_queue_set_tail(1 queue_ptr, 1 new_tail) +{ + !st32 queue_ptr, new_tail; + return 0; +} + +fun serial_queue_is_empty(1 queue_ptr) +{ + var 1 head = serial_queue_get_head(queue_ptr); + var 1 tail = serial_queue_get_tail(queue_ptr); + return (head == tail); +} + +fun serial_queue_is_full(1 queue_ptr, 1 capacity) +{ + var 1 head = serial_queue_get_head(queue_ptr); + var 1 tail = serial_queue_get_tail(queue_ptr); + var used = tail - head; + return (used == capacity); +} + +fun serial_queue_get_used(1 queue_ptr) +{ + var 1 head = serial_queue_get_head(queue_ptr); + var 1 tail = serial_queue_get_tail(queue_ptr); + return (tail - head); +} + +fun serial_queue_get_free(1 queue_ptr, 1 capacity) +{ + var 1 used = serial_queue_get_used(queue_ptr); + return (capacity - used); +} + +fun serial_queue_enqueue_char(1 queue_ptr, 1 capacity, 1 data_ptr, 1 c) +{ + var 1 tail = serial_queue_get_tail(queue_ptr); + var pos = tail & (capacity - 1); + var char_addr = data_ptr + pos; + !st8 char_addr, c; + + var new_tail = tail + 1; + serial_queue_set_tail(queue_ptr, new_tail); + return 0; +} + +fun serial_queue_dequeue_char(1 queue_ptr, 1 capacity, 1 data_ptr) +{ + var 1 head = serial_queue_get_head(queue_ptr); + var pos = head & (capacity - 1); + var char_addr = data_ptr + pos; + var c = 0; + !ld8 c, char_addr; + + var new_head = head + 1; + serial_queue_set_head(queue_ptr, new_head); + return c; +} + +fun serial_queue_get_signal_state(1 queue_ptr) +{ + var signal_state = 0; + !ld32 signal_state, queue_ptr + 8; + return signal_state; +} + +fun serial_queue_set_signal_state(1 queue_ptr, 1 state) +{ + !st32 queue_ptr + 8, state; + return 0; +} + +fun serial_queue_request_signal(1 queue_ptr) +{ + return serial_queue_set_signal_state(queue_ptr, 0); +} + +fun serial_queue_cancel_signal(1 queue_ptr) +{ + return serial_queue_set_signal_state(queue_ptr, 1); +} + +fun serial_queue_require_signal(1 queue_ptr) +{ + var 1 signal_state = serial_queue_get_signal_state(queue_ptr); + return (!signal_state); +} + +fun serial_require_consumer_signal_client(1 client, 1 cli_queue_base) { + var cli_offset = cli_queue_base + client * 3; + var cli_queue_ptr = pnk_mem(cli_offset); + + var producer_signalled = 0; + !ld32 producer_signalled, cli_queue_ptr + 8; + + return !producer_signalled; +} + +fun serial_cancel_consumer_signal_client(1 client, 1 cli_queue_base) { + var cli_offset = cli_queue_base + client * 3; + var cli_queue_ptr = pnk_mem(cli_offset); + + !st32 cli_queue_ptr + 8, 1; + return 0; +} + +fun serial_request_consumer_signal_client(1 client, 1 cli_queue_base) { + var cli_offset = cli_queue_base + client * 3; + var cli_queue_ptr = pnk_mem(cli_offset); + + !st32 cli_queue_ptr + 8, 0; + return 0; +} + +fun serial_queue_get_length(1 queue_ptr) +{ + var 1 head = serial_queue_get_head(queue_ptr); + var 1 tail = serial_queue_get_tail(queue_ptr); + return (tail - head); +} + +fun serial_queue_transfer_byte(1 src_queue_ptr, 1 src_capacity, 1 src_data_ptr, 1 dst_queue_ptr, 1 dst_capacity, 1 dst_data_ptr) +{ + var 1 src_empty = serial_queue_is_empty(src_queue_ptr); + if (src_empty) { return 0; } + + var 1 dst_full = serial_queue_is_full(dst_queue_ptr, dst_capacity); + if (dst_full) { return 0; } + + var 1 byte_val = serial_queue_dequeue_char(src_queue_ptr, src_capacity, src_data_ptr); + serial_queue_enqueue_char(dst_queue_ptr, dst_capacity, dst_data_ptr, byte_val); + return 1; +} + From e7d06b085a4e2c2929acafc55c16b267a6190d45 Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Fri, 14 Aug 2026 17:36:36 +1000 Subject: [PATCH 03/21] tools: add pancake related definitions Signed-off-by: Kurt Wu --- tools/make/board/common.mk | 1 + tools/make/toolchain/pancake.mk | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tools/make/toolchain/pancake.mk diff --git a/tools/make/board/common.mk b/tools/make/board/common.mk index 5a415d69..7f7b3bbb 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 00000000..fe8c7294 --- /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 From 59b2c04dd26636a820356fe4d48524511fb02b14 Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Fri, 14 Aug 2026 16:51:59 +1000 Subject: [PATCH 04/21] serial: add imx driver in pancake Signed-off-by: Kurt Wu --- drivers/serial/imx/serial_driver.mk | 19 ++- drivers/serial/imx/uart.c | 62 +++++++ drivers/serial/imx/uart.pnk | 254 ++++++++++++++++++++++++++++ 3 files changed, 332 insertions(+), 3 deletions(-) create mode 100644 drivers/serial/imx/uart.pnk diff --git a/drivers/serial/imx/serial_driver.mk b/drivers/serial/imx/serial_driver.mk index 4af3b768..b9d6691f 100644 --- a/drivers/serial/imx/serial_driver.mk +++ b/drivers/serial/imx/serial_driver.mk @@ -8,11 +8,24 @@ SERIAL_DRIVER_DIR := $(dir $(lastword $(MAKEFILE_LIST))) -serial_driver.elf: serial/imx/serial_driver.o +DRIVER_PNK = \ + ${UTIL}/util.pnk \ + ${SDDF}/include/sddf/serial/queue.pnk \ + ${SERIAL_DRIVER_DIR}/uart.pnk + +# serial_driver.elf: serial/imx/serial_driver.o +serial_driver.elf: serial/imx/serial_pnk.o serial/imx/serial_driver.o util/pancake_ffi.o $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ +serial/imx/serial_pnk.o: serial/imx/serial_pnk.S |serial/imx + $(CC) -c $(CFLAGS) -o $@ $< + +serial/imx/serial_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) -I${SERIAL_DRIVER_DIR}/include -o $@ $< +# $(CC) -c $(CFLAGS) -I${SERIAL_DRIVER_DIR}/include -o $@ $< + $(CC) -c $(CFLAGS) -DPANCAKE_SERIAL_DRIVER -I${SERIAL_DRIVER_DIR}/include -o $@ $< -include serial_driver.d @@ -20,7 +33,7 @@ serial/imx: mkdir -p $@ clean:: - rm -f serial/imx/serial_driver.[do] + rm -f serial/imx/serial_driver.[do] serial/imx/serial_pnk.[oS] clobber:: rm -rf serial diff --git a/drivers/serial/imx/uart.c b/drivers/serial/imx/uart.c index 8cb06347..ca3e1570 100644 --- a/drivers/serial/imx/uart.c +++ b/drivers/serial/imx/uart.c @@ -16,8 +16,42 @@ __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 */ + +#ifndef PANCAKE_SERIAL_DRIVER serial_queue_handle_t rx_queue_handle; serial_queue_handle_t tx_queue_handle; +#endif /* PANCAKE_SERIAL_DRIVER */ volatile imx_uart_regs_t *uart_regs; @@ -40,6 +74,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 +147,7 @@ static void handle_irq(void) uart_cr1 = uart_regs->cr1; } } +#endif /* PANCAKE_SERIAL_DRIVER */ static void uart_setup(void) { @@ -166,12 +202,37 @@ void init(void) uart_setup(); +#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; + serial_queue_handle_t *rx_queue_handlep = (serial_queue_handle_t *) &pnk_mem[4]; + serial_queue_handle_t *tx_queue_handlep = (serial_queue_handle_t *) &pnk_mem[7]; + pnk_mem[1024] = config.rx_enabled; + + // Initialize queue handles + if (config.rx_enabled) { + serial_queue_init(rx_queue_handlep, config.rx.queue.vaddr, config.rx.data.size, config.rx.data.vaddr); + } + serial_queue_init(tx_queue_handlep, config.tx.queue.vaddr, config.tx.data.size, config.tx.data.vaddr); + + cml_main(); +#else /* PANCAKE_SERIAL_DRIVER */ if (config.rx_enabled) { 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); +#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 +247,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 00000000..215395bc --- /dev/null +++ b/drivers/serial/imx/uart.pnk @@ -0,0 +1,254 @@ +/* + * 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 pnk_mem(4) +#define SERIAL_RX_CAPACITY pnk_mem(5) +#define SERIAL_RX_DATA pnk_mem(6) + +#define SERIAL_TX_QUEUE pnk_mem(7) +#define SERIAL_TX_CAPACITY pnk_mem(8) +#define SERIAL_TX_DATA pnk_mem(9) + +#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 + +#define UART_CR1_UART_EN 1 +#define UART_CR1_RX_READY_INT 512 +#define UART_CR1_TX_READY_INT 8192 + +#define UART_CR2_TX_EN 4 +#define UART_CR2_RX_EN 2 + +#define UART_SR1_RX_RDY 512 +#define UART_SR1_TX_RDY 8192 +#define UART_SR1_FRM_ERR 1024 +#define UART_SR1_PARITY_ERR 32768 +#define UART_SR1_ABNORMAL 33792 + +#define UART_TST_TX_FIFO_FULL 16 +#define UART_TST_RX_FIFO_EMPTY 32 + + +fun uart_read_rxd() +{ + var reg_addr = UART_REG_BASE + RXD_OFFSET; + var rxd = 0; + !ld32 rxd, reg_addr; + return rxd; +} + +fun uart_write_txd(1 data) +{ + var reg_addr = UART_REG_BASE + TXD_OFFSET; + !st32 reg_addr, data; + return 0; +} + +fun uart_read_cr1() +{ + var reg_addr = UART_REG_BASE + CR1_OFFSET; + var cr1 = 0; + !ld32 cr1, reg_addr; + return cr1; +} + +fun uart_write_cr1(1 cr1) +{ + var reg_addr = UART_REG_BASE + CR1_OFFSET; + !st32 reg_addr, cr1; + return 0; +} + +fun uart_read_sr1() +{ + var reg_addr = UART_REG_BASE + SR1_OFFSET; + var sr1 = 0; + !ld32 sr1, reg_addr; + return sr1; +} + +fun uart_write_sr1(1 sr1) +{ + var reg_addr = UART_REG_BASE + SR1_OFFSET; + !st32 reg_addr, sr1; + return 0; +} + +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 transferred = false; + var tx_queue = SERIAL_TX_QUEUE; + var tx_capacity = SERIAL_TX_CAPACITY; + var tx_data = SERIAL_TX_DATA; + + while (true) + { + var 1 ts = uart_read_ts(); + if (ts & UART_TST_TX_FIFO_FULL) { break; } + + var 1 is_empty = serial_queue_is_empty(tx_queue); + if (is_empty) { break; } + + var 1 c = serial_queue_dequeue_char(tx_queue, tx_capacity, tx_data); + uart_write_txd(c); + transferred = true; + } + + var 1 ts = uart_read_ts(); + var 1 cr1 = uart_read_cr1(); + var 1 is_empty = serial_queue_is_empty(tx_queue); + + if ((ts & UART_TST_TX_FIFO_FULL) && (!is_empty)) { + cr1 = cr1 | UART_CR1_TX_READY_INT; + uart_write_cr1(cr1); + } else { + cr1 = cr1 & 4294959103; + uart_write_cr1(cr1); + } + + var 1 requires_signal = serial_queue_require_signal(tx_queue); + if (transferred && requires_signal) { + serial_queue_cancel_signal(tx_queue); + sddf_notify(TX_CH); + } + + return 0; +} + +fun rx_return() +{ + var reprocess = true; + var enqueued = false; + var rx_queue = SERIAL_RX_QUEUE; + var rx_capacity = SERIAL_RX_CAPACITY; + var rx_data = SERIAL_RX_DATA; + + while (reprocess) + { + while (true) + { + var 1 ts = uart_read_ts(); + if (ts & UART_TST_RX_FIFO_EMPTY) { break; } + + var 1 is_full = serial_queue_is_full(rx_queue, rx_capacity); + if (is_full) { break; } + + var 1 rxd = uart_read_rxd(); + var c = rxd & 255; + serial_queue_enqueue_char(rx_queue, rx_capacity, rx_data, c); + enqueued = true; + } + + var 1 ts = uart_read_ts(); + var 1 is_full = serial_queue_is_full(rx_queue, rx_capacity); + + if ((!(ts & UART_TST_RX_FIFO_EMPTY)) && is_full) { + var 1 cr1 = uart_read_cr1(); + cr1 = cr1 & 4294966783; + uart_write_cr1(cr1); + serial_queue_request_signal(rx_queue); + } + + reprocess = false; + + if ((!(ts & UART_TST_RX_FIFO_EMPTY)) && (!is_full)) { + serial_queue_cancel_signal(rx_queue); + var 1 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 1 uart_sr1 = uart_read_sr1(); + var 1 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) { + uart_write_sr1(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); + return 0; + } + + if (ch == TX_CH) { + tx_provide(); + return 0; + } + + if (ch == RX_CH) { + var 1 cr1 = uart_read_cr1(); + cr1 = cr1 | UART_CR1_RX_READY_INT; + uart_write_cr1(cr1); + + rx_return(); + return 0; + } + + return 0; +} From b6d06c3483790c05c8d9422a537f13f477f047ed Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Mon, 17 Aug 2026 22:10:15 +1000 Subject: [PATCH 05/21] serial/imx: refactor Signed-off-by: Kurt Wu --- drivers/serial/imx/uart.c | 28 +-- drivers/serial/imx/uart.pnk | 406 +++++++++++++++++----------------- include/sddf/serial/queue.pnk | 385 +++++++++++++++++++++++--------- util/pancake_ffi.c | 4 + util/util.pnk | 33 +-- 5 files changed, 523 insertions(+), 333 deletions(-) diff --git a/drivers/serial/imx/uart.c b/drivers/serial/imx/uart.c index ca3e1570..15cce4d1 100644 --- a/drivers/serial/imx/uart.c +++ b/drivers/serial/imx/uart.c @@ -17,7 +17,7 @@ __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]; +static char cml_memory[1024 * 20]; extern void *cml_heap; extern void *cml_stack; extern void *cml_stackend; @@ -40,18 +40,16 @@ void cml_clear() { } void init_pancake_mem() { - unsigned long cml_heap_sz = 1024*10; - unsigned long cml_stack_sz = 1024*10; + 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 */ -#ifndef PANCAKE_SERIAL_DRIVER serial_queue_handle_t rx_queue_handle; serial_queue_handle_t tx_queue_handle; -#endif /* PANCAKE_SERIAL_DRIVER */ volatile imx_uart_regs_t *uart_regs; @@ -202,6 +200,11 @@ void init(void) uart_setup(); + if (config.rx_enabled) { + 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(); @@ -211,22 +214,11 @@ void init(void) pnk_mem[1] = device_resources.irqs[0].id; pnk_mem[2] = config.rx.id; pnk_mem[3] = config.tx.id; - serial_queue_handle_t *rx_queue_handlep = (serial_queue_handle_t *) &pnk_mem[4]; - serial_queue_handle_t *tx_queue_handlep = (serial_queue_handle_t *) &pnk_mem[7]; + pnk_mem[4] = (uintptr_t) &rx_queue_handle; + pnk_mem[5] = (uintptr_t) &tx_queue_handle; pnk_mem[1024] = config.rx_enabled; - // Initialize queue handles - if (config.rx_enabled) { - serial_queue_init(rx_queue_handlep, config.rx.queue.vaddr, config.rx.data.size, config.rx.data.vaddr); - } - serial_queue_init(tx_queue_handlep, config.tx.queue.vaddr, config.tx.data.size, config.tx.data.vaddr); - cml_main(); -#else /* PANCAKE_SERIAL_DRIVER */ - if (config.rx_enabled) { - 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); #endif /* PANCAKE_SERIAL_DRIVER */ } diff --git a/drivers/serial/imx/uart.pnk b/drivers/serial/imx/uart.pnk index 215395bc..12751372 100644 --- a/drivers/serial/imx/uart.pnk +++ b/drivers/serial/imx/uart.pnk @@ -3,252 +3,260 @@ * 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 pnk_mem(4) -#define SERIAL_RX_CAPACITY pnk_mem(5) -#define SERIAL_RX_DATA pnk_mem(6) - -#define SERIAL_TX_QUEUE pnk_mem(7) -#define SERIAL_TX_CAPACITY pnk_mem(8) -#define SERIAL_TX_DATA pnk_mem(9) - -#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 - -#define UART_CR1_UART_EN 1 -#define UART_CR1_RX_READY_INT 512 -#define UART_CR1_TX_READY_INT 8192 - -#define UART_CR2_TX_EN 4 -#define UART_CR2_RX_EN 2 - -#define UART_SR1_RX_RDY 512 -#define UART_SR1_TX_RDY 8192 -#define UART_SR1_FRM_ERR 1024 -#define UART_SR1_PARITY_ERR 32768 -#define UART_SR1_ABNORMAL 33792 - -#define UART_TST_TX_FIFO_FULL 16 -#define UART_TST_RX_FIFO_EMPTY 32 - - -fun uart_read_rxd() +#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; + var reg_addr = UART_REG_BASE + RXD_OFFSET; + var rxd = 0; + !ld32 rxd, reg_addr; + return rxd; } -fun uart_write_txd(1 data) +inline fun uart_write_txd(1 data) { - var reg_addr = UART_REG_BASE + TXD_OFFSET; - !st32 reg_addr, data; - return 0; + var reg_addr = UART_REG_BASE + TXD_OFFSET; + !st32 reg_addr, data; + return 0; } -fun uart_read_cr1() +inline fun uart_read_cr1() { - var reg_addr = UART_REG_BASE + CR1_OFFSET; - var cr1 = 0; - !ld32 cr1, reg_addr; - return cr1; + var reg_addr = UART_REG_BASE + CR1_OFFSET; + var cr1 = 0; + !ld32 cr1, reg_addr; + return cr1; } -fun uart_write_cr1(1 cr1) +inline fun uart_write_cr1(1 cr1) { - var reg_addr = UART_REG_BASE + CR1_OFFSET; - !st32 reg_addr, cr1; - return 0; + var reg_addr = UART_REG_BASE + CR1_OFFSET; + !st32 reg_addr, cr1; + return 0; } -fun uart_read_sr1() +inline fun uart_read_sr1() { - var reg_addr = UART_REG_BASE + SR1_OFFSET; - var sr1 = 0; - !ld32 sr1, reg_addr; - return sr1; + var reg_addr = UART_REG_BASE + SR1_OFFSET; + var sr1 = 0; + !ld32 sr1, reg_addr; + return sr1; } -fun uart_write_sr1(1 sr1) +inline fun uart_write_sr1(1 sr1) { - var reg_addr = UART_REG_BASE + SR1_OFFSET; - !st32 reg_addr, sr1; - return 0; + var reg_addr = UART_REG_BASE + SR1_OFFSET; + !st32 reg_addr, sr1; + return 0; } -fun uart_read_ts() +inline fun uart_read_ts() { - var reg_addr = UART_REG_BASE + TS_OFFSET; - var ts = 0; - !ld32 ts, reg_addr; - return ts; + var reg_addr = UART_REG_BASE + TS_OFFSET; + var ts = 0; + !ld32 ts, reg_addr; + return ts; } - export fun tx_provide() { - var transferred = false; - var tx_queue = SERIAL_TX_QUEUE; - var tx_capacity = SERIAL_TX_CAPACITY; - var tx_data = SERIAL_TX_DATA; - - while (true) - { - var 1 ts = uart_read_ts(); - if (ts & UART_TST_TX_FIFO_FULL) { break; } - - var 1 is_empty = serial_queue_is_empty(tx_queue); - if (is_empty) { break; } - - var 1 c = serial_queue_dequeue_char(tx_queue, tx_capacity, tx_data); - uart_write_txd(c); - transferred = true; - } - - var 1 ts = uart_read_ts(); - var 1 cr1 = uart_read_cr1(); - var 1 is_empty = serial_queue_is_empty(tx_queue); - - if ((ts & UART_TST_TX_FIFO_FULL) && (!is_empty)) { - cr1 = cr1 | UART_CR1_TX_READY_INT; - uart_write_cr1(cr1); - } else { - cr1 = cr1 & 4294959103; - uart_write_cr1(cr1); - } - - var 1 requires_signal = serial_queue_require_signal(tx_queue); - if (transferred && requires_signal) { - serial_queue_cancel_signal(tx_queue); - sddf_notify(TX_CH); - } - - return 0; -} + var tx_queue_handle = SERIAL_TX_QUEUE_HANDLE; -fun rx_return() -{ - var reprocess = true; - var enqueued = false; - var rx_queue = SERIAL_RX_QUEUE; - var rx_capacity = SERIAL_RX_CAPACITY; - var rx_data = SERIAL_RX_DATA; - - while (reprocess) - { + var transferred = false; while (true) { - var 1 ts = uart_read_ts(); - if (ts & UART_TST_RX_FIFO_EMPTY) { break; } - - var 1 is_full = serial_queue_is_full(rx_queue, rx_capacity); - if (is_full) { break; } - - var 1 rxd = uart_read_rxd(); - var c = rxd & 255; - serial_queue_enqueue_char(rx_queue, rx_capacity, rx_data, c); - enqueued = 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 & 255); + transferred = true; + break; } - var 1 ts = uart_read_ts(); - var 1 is_full = serial_queue_is_full(rx_queue, rx_capacity); - - if ((!(ts & UART_TST_RX_FIFO_EMPTY)) && is_full) { - var 1 cr1 = uart_read_cr1(); - cr1 = cr1 & 4294966783; - uart_write_cr1(cr1); - serial_queue_request_signal(rx_queue); - } - - reprocess = false; - - if ((!(ts & UART_TST_RX_FIFO_EMPTY)) && (!is_full)) { - serial_queue_cancel_signal(rx_queue); - var 1 cr1 = uart_read_cr1(); - cr1 = cr1 | UART_CR1_RX_READY_INT; - uart_write_cr1(cr1); - reprocess = 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 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 (enqueued) { - sddf_notify(RX_CH); - } + var require_signal = serial_require_consumer_signal(tx_queue_handle); + if (transferred && require_signal) { + serial_cancel_consumer_signal(tx_queue_handle); + sddf_notify(TX_CH); + } - return 0; + return 0; } -fun handle_irq() +fun rx_return() { - var 1 uart_sr1 = uart_read_sr1(); - var 1 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(); - } + var rx_queue_handle = SERIAL_RX_QUEUE_HANDLE; - if ((uart_cr1 & UART_CR1_TX_READY_INT) && (uart_sr1 & UART_SR1_TX_RDY)) { - tx_provide(); + 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 & 255; + 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 (uart_sr1 & UART_SR1_ABNORMAL) { - uart_write_sr1(UART_SR1_ABNORMAL); + if (enqueued) { + sddf_notify(RX_CH); } - uart_sr1 = uart_read_sr1(); - uart_cr1 = uart_read_cr1(); - } + 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) { + uart_cr1 = uart_read_cr1(); + debug_print(uart_cr1); + uart_write_sr1(uart_cr1 | UART_SR1_ABNORMAL); + } + + uart_sr1 = uart_read_sr1(); + uart_cr1 = uart_read_cr1(); + } - return 0; + return 0; } export fun notified(1 ch) { - if (ch == IRQ_CH) { - handle_irq(); - sddf_deferred_irq_ack(ch); - return 0; - } + if (ch == IRQ_CH) { + handle_irq(); + sddf_deferred_irq_ack(ch); + return 0; + } - if (ch == TX_CH) { - tx_provide(); - return 0; - } + if (ch == TX_CH) { + tx_provide(); + return 0; + } - if (ch == RX_CH) { - var 1 cr1 = uart_read_cr1(); - cr1 = cr1 | UART_CR1_RX_READY_INT; - uart_write_cr1(cr1); + if (ch == RX_CH) { + var cr1 = uart_read_cr1(); + cr1 = cr1 | UART_CR1_RX_READY_INT; + uart_write_cr1(cr1); - rx_return(); - return 0; - } + rx_return(); + return 0; + } - return 0; + return 0; } diff --git a/include/sddf/serial/queue.pnk b/include/sddf/serial/queue.pnk index 3ac70234..644aac9a 100644 --- a/include/sddf/serial/queue.pnk +++ b/include/sddf/serial/queue.pnk @@ -6,159 +6,342 @@ * * 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. */ -fun serial_queue_get_head(1 queue_ptr) +// helper functions +inline fun serial_queue_get_tail(1 queue_handle) { - var head = 0; - !ld32 head, queue_ptr + 4; - return head; + var queue = 0; + !ldw queue, queue_handle; + var tail = 0; + !ld32 tail, queue; + return tail; } -fun serial_queue_get_tail(1 queue_ptr) +inline fun serial_queue_get_head(1 queue_handle) { - var tail = 0; - !ld32 tail, queue_ptr; - return tail; + var queue = 0; + !ldw queue, queue_handle; + var head = 0; + !ld32 head, queue + 4; + return head; } -fun serial_queue_set_head(1 queue_ptr, 1 new_head) +inline fun serial_queue_get_producer_signalled(1 queue_handle) { - !st32 queue_ptr + 4, new_head; - return 0; + var queue = 0; + !ldw queue, queue_handle; + var producer_signalled = 0; + !ld32 producer_signalled, queue + 8; + return producer_signalled; } -fun serial_queue_set_tail(1 queue_ptr, 1 new_tail) +inline fun serial_queue_set_tail(1 queue_handle, 1 new_tail) { - !st32 queue_ptr, new_tail; - return 0; + var queue = 0; + !ldw queue, queue_handle; + !st32 queue, new_tail; + return 0; } -fun serial_queue_is_empty(1 queue_ptr) +inline fun serial_queue_set_head(1 queue_handle, 1 new_head) { - var 1 head = serial_queue_get_head(queue_ptr); - var 1 tail = serial_queue_get_tail(queue_ptr); - return (head == tail); + var queue = 0; + !ldw queue, queue_handle; + !st32 queue + 4, new_head; + return 0; } -fun serial_queue_is_full(1 queue_ptr, 1 capacity) +inline fun serial_queue_set_producer_signalled(1 queue_handle, 1 new_producer_signalled) { - var 1 head = serial_queue_get_head(queue_ptr); - var 1 tail = serial_queue_get_tail(queue_ptr); - var used = tail - head; - return (used == capacity); + var queue = 0; + !ldw queue, queue_handle; + !st32 queue + 8, new_producer_signalled; + return 0; } -fun serial_queue_get_used(1 queue_ptr) +inline fun serial_queue_get_capacity(1 queue_handle) { - var 1 head = serial_queue_get_head(queue_ptr); - var 1 tail = serial_queue_get_tail(queue_ptr); - return (tail - head); + var capacity = 0; + !ld32 capacity, queue_handle + 8; + return capacity; } -fun serial_queue_get_free(1 queue_ptr, 1 capacity) +inline fun serial_queue_get_data_region(1 queue_handle) { - var 1 used = serial_queue_get_used(queue_ptr); - return (capacity - used); + var data_region = 0; + !ldw data_region, queue_handle + 16; + return data_region; } -fun serial_queue_enqueue_char(1 queue_ptr, 1 capacity, 1 data_ptr, 1 c) -{ - var 1 tail = serial_queue_get_tail(queue_ptr); - var pos = tail & (capacity - 1); - var char_addr = data_ptr + pos; - !st8 char_addr, c; - - var new_tail = tail + 1; - serial_queue_set_tail(queue_ptr, new_tail); - return 0; -} +// library functions -fun serial_queue_dequeue_char(1 queue_ptr, 1 capacity, 1 data_ptr) +/** + * 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) { - var 1 head = serial_queue_get_head(queue_ptr); - var pos = head & (capacity - 1); - var char_addr = data_ptr + pos; - var c = 0; - !ld8 c, char_addr; - - var new_head = head + 1; - serial_queue_set_head(queue_ptr, new_head); - return c; + /* 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; } -fun serial_queue_get_signal_state(1 queue_ptr) +/** + * 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 signal_state = 0; - !ld32 signal_state, queue_ptr + 8; - return signal_state; + 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; } -fun serial_queue_set_signal_state(1 queue_ptr, 1 state) +/** + * 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) { - !st32 queue_ptr + 8, state; - return 0; + /* 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; } -fun serial_queue_request_signal(1 queue_ptr) +/** + * 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) { - return serial_queue_set_signal_state(queue_ptr, 0); + /* 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; } -fun serial_queue_cancel_signal(1 queue_ptr) +/** + * 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) { - return serial_queue_set_signal_state(queue_ptr, 1); + 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; } -fun serial_queue_require_signal(1 queue_ptr) +// 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 1 signal_state = serial_queue_get_signal_state(queue_ptr); - return (!signal_state); -} + var head = serial_queue_get_head(queue_handle); -fun serial_require_consumer_signal_client(1 client, 1 cli_queue_base) { - var cli_offset = cli_queue_base + client * 3; - var cli_queue_ptr = pnk_mem(cli_offset); - - var producer_signalled = 0; - !ld32 producer_signalled, cli_queue_ptr + 8; - - return !producer_signalled; -} + 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; -fun serial_cancel_consumer_signal_client(1 client, 1 cli_queue_base) { - var cli_offset = cli_queue_base + client * 3; - var cli_queue_ptr = pnk_mem(cli_offset); - - !st32 cli_queue_ptr + 8, 1; - return 0; + /* 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>; } -fun serial_request_consumer_signal_client(1 client, 1 cli_queue_base) { - var cli_offset = cli_queue_base + client * 3; - var cli_queue_ptr = pnk_mem(cli_offset); - - !st32 cli_queue_ptr + 8, 0; +// 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; } -fun serial_queue_get_length(1 queue_ptr) +/** + * 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) { - var 1 head = serial_queue_get_head(queue_ptr); - var 1 tail = serial_queue_get_tail(queue_ptr); - return (tail - head); + 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; } -fun serial_queue_transfer_byte(1 src_queue_ptr, 1 src_capacity, 1 src_data_ptr, 1 dst_queue_ptr, 1 dst_capacity, 1 dst_data_ptr) +/** + * Producer of the queue requires signalling. + * + * @param queue queue handle of the queue to check. + */ +inline fun serial_require_consumer_signal(1 queue_handle) { - var 1 src_empty = serial_queue_is_empty(src_queue_ptr); - if (src_empty) { return 0; } - - var 1 dst_full = serial_queue_is_full(dst_queue_ptr, dst_capacity); - if (dst_full) { return 0; } - - var 1 byte_val = serial_queue_dequeue_char(src_queue_ptr, src_capacity, src_data_ptr); - serial_queue_enqueue_char(dst_queue_ptr, dst_capacity, dst_data_ptr, byte_val); - return 1; + /* 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/util/pancake_ffi.c b/util/pancake_ffi.c index 17653204..c9f05609 100644 --- a/util/pancake_ffi.c +++ b/util/pancake_ffi.c @@ -40,3 +40,7 @@ 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 index e97974a9..42a36cab 100644 --- a/util/util.pnk +++ b/util/util.pnk @@ -4,40 +4,43 @@ */ /* Helper constants */ -#define MAX_INT32 4294967295 -#define MAX_INT16 65535 -#define MAX_INT8 255 +#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 get_idx_array(addr, idx) lds 1 (addr + idx * @biw) +#define bitwise_not(n) ((n) ^ -1) /* Macros for FFI calls */ #define sddf_irq_ack(channel) \ - @sddf_irq_ack(0,channel,0,0) + @sddf_irq_ack(0, channel, 0, 0) #define sddf_notify(channel) \ - @sddf_notify(0,channel,0,0) + @sddf_notify(0, channel, 0, 0) #define sddf_deferred_irq_ack(channel) \ - @sddf_deferred_irq_ack(0,channel,0,0) + @sddf_deferred_irq_ack(0, channel, 0, 0) #define sddf_deferred_notify(channel) \ - @sddf_deferred_notify(0,channel,0,0) + @sddf_deferred_notify(0, channel, 0, 0) #define cache_clean(start, end) \ - @cache_clean(start,0,end,0) + @cache_clean(start, 0, end, 0) #define cache_clean_and_invalidate(start, end) \ - @cache_clean_and_invalidate(start,0,end,0) + @cache_clean_and_invalidate(start, 0, end, 0) #define THREAD_MEMORY_RELEASE() \ - @THREAD_MEMORY_RELEASE(0,0,0,0) + @THREAD_MEMORY_RELEASE(0, 0, 0, 0) #define THREAD_MEMORY_ACQUIRE() \ - @THREAD_MEMORY_ACQUIRE(0,0,0,0) + @THREAD_MEMORY_ACQUIRE(0, 0, 0, 0) + +#define fence_seq_cst() \ + @fence_seq_cst(0, 0, 0, 0) -#define debug_print(c, clen, a, alen) \ - @debug_print(c,clen,a,alen) +#define debug_print(value) \ + @debug_print(0, __LINE__, 0, value) From 30ce055548fdd5c13f6c941ea186d7dc879dd000 Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 11:59:55 +1000 Subject: [PATCH 06/21] ci: add support for optionally building Pancake components Signed-off-by: Kurt Wu --- ci/build.py | 8 +++++++- drivers/serial/imx/serial_driver.mk | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ci/build.py b/ci/build.py index 3027b353..ce84c77c 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,7 @@ 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 +142,7 @@ 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/drivers/serial/imx/serial_driver.mk b/drivers/serial/imx/serial_driver.mk index b9d6691f..b92afd5c 100644 --- a/drivers/serial/imx/serial_driver.mk +++ b/drivers/serial/imx/serial_driver.mk @@ -13,7 +13,7 @@ DRIVER_PNK = \ ${SDDF}/include/sddf/serial/queue.pnk \ ${SERIAL_DRIVER_DIR}/uart.pnk -# serial_driver.elf: serial/imx/serial_driver.o +ifeq ($(PANCAKE_SERIAL_DRIVER),1) serial_driver.elf: serial/imx/serial_pnk.o serial/imx/serial_driver.o util/pancake_ffi.o $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ @@ -24,8 +24,14 @@ serial/imx/serial_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) -I${SERIAL_DRIVER_DIR}/include -o $@ $< $(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 From f9e73a7ea62c18d478130413361938bdfee6307d Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 12:03:54 +1000 Subject: [PATCH 07/21] drivers/serial/imx: misc Signed-off-by: Kurt Wu --- drivers/serial/imx/serial_driver.mk | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/serial/imx/serial_driver.mk b/drivers/serial/imx/serial_driver.mk index b92afd5c..c5659b44 100644 --- a/drivers/serial/imx/serial_driver.mk +++ b/drivers/serial/imx/serial_driver.mk @@ -8,19 +8,19 @@ 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 -ifeq ($(PANCAKE_SERIAL_DRIVER),1) -serial_driver.elf: serial/imx/serial_pnk.o serial/imx/serial_driver.o util/pancake_ffi.o +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_pnk.o: serial/imx/serial_pnk.S |serial/imx +serial/imx/serial_driver_pnk.o: serial/imx/serial_driver_pnk.S |serial/imx $(CC) -c $(CFLAGS) -o $@ $< -serial/imx/serial_pnk.S: $(DRIVER_PNK) |serial/imx +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) @@ -39,7 +39,7 @@ serial/imx: mkdir -p $@ clean:: - rm -f serial/imx/serial_driver.[do] serial/imx/serial_pnk.[oS] + rm -f serial/imx/serial_driver.[do] serial/imx/serial_driver_pnk.[oS] clobber:: rm -rf serial From e78168d102bcd59fcdc178828dd18f71fadda311 Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 13:12:48 +1000 Subject: [PATCH 08/21] drivers/serial: add meson derivers in Pancake Signed-off-by: Kurt Wu --- drivers/serial/imx/uart.pnk | 10 +- drivers/serial/meson/serial_driver.mk | 21 ++- drivers/serial/meson/uart.c | 54 ++++++ drivers/serial/meson/uart.pnk | 228 ++++++++++++++++++++++++++ 4 files changed, 307 insertions(+), 6 deletions(-) create mode 100644 drivers/serial/meson/uart.pnk diff --git a/drivers/serial/imx/uart.pnk b/drivers/serial/imx/uart.pnk index 12751372..bc9c39e6 100644 --- a/drivers/serial/imx/uart.pnk +++ b/drivers/serial/imx/uart.pnk @@ -122,7 +122,7 @@ export fun tx_provide() if (res.0) { break; } - uart_write_txd(res.1 & 255); + uart_write_txd(res.1 & MAX_UINT8); transferred = true; break; } @@ -171,21 +171,21 @@ fun rx_return() } var rxd = uart_read_rxd(); - var c = rxd & 255; + 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); + 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(); @@ -223,8 +223,8 @@ fun handle_irq() } if (uart_sr1 & UART_SR1_ABNORMAL) { + debug_print(uart_sr1); uart_cr1 = uart_read_cr1(); - debug_print(uart_cr1); uart_write_sr1(uart_cr1 | UART_SR1_ABNORMAL); } diff --git a/drivers/serial/meson/serial_driver.mk b/drivers/serial/meson/serial_driver.mk index c17780a6..461f5968 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 a28afb1b..86bb4c33 100644 --- a/drivers/serial/meson/uart.c +++ b/drivers/serial/meson/uart.c @@ -15,6 +15,38 @@ __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 +103,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 +176,7 @@ static void handle_irq(void) uart_cr = uart_regs->cr; } } +#endif /* PANCAKE_SERIAL_DRIVER */ static void uart_setup(void) { @@ -203,8 +237,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 +272,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 00000000..7ccefce9 --- /dev/null +++ b/drivers/serial/meson/uart.pnk @@ -0,0 +1,228 @@ +/* + * 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); + } + + var require_signal = serial_require_consumer_signal(tx_queue_handle); + if (transferred && 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); + return 0; + } + + if (ch == TX_CH) { + tx_provide(); + return 0; + } + + if (ch == RX_CH) { + var cr = uart_read_cr(); + cr = cr | AML_UART_RX_INT_EN; + uart_write_cr(cr); + + rx_return(); + return 0; + } + + return 0; +} From a7556cf54a1f87fae5841ed6b8e0597938d066d9 Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 13:27:48 +1000 Subject: [PATCH 09/21] drivers/serial: fix Pancake implementation of `notified()` Signed-off-by: Kurt Wu --- drivers/serial/imx/uart.pnk | 29 ++++++++++++++--------------- drivers/serial/meson/uart.pnk | 29 ++++++++++++++--------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/drivers/serial/imx/uart.pnk b/drivers/serial/imx/uart.pnk index bc9c39e6..d0ae7c8f 100644 --- a/drivers/serial/imx/uart.pnk +++ b/drivers/serial/imx/uart.pnk @@ -241,21 +241,20 @@ export fun notified(1 ch) if (ch == IRQ_CH) { handle_irq(); sddf_deferred_irq_ack(ch); - return 0; - } - - if (ch == TX_CH) { - tx_provide(); - return 0; - } - - if (ch == RX_CH) { - var cr1 = uart_read_cr1(); - cr1 = cr1 | UART_CR1_RX_READY_INT; - uart_write_cr1(cr1); - - rx_return(); - return 0; + } 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/uart.pnk b/drivers/serial/meson/uart.pnk index 7ccefce9..8e05703c 100644 --- a/drivers/serial/meson/uart.pnk +++ b/drivers/serial/meson/uart.pnk @@ -207,21 +207,20 @@ export fun notified(1 ch) if (ch == IRQ_CH) { handle_irq(); sddf_deferred_irq_ack(ch); - return 0; - } - - if (ch == TX_CH) { - tx_provide(); - return 0; - } - - if (ch == RX_CH) { - var cr = uart_read_cr(); - cr = cr | AML_UART_RX_INT_EN; - uart_write_cr(cr); - - rx_return(); - return 0; + } 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; From 147b158e745c8de9babbe4540790008272c15388 Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 16:44:28 +1000 Subject: [PATCH 10/21] drivers/serial: add ns16550a drivers in Pancake Signed-off-by: Kurt Wu --- drivers/serial/imx/uart.pnk | 12 +- drivers/serial/meson/uart.pnk | 12 +- drivers/serial/ns16550a/serial_driver.mk | 22 +- drivers/serial/ns16550a/uart.c | 64 ++++ drivers/serial/ns16550a/uart.pnk | 373 +++++++++++++++++++++++ util/util.pnk | 20 +- 6 files changed, 476 insertions(+), 27 deletions(-) create mode 100644 drivers/serial/ns16550a/uart.pnk diff --git a/drivers/serial/imx/uart.pnk b/drivers/serial/imx/uart.pnk index d0ae7c8f..6a3687a5 100644 --- a/drivers/serial/imx/uart.pnk +++ b/drivers/serial/imx/uart.pnk @@ -111,8 +111,7 @@ export fun tx_provide() var tx_queue_handle = SERIAL_TX_QUEUE_HANDLE; var transferred = false; - while (true) - { + while (true) { var ts = uart_read_ts(); if (ts & UART_TST_TX_FIFO_FULL) { break; @@ -155,10 +154,8 @@ fun rx_return() var reprocess = true; var enqueued = false; - while (reprocess) - { - while (true) - { + while (reprocess) { + while (true) { var ts = uart_read_ts(); if (ts & UART_TST_RX_FIFO_EMPTY) { break; @@ -212,8 +209,7 @@ 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))) - { + ((uart_cr1 & UART_CR1_TX_READY_INT) && (uart_sr1 & UART_SR1_TX_RDY))) { if (RX_ENABLED && (uart_sr1 & UART_SR1_RX_RDY)) { rx_return(); } diff --git a/drivers/serial/meson/uart.pnk b/drivers/serial/meson/uart.pnk index 8e05703c..17298edc 100644 --- a/drivers/serial/meson/uart.pnk +++ b/drivers/serial/meson/uart.pnk @@ -79,8 +79,7 @@ export fun tx_provide() var tx_queue_handle = SERIAL_TX_QUEUE_HANDLE; var transferred = false; - while (true) - { + while (true) { var sr = uart_read_sr(); if (sr & AML_UART_TX_FULL) { break; @@ -122,10 +121,8 @@ fun rx_return() var reprocess = true; var enqueued = false; - while (reprocess) - { - while (true) - { + while (reprocess) { + while (true) { var sr = uart_read_sr(); if (sr & AML_UART_RX_EMPTY) { break; @@ -179,8 +176,7 @@ 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)))) - { + ((uart_cr & AML_UART_TX_INT_EN) && (!(uart_sr & AML_UART_TX_FULL)))) { if (RX_ENABLED && !(uart_sr & AML_UART_RX_EMPTY)) { rx_return(); } diff --git a/drivers/serial/ns16550a/serial_driver.mk b/drivers/serial/ns16550a/serial_driver.mk index 41f76b62..d110a3c2 100644 --- a/drivers/serial/ns16550a/serial_driver.mk +++ b/drivers/serial/ns16550a/serial_driver.mk @@ -8,11 +8,31 @@ 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 > /workspaces/sddf/1.pnk + 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 +40,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 e00fec3e..a4ec18dd 100644 --- a/drivers/serial/ns16550a/uart.c +++ b/drivers/serial/ns16550a/uart.c @@ -17,6 +17,38 @@ __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 +69,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 +120,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 +150,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 +225,7 @@ static void handle_irq(void) tx_provide(); } } +#endif void init(void) { @@ -241,8 +277,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 +319,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 00000000..735e251f --- /dev/null +++ b/drivers/serial/ns16550a/uart.pnk @@ -0,0 +1,373 @@ +/* + * 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); + } + + var require_signal = serial_require_consumer_signal(tx_queue_handle); + if (transferred && 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/util/util.pnk b/util/util.pnk index 42a36cab..ddbe645e 100644 --- a/util/util.pnk +++ b/util/util.pnk @@ -16,31 +16,31 @@ /* Macros for FFI calls */ #define sddf_irq_ack(channel) \ - @sddf_irq_ack(0, channel, 0, 0) + @sddf_irq_ack(0, channel, 0, 0) #define sddf_notify(channel) \ - @sddf_notify(0, channel, 0, 0) + @sddf_notify(0, channel, 0, 0) #define sddf_deferred_irq_ack(channel) \ - @sddf_deferred_irq_ack(0, channel, 0, 0) + @sddf_deferred_irq_ack(0, channel, 0, 0) #define sddf_deferred_notify(channel) \ - @sddf_deferred_notify(0, channel, 0, 0) + @sddf_deferred_notify(0, channel, 0, 0) #define cache_clean(start, end) \ - @cache_clean(start, 0, end, 0) + @cache_clean(start, 0, end, 0) #define cache_clean_and_invalidate(start, end) \ - @cache_clean_and_invalidate(start, 0, end, 0) + @cache_clean_and_invalidate(start, 0, end, 0) #define THREAD_MEMORY_RELEASE() \ - @THREAD_MEMORY_RELEASE(0, 0, 0, 0) + @THREAD_MEMORY_RELEASE(0, 0, 0, 0) #define THREAD_MEMORY_ACQUIRE() \ - @THREAD_MEMORY_ACQUIRE(0, 0, 0, 0) + @THREAD_MEMORY_ACQUIRE(0, 0, 0, 0) #define fence_seq_cst() \ - @fence_seq_cst(0, 0, 0, 0) + @fence_seq_cst(0, 0, 0, 0) #define debug_print(value) \ - @debug_print(0, __LINE__, 0, value) + @debug_print(0, value, 0, __LINE__) From 0b06482e7d81baeef734795e70b0d997c3ad3c70 Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 17:37:07 +1000 Subject: [PATCH 11/21] drivers/serial: add arm driver in Pancake Signed-off-by: Kurt Wu --- drivers/serial/arm/serial_driver.mk | 21 +- drivers/serial/arm/uart.c | 54 +++++ drivers/serial/arm/uart.pnk | 258 +++++++++++++++++++++++ drivers/serial/imx/uart.pnk | 10 +- drivers/serial/meson/uart.pnk | 10 +- drivers/serial/ns16550a/serial_driver.mk | 1 - drivers/serial/ns16550a/uart.pnk | 10 +- 7 files changed, 350 insertions(+), 14 deletions(-) create mode 100644 drivers/serial/arm/uart.pnk diff --git a/drivers/serial/arm/serial_driver.mk b/drivers/serial/arm/serial_driver.mk index 45ff3278..3f1ccc33 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 07f381d8..43f785a9 100644 --- a/drivers/serial/arm/uart.c +++ b/drivers/serial/arm/uart.c @@ -15,6 +15,38 @@ __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 +71,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 +137,7 @@ static void handle_irq(void) uart_int_reg = uart_regs->mis; } } +#endif /* PANCAKE_SERIAL_DRIVER */ static void uart_setup(void) { @@ -165,8 +199,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 +234,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 00000000..6d37e5b3 --- /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/uart.pnk b/drivers/serial/imx/uart.pnk index 6a3687a5..2468d6aa 100644 --- a/drivers/serial/imx/uart.pnk +++ b/drivers/serial/imx/uart.pnk @@ -139,10 +139,12 @@ export fun tx_provide() uart_write_cr1(cr1); } - var require_signal = serial_require_consumer_signal(tx_queue_handle); - if (transferred && require_signal) { - serial_cancel_consumer_signal(tx_queue_handle); - sddf_notify(TX_CH); + 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; diff --git a/drivers/serial/meson/uart.pnk b/drivers/serial/meson/uart.pnk index 17298edc..62fa35bc 100644 --- a/drivers/serial/meson/uart.pnk +++ b/drivers/serial/meson/uart.pnk @@ -106,10 +106,12 @@ export fun tx_provide() uart_write_cr(cr); } - var require_signal = serial_require_consumer_signal(tx_queue_handle); - if (transferred && require_signal) { - serial_cancel_consumer_signal(tx_queue_handle); - sddf_notify(TX_CH); + 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; diff --git a/drivers/serial/ns16550a/serial_driver.mk b/drivers/serial/ns16550a/serial_driver.mk index d110a3c2..542ad8ac 100644 --- a/drivers/serial/ns16550a/serial_driver.mk +++ b/drivers/serial/ns16550a/serial_driver.mk @@ -21,7 +21,6 @@ serial/ns16550a/serial_driver_pnk.o: serial/ns16550a/serial_driver_pnk.S |serial $(CC) -c $(CFLAGS) -o $@ $< serial/ns16550a/serial_driver_pnk.S: $(DRIVER_PNK) |serial/ns16550a - cat $(DRIVER_PNK) | cpp -P > /workspaces/sddf/1.pnk cat $(DRIVER_PNK) | cpp -P | $(PANCAKE_COMPILER) $(PANCAKE_FLAGS) > $@ serial/ns16550a/serial_driver.o: ${SERIAL_DRIVER_DIR}/uart.c |serial/ns16550a $(SDDF_LIBC_INCLUDE) diff --git a/drivers/serial/ns16550a/uart.pnk b/drivers/serial/ns16550a/uart.pnk index 735e251f..7ba46a7f 100644 --- a/drivers/serial/ns16550a/uart.pnk +++ b/drivers/serial/ns16550a/uart.pnk @@ -255,10 +255,12 @@ fun tx_provide() uart_write_reg(UART_IER, ier); } - var require_signal = serial_require_consumer_signal(tx_queue_handle); - if (transferred && require_signal) { - serial_cancel_consumer_signal(tx_queue_handle); - sddf_notify(TX_CH); + 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; From 606ef76b0b762645f37e26ae49daa1cdda6efef7 Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 18:03:48 +1000 Subject: [PATCH 12/21] drivers/serial: add zynqmp driver in Pancake Signed-off-by: Kurt Wu --- drivers/serial/zynqmp/serial_driver.mk | 21 +- drivers/serial/zynqmp/uart.c | 54 +++++ drivers/serial/zynqmp/uart.pnk | 265 +++++++++++++++++++++++++ 3 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 drivers/serial/zynqmp/uart.pnk diff --git a/drivers/serial/zynqmp/serial_driver.mk b/drivers/serial/zynqmp/serial_driver.mk index 78eb9d05..5cc12dca 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 0f24a337..4ff5c9da 100644 --- a/drivers/serial/zynqmp/uart.c +++ b/drivers/serial/zynqmp/uart.c @@ -25,6 +25,38 @@ __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 +67,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 +163,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 +306,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 +340,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 00000000..3b857fe8 --- /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; +} From 6e37f969bb55fbb4962e5467a3aaa6355a48c979 Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 18:53:40 +1000 Subject: [PATCH 13/21] drivers/pc99: add pc99 driver in Pancake Signed-off-by: Kurt Wu --- drivers/serial/pc99/serial_driver.mk | 19 +++ drivers/serial/pc99/uart.c | 51 ++++++++ drivers/serial/pc99/uart.pnk | 166 +++++++++++++++++++++++++++ util/pancake_ffi.c | 12 ++ util/util.pnk | 6 + 5 files changed, 254 insertions(+) create mode 100644 drivers/serial/pc99/uart.pnk diff --git a/drivers/serial/pc99/serial_driver.mk b/drivers/serial/pc99/serial_driver.mk index f57daf6c..a1861ba1 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 $@ diff --git a/drivers/serial/pc99/uart.c b/drivers/serial/pc99/uart.c index 4687bbd7..7b90713d 100644 --- a/drivers/serial/pc99/uart.c +++ b/drivers/serial/pc99/uart.c @@ -19,6 +19,38 @@ __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 +96,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 +106,7 @@ int rx_ready(void) { return read(SERIAL_LSR) & SERIAL_LSR_DATA_READY; } +#endif /* PANCAKE_SERIAL_DRIVER */ void init(void) { @@ -102,8 +136,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 +206,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 00000000..16cf9545 --- /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/util/pancake_ffi.c b/util/pancake_ffi.c index c9f05609..31d3afb0 100644 --- a/util/pancake_ffi.c +++ b/util/pancake_ffi.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -20,6 +21,17 @@ void ffisddf_deferred_notify(unsigned char *c, long clen, unsigned char *a, long 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); } diff --git a/util/util.pnk b/util/util.pnk index ddbe645e..d00856ab 100644 --- a/util/util.pnk +++ b/util/util.pnk @@ -27,6 +27,12 @@ #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) From ce5179c5b36f4ce03b07b6e8a1455d2aa004429e Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 19:32:01 +1000 Subject: [PATCH 14/21] ci: add Pancake build example Signed-off-by: Kurt Wu --- .github/workflows/examples.yaml | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml index 54deab4e..856d6e91 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] From 61485627ca50fa5d9ee416dc3e6eb5b9ae9dd6c9 Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 19:49:54 +1000 Subject: [PATCH 15/21] trim trailing whitespaces Signed-off-by: Kurt Wu --- include/sddf/serial/queue.pnk | 2 +- util/pancake_ffi.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/sddf/serial/queue.pnk b/include/sddf/serial/queue.pnk index 644aac9a..17f3a1f0 100644 --- a/include/sddf/serial/queue.pnk +++ b/include/sddf/serial/queue.pnk @@ -3,7 +3,7 @@ * 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. * diff --git a/util/pancake_ffi.c b/util/pancake_ffi.c index 31d3afb0..a8fae016 100644 --- a/util/pancake_ffi.c +++ b/util/pancake_ffi.c @@ -28,7 +28,7 @@ void ffimicrokit_x86_ioport_write_8(unsigned char *c, long clen, unsigned char * 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; + *(seL4_Uint8 *)c = ret; } #endif From fe3ac7078ed52a517e395dad2f4746234a0c88cf Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 19:52:42 +1000 Subject: [PATCH 16/21] util/pancake_ffi.c: add license Signed-off-by: Kurt Wu --- util/pancake_ffi.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/util/pancake_ffi.c b/util/pancake_ffi.c index a8fae016..09341370 100644 --- a/util/pancake_ffi.c +++ b/util/pancake_ffi.c @@ -1,3 +1,8 @@ +/* + * Copyright 2026, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + #include #include #include From 680a757deb90ea06c925508b9400182ae086812d Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 19:53:36 +1000 Subject: [PATCH 17/21] ci/build.py: fix style Signed-off-by: Kurt Wu --- ci/build.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ci/build.py b/ci/build.py index ce84c77c..bef998fa 100755 --- a/ci/build.py +++ b/ci/build.py @@ -40,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, + ] + + pancake_args, check=True, ) @@ -142,7 +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") + parser.add_argument( + "--pancake", action="store_true", help="Use Pancake implementations" + ) filters = parser.add_argument_group(title="filters") filters.add_argument( From ce467dcb7891c402b05c37cf9911c54dae622b61 Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 19:59:50 +1000 Subject: [PATCH 18/21] fix style Signed-off-by: Kurt Wu --- drivers/serial/arm/uart.c | 23 +++++++++++------- drivers/serial/imx/uart.c | 23 +++++++++++------- drivers/serial/meson/uart.c | 23 +++++++++++------- drivers/serial/ns16550a/uart.c | 23 +++++++++++------- drivers/serial/pc99/uart.c | 23 +++++++++++------- drivers/serial/zynqmp/uart.c | 23 +++++++++++------- util/pancake_ffi.c | 44 +++++++++++++++++++++------------- 7 files changed, 112 insertions(+), 70 deletions(-) diff --git a/drivers/serial/arm/uart.c b/drivers/serial/arm/uart.c index 43f785a9..6b503351 100644 --- a/drivers/serial/arm/uart.c +++ b/drivers/serial/arm/uart.c @@ -23,22 +23,27 @@ extern void *cml_stackend; extern void cml_main(void); -void cml_exit(int arg) { +void cml_exit(int arg) +{ microkit_dbg_puts("ERROR! We should not be getting here\n"); } -void cml_err(int arg) { +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"); + 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() { +void cml_clear() +{ microkit_dbg_puts("Trying to clear cache\n"); } -void init_pancake_mem() { +void init_pancake_mem() +{ unsigned long cml_heap_sz = 1024 * 10; unsigned long cml_stack_sz = 1024 * 10; cml_heap = cml_memory; @@ -203,14 +208,14 @@ void init(void) #ifdef PANCAKE_SERIAL_DRIVER init_pancake_mem(); - uintptr_t *pnk_mem = (uintptr_t *) cml_heap; + uintptr_t *pnk_mem = (uintptr_t *)cml_heap; - pnk_mem[0] = (uintptr_t) uart_regs; + 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[4] = (uintptr_t)&rx_queue_handle; + pnk_mem[5] = (uintptr_t)&tx_queue_handle; pnk_mem[1024] = config.rx_enabled; cml_main(); diff --git a/drivers/serial/imx/uart.c b/drivers/serial/imx/uart.c index 15cce4d1..e0dbfe7a 100644 --- a/drivers/serial/imx/uart.c +++ b/drivers/serial/imx/uart.c @@ -24,22 +24,27 @@ extern void *cml_stackend; extern void cml_main(void); -void cml_exit(int arg) { +void cml_exit(int arg) +{ microkit_dbg_puts("ERROR! We should not be getting here\n"); } -void cml_err(int arg) { +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"); + 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() { +void cml_clear() +{ microkit_dbg_puts("Trying to clear cache\n"); } -void init_pancake_mem() { +void init_pancake_mem() +{ unsigned long cml_heap_sz = 1024 * 10; unsigned long cml_stack_sz = 1024 * 10; cml_heap = cml_memory; @@ -208,14 +213,14 @@ void init(void) #ifdef PANCAKE_SERIAL_DRIVER init_pancake_mem(); - uintptr_t *pnk_mem = (uintptr_t *) cml_heap; + uintptr_t *pnk_mem = (uintptr_t *)cml_heap; - pnk_mem[0] = (uintptr_t) uart_regs; + 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[4] = (uintptr_t)&rx_queue_handle; + pnk_mem[5] = (uintptr_t)&tx_queue_handle; pnk_mem[1024] = config.rx_enabled; cml_main(); diff --git a/drivers/serial/meson/uart.c b/drivers/serial/meson/uart.c index 86bb4c33..620f30bb 100644 --- a/drivers/serial/meson/uart.c +++ b/drivers/serial/meson/uart.c @@ -23,22 +23,27 @@ extern void *cml_stackend; extern void cml_main(void); -void cml_exit(int arg) { +void cml_exit(int arg) +{ microkit_dbg_puts("ERROR! We should not be getting here\n"); } -void cml_err(int arg) { +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"); + 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() { +void cml_clear() +{ microkit_dbg_puts("Trying to clear cache\n"); } -void init_pancake_mem() { +void init_pancake_mem() +{ unsigned long cml_heap_sz = 1024 * 10; unsigned long cml_stack_sz = 1024 * 10; cml_heap = cml_memory; @@ -241,14 +246,14 @@ void init(void) #ifdef PANCAKE_SERIAL_DRIVER init_pancake_mem(); - uintptr_t *pnk_mem = (uintptr_t *) cml_heap; + uintptr_t *pnk_mem = (uintptr_t *)cml_heap; - pnk_mem[0] = (uintptr_t) uart_regs; + 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[4] = (uintptr_t)&rx_queue_handle; + pnk_mem[5] = (uintptr_t)&tx_queue_handle; pnk_mem[1024] = config.rx_enabled; cml_main(); diff --git a/drivers/serial/ns16550a/uart.c b/drivers/serial/ns16550a/uart.c index a4ec18dd..f36401f7 100644 --- a/drivers/serial/ns16550a/uart.c +++ b/drivers/serial/ns16550a/uart.c @@ -25,22 +25,27 @@ extern void *cml_stackend; extern void cml_main(void); -void cml_exit(int arg) { +void cml_exit(int arg) +{ microkit_dbg_puts("ERROR! We should not be getting here\n"); } -void cml_err(int arg) { +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"); + 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() { +void cml_clear() +{ microkit_dbg_puts("Trying to clear cache\n"); } -void init_pancake_mem() { +void init_pancake_mem() +{ unsigned long cml_heap_sz = 1024 * 10; unsigned long cml_stack_sz = 1024 * 10; cml_heap = cml_memory; @@ -281,14 +286,14 @@ void init(void) #ifdef PANCAKE_SERIAL_DRIVER init_pancake_mem(); - uintptr_t *pnk_mem = (uintptr_t *) cml_heap; + uintptr_t *pnk_mem = (uintptr_t *)cml_heap; - pnk_mem[0] = (uintptr_t) uart_base; + 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[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; diff --git a/drivers/serial/pc99/uart.c b/drivers/serial/pc99/uart.c index 7b90713d..d911e940 100644 --- a/drivers/serial/pc99/uart.c +++ b/drivers/serial/pc99/uart.c @@ -27,22 +27,27 @@ extern void *cml_stackend; extern void cml_main(void); -void cml_exit(int arg) { +void cml_exit(int arg) +{ microkit_dbg_puts("ERROR! We should not be getting here\n"); } -void cml_err(int arg) { +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"); + 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() { +void cml_clear() +{ microkit_dbg_puts("Trying to clear cache\n"); } -void init_pancake_mem() { +void init_pancake_mem() +{ unsigned long cml_heap_sz = 1024 * 10; unsigned long cml_stack_sz = 1024 * 10; cml_heap = cml_memory; @@ -140,12 +145,12 @@ void init(void) #ifdef PANCAKE_SERIAL_DRIVER init_pancake_mem(); - uintptr_t *pnk_mem = (uintptr_t *) cml_heap; -; + 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; + pnk_mem[4] = (uintptr_t)&rx_queue_handle; + pnk_mem[5] = (uintptr_t)&tx_queue_handle; cml_main(); #endif /* PANCAKE_SERIAL_DRIVER */ diff --git a/drivers/serial/zynqmp/uart.c b/drivers/serial/zynqmp/uart.c index 4ff5c9da..74015d4b 100644 --- a/drivers/serial/zynqmp/uart.c +++ b/drivers/serial/zynqmp/uart.c @@ -33,22 +33,27 @@ extern void *cml_stackend; extern void cml_main(void); -void cml_exit(int arg) { +void cml_exit(int arg) +{ microkit_dbg_puts("ERROR! We should not be getting here\n"); } -void cml_err(int arg) { +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"); + 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() { +void cml_clear() +{ microkit_dbg_puts("Trying to clear cache\n"); } -void init_pancake_mem() { +void init_pancake_mem() +{ unsigned long cml_heap_sz = 1024 * 10; unsigned long cml_stack_sz = 1024 * 10; cml_heap = cml_memory; @@ -310,14 +315,14 @@ void init(void) #ifdef PANCAKE_SERIAL_DRIVER init_pancake_mem(); - uintptr_t *pnk_mem = (uintptr_t *) cml_heap; + uintptr_t *pnk_mem = (uintptr_t *)cml_heap; - pnk_mem[0] = (uintptr_t) uart_base; + 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[4] = (uintptr_t)&rx_queue_handle; + pnk_mem[5] = (uintptr_t)&tx_queue_handle; pnk_mem[1024] = config.rx_enabled; cml_main(); diff --git a/util/pancake_ffi.c b/util/pancake_ffi.c index 09341370..18a85069 100644 --- a/util/pancake_ffi.c +++ b/util/pancake_ffi.c @@ -10,54 +10,66 @@ #include #include -void ffisddf_irq_ack(unsigned char *c, long clen, unsigned char *a, long alen) { +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) { +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) { +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) { +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_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); +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(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 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) { +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) { +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) { +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) { +void ffifence_seq_cst(unsigned char *c, long clen, unsigned char *a, long alen) +{ __atomic_thread_fence(__ATOMIC_SEQ_CST); } From c11020ed63425cd04ca93a2dafb35e9083e935aa Mon Sep 17 00:00:00 2001 From: Kurt Wu Date: Tue, 18 Aug 2026 20:06:42 +1000 Subject: [PATCH 19/21] drivers/serial/pc99: fix make clean Signed-off-by: Kurt Wu --- drivers/serial/pc99/serial_driver.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/serial/pc99/serial_driver.mk b/drivers/serial/pc99/serial_driver.mk index a1861ba1..b8125e81 100644 --- a/drivers/serial/pc99/serial_driver.mk +++ b/drivers/serial/pc99/serial_driver.mk @@ -42,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 From 818d6f38bb1fa31d945d9bd054eac389ba437dc2 Mon Sep 17 00:00:00 2001 From: Julia Vassiliki Date: Wed, 19 Aug 2026 11:08:24 +1000 Subject: [PATCH 20/21] nix: add pancake Signed-off-by: Julia Vassiliki --- .github/workflows/examples.yaml | 25 +++++++++++++++++ ci/cakeml.nix | 49 +++++++++++++++++++++++++++++++++ flake.nix | 4 +++ 3 files changed, 78 insertions(+) create mode 100644 ci/cakeml.nix diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml index 856d6e91..dfa93865 100644 --- a/.github/workflows/examples.yaml +++ b/.github/workflows/examples.yaml @@ -110,6 +110,31 @@ jobs: ci_build/**/sel4_32.elf if-no-files-found: error + build_pancake_linux_x86_64_nix: + name: Build (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/cakeml.nix b/ci/cakeml.nix new file mode 100644 index 00000000..2722ed04 --- /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/flake.nix b/flake.nix index cd2364ad..b89e85fa 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 From e6b79e7baafd2c98ecf7e63d2527085fec8e449e Mon Sep 17 00:00:00 2001 From: Julia Vassiliki Date: Wed, 19 Aug 2026 11:09:21 +1000 Subject: [PATCH 21/21] nix: fix name of ci job Signed-off-by: Julia Vassiliki --- .github/workflows/examples.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml index dfa93865..23effb60 100644 --- a/.github/workflows/examples.yaml +++ b/.github/workflows/examples.yaml @@ -111,7 +111,7 @@ jobs: if-no-files-found: error build_pancake_linux_x86_64_nix: - name: Build (Linux x86-64 Nix) + name: Build Pancake (Linux x86-64 Nix) runs-on: [self-hosted, Linux, X64] steps: - name: Checkout repository