From 3f49a57b99e7fe5aafa73e70ed146d98b1ae174c Mon Sep 17 00:00:00 2001 From: "Luke T. Shumaker" Date: Mon, 24 Feb 2025 22:54:30 -0700 Subject: libhw: rp2040_hwspi: Use interrupts instead of busy-polling --- build-aux/stack.c.gen | 5 +++ libhw/CMakeLists.txt | 1 + libhw/rp2040_dma.c | 56 +++++++++++++++++++++++++++++++ libhw/rp2040_dma.h | 23 ++++++++++--- libhw/rp2040_hwspi.c | 21 +++++++----- libhw/rp2040_include/libhw/rp2040_hwspi.h | 2 ++ 6 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 libhw/rp2040_dma.c diff --git a/build-aux/stack.c.gen b/build-aux/stack.c.gen index 66d837a..60f51fe 100755 --- a/build-aux/stack.c.gen +++ b/build-aux/stack.c.gen @@ -505,6 +505,7 @@ class LibHWPlugin: "hostclock_handle_sig_alarm", "hostnet_handle_sig_io", "gpioirq_handler", + "dmairq_handler", ] def extra_nodes(self) -> typing.Collection[Node]: @@ -527,6 +528,10 @@ class LibHWPlugin: return [ "w5500_intrhandler", ], False + if "/rp2040_dmairq.c:" in loc and "handler->fn" in line: + return [ + "rp2040_hwspi_intrhandler", + ], False return None def skip_call(self, chain: list[str], call: str) -> bool: diff --git a/libhw/CMakeLists.txt b/libhw/CMakeLists.txt index bd92e04..242a3fa 100644 --- a/libhw/CMakeLists.txt +++ b/libhw/CMakeLists.txt @@ -14,6 +14,7 @@ if (PICO_PLATFORM STREQUAL "rp2040") libcr_ipc ) target_sources(libhw INTERFACE + rp2040_dma.c rp2040_gpioirq.c rp2040_hwspi.c rp2040_hwtimer.c diff --git a/libhw/rp2040_dma.c b/libhw/rp2040_dma.c new file mode 100644 index 0000000..dfbf136 --- /dev/null +++ b/libhw/rp2040_dma.c @@ -0,0 +1,56 @@ +/* libhw/rp2040_dma.c - Utilities for sharing the DMA IRQs + * + * Copyright (C) 2025 Luke T. Shumaker + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include + +#include /* for irq_set_exclusive_handler() */ + +#include "rp2040_dma.h" + +struct dmairq_handler_entry { + dmairq_handler_t fn; + void *arg; +}; +struct dmairq_handler_entry dmairq_handlers[NUM_DMA_CHANNELS] = {0}; + +bool dmairq_initialized[NUM_DMA_IRQS] = {0}; + +static void dmairq_handler(void) { + enum dmairq irq = __get_current_exception() - VTABLE_FIRST_IRQ; + size_t irq_idx = irq - DMAIRQ_0; + assert(irq_idx < NUM_DMA_IRQS); + + uint32_t regval = dma_hw->irq_ctrl[irq_idx].ints; + for (uint channel = 0; channel < NUM_DMA_CHANNELS; channel++) { + if (regval & 1u<fn) + handler->fn(handler->arg, irq, channel); + } + } + /* acknowledge irq */ + dma_hw->intr = regval; +} + +void dmairq_set_and_enable_exclusive_handler(enum dmairq irq, uint channel, dmairq_handler_t fn, void *arg) { + assert(irq == DMAIRQ_0 || irq == DMAIRQ_1); + assert(channel < NUM_DMA_CHANNELS); + assert(fn); + + assert(dmairq_handlers[channel].fn == NULL); + + dmairq_handlers[channel].fn = fn; + dmairq_handlers[channel].arg = arg; + + size_t irq_idx = irq - DMAIRQ_0; + hw_set_bits(&dma_hw->irq_ctrl[irq_idx].inte, 1u<ch[channel].al1_ctrl & DMA_CH0_CTRL_TRIG_BUSY_BITS; -} - /* Our own code ***************************************************************/ +enum dmairq { + DMAIRQ_0 = DMA_IRQ_0, + DMAIRQ_1 = DMA_IRQ_1, +}; + +typedef void (*dmairq_handler_t)(void *arg, enum dmairq irq, uint channel); + +/** + * Register `fn(arg, ...)` to be called when `channel` completes or + * has a NULL trigger (depending on the channel's configuration). + * + * Your handler does not need to acknowledge the IRQ; that will be + * done for you after your handler is called. + * + * It is illegal to enable the same channel on more than one IRQ. + */ +void dmairq_set_and_enable_exclusive_handler(enum dmairq irq, uint channel, dmairq_handler_t fn, void *arg); + #define DMA_CTRL_ENABLE (1<<0) #define DMA_CTRL_HI_PRIO (1<<1) #define DMA_CTRL_DATA_SIZE(sz) ((sz)<<2) diff --git a/libhw/rp2040_hwspi.c b/libhw/rp2040_hwspi.c index 1c4e096..f747b1e 100644 --- a/libhw/rp2040_hwspi.c +++ b/libhw/rp2040_hwspi.c @@ -33,6 +33,12 @@ LO_IMPLEMENTATION_C(io_duplex_readwriter, struct rp2040_hwspi, rp2040_hwspi, static) LO_IMPLEMENTATION_C(spi, struct rp2040_hwspi, rp2040_hwspi, static) +static void rp2040_hwspi_intrhandler(void *_self, enum dmairq LM_UNUSED(irq), uint LM_UNUSED(channel)) { + struct rp2040_hwspi *self = _self; + gpio_put(self->pin_cs, 1); + cr_sema_signal_from_intrhandler(&self->sema); +} + #define assert_4distinct(a, b, c, d) \ assert(a != b); \ assert(a != c); \ @@ -124,6 +130,10 @@ void _rp2040_hwspi_init(struct rp2040_hwspi *self, self->dma_tx_data = dma3; self->dma_rx_data = dma4; self->dead_until_ns = 0; + self->sema = (cr_sema_t){0}; + + /* Initialize the interrupt handler. */ + dmairq_set_and_enable_exclusive_handler(DMAIRQ_0, self->dma_rx_data, rp2040_hwspi_intrhandler, self); } static void rp2040_hwspi_readwritev(struct rp2040_hwspi *self, const struct duplex_iovec *iov, int iovcnt) { @@ -225,15 +235,10 @@ static void rp2040_hwspi_readwritev(struct rp2040_hwspi *self, const struct dupl uint64_t now = LO_CALL(bootclock, get_time_ns); if (now < self->dead_until_ns) sleep_until_ns(self->dead_until_ns); - /* TODO: Use interrupts instead of busy-polling. */ + bool saved = cr_save_and_disable_interrupts(); gpio_put(self->pin_cs, 0); dma_hw->multi_channel_trigger = (1u<dma_tx_ctrl) | (1u<dma_rx_ctrl); - while (dma_channel_is_busy(self->dma_tx_ctrl) - || dma_channel_is_busy(self->dma_tx_data) - || dma_channel_is_busy(self->dma_rx_ctrl) - || dma_channel_is_busy(self->dma_rx_data)) - tight_loop_contents(); - __compiler_memory_barrier(); - gpio_put(self->pin_cs, 1); + cr_restore_interrupts(saved); + cr_sema_wait(&self->sema); self->dead_until_ns = LO_CALL(bootclock, get_time_ns) + self->min_delay_ns; } diff --git a/libhw/rp2040_include/libhw/rp2040_hwspi.h b/libhw/rp2040_include/libhw/rp2040_hwspi.h index a76a2c8..eb54cdc 100644 --- a/libhw/rp2040_include/libhw/rp2040_hwspi.h +++ b/libhw/rp2040_include/libhw/rp2040_hwspi.h @@ -9,6 +9,7 @@ #include /* for bi_* */ +#include #include #include @@ -32,6 +33,7 @@ struct rp2040_hwspi { /* mutable */ uint64_t dead_until_ns; + cr_sema_t sema; END_PRIVATE(LIBHW_RP2040_HWSPI_H) }; LO_IMPLEMENTATION_H(io_duplex_readwriter, struct rp2040_hwspi, rp2040_hwspi) -- cgit v1.2.3-2-g168b