summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2025-02-26 14:26:28 -0700
committerLuke T. Shumaker <lukeshu@lukeshu.com>2025-03-02 13:30:55 -0700
commit8dc86e5ec58ec94f7ea8227ee008ded202c4204f (patch)
tree02ab6704d2ad5698c45981162cc2c192ebdee9db
parent0110cae147d8b1749b4bd5763620ec73d5296a76 (diff)
libhw: Add rp2040_gpioirq, use it
-rwxr-xr-xbuild-aux/lint-bin20
-rwxr-xr-xbuild-aux/stack.c.gen16
-rw-r--r--cmd/sbc_harness/config/config.h7
-rw-r--r--libhw/CMakeLists.txt5
-rw-r--r--libhw/rp2040_gpioirq.c75
-rw-r--r--libhw/rp2040_gpioirq.h33
-rw-r--r--libhw/w5500.c25
7 files changed, 141 insertions, 40 deletions
diff --git a/build-aux/lint-bin b/build-aux/lint-bin
index 0b955de..c487f36 100755
--- a/build-aux/lint-bin
+++ b/build-aux/lint-bin
@@ -25,7 +25,7 @@ RESET=$(tput sgr0)
err() {
printf "${RED}%s${RESET}: %s\n" "$1" "$2" >&2
- #r=1
+ r=1
}
# Input is `ld --print-map` format.
@@ -107,6 +107,21 @@ lint_stack() {
<(readelf_funcs "$in_elffile" | sed 's/\.part\.[0-9]*$//' | sort -u))
}
+lint_func_blocklist() {
+ local in_elffile
+ in_elffile=$1
+
+ local blocklist=(
+ gpio_default_irq_handler
+ )
+
+ while read -r func; do
+ err "$in_elffile" "Contains blocklisted function: ${func}"
+ done < <(readelf --syms --wide -- "$in_elffile" |
+ awk '$4 == "FUNC" { print $8 }' |
+ grep -Fx "${blocklist[@]/#/-e}")
+}
+
main() {
r=0
@@ -116,7 +131,8 @@ main() {
echo 'Global variables:'
lint_globals "${elf}.map" | sed 's/^/ /'
} > "${elf%.elf}.lint.globals"
- lint_stack "$elf" &> "${elf%.elf}.lint.stack"
+ (lint_stack "$elf") &> "${elf%.elf}.lint.stack"
+ lint_func_blocklist "$elf"
done
return $r
diff --git a/build-aux/stack.c.gen b/build-aux/stack.c.gen
index c73a8bb..66d837a 100755
--- a/build-aux/stack.c.gen
+++ b/build-aux/stack.c.gen
@@ -504,6 +504,7 @@ class LibHWPlugin:
"rp2040_hwtimer_intrhandler",
"hostclock_handle_sig_alarm",
"hostnet_handle_sig_io",
+ "gpioirq_handler",
]
def extra_nodes(self) -> typing.Collection[Node]:
@@ -522,6 +523,10 @@ class LibHWPlugin:
"w5500_udp_alarm_handler",
]
return ret, False
+ if "/rp2040_gpioirq.c:" in loc and "handler->fn" in line:
+ return [
+ "w5500_intrhandler",
+ ], False
return None
def skip_call(self, chain: list[str], call: str) -> bool:
@@ -711,17 +716,14 @@ class LibMiscPlugin:
class PicoSDKPlugin:
- app_gpio_handlers: typing.Collection[str]
app_init_array: typing.Collection[str]
app_preinit_array: typing.Collection[str]
def __init__(
self,
*,
- app_gpio_handlers: typing.Collection[str],
app_init_array: typing.Collection[str],
) -> None:
- self.app_gpio_handlers = app_gpio_handlers
self.app_init_array = app_init_array
# git grep '^PICO_RUNTIME_INIT_FUNC\S*('
@@ -754,7 +756,6 @@ class PicoSDKPlugin:
def is_intrhandler(self, name: str) -> bool:
return name in [
- "gpio_default_irq_handler",
"isr_invalid",
"isr_nmi",
"isr_hardfault",
@@ -783,8 +784,6 @@ class PicoSDKPlugin:
return ["rom_hword_as_ptr(BOOTROM_TABLE_LOOKUP_OFFSET)"], False
if "/flash.c:" in loc and "boot2_copyout" in line:
return ["_stage2_boot"], False
- if "/gpio.c:" in loc and call == "callback":
- return sorted(self.app_gpio_handlers), False
if "/printf.c:" in loc:
if call == "out":
return [
@@ -1137,10 +1136,6 @@ def main(
lib9p_plugin = Lib9PPlugin(arg_base_dir, arg_c_fnames)
- sbc_gpio_handlers = [
- "w5500_intrhandler",
- ]
-
def sbc_is_thread(name: str) -> int:
if name.endswith("_cr") and name != "lib9p_srv_read_cr":
if "9p" in name:
@@ -1165,7 +1160,6 @@ def main(
if arg_pico_platform == "rp2040":
plugins += [
PicoSDKPlugin(
- app_gpio_handlers=sbc_gpio_handlers,
app_init_array=["register_fini"],
),
TinyUSBDevicePlugin(arg_c_fnames),
diff --git a/cmd/sbc_harness/config/config.h b/cmd/sbc_harness/config/config.h
index b569cd5..f9c7df2 100644
--- a/cmd/sbc_harness/config/config.h
+++ b/cmd/sbc_harness/config/config.h
@@ -12,11 +12,6 @@
/* W5500 **********************************************************************/
/**
- * How many W5500 chips we have.
- */
-#define CONFIG_W5500_NUM 1
-
-/**
* When allocating an arbitrary local port, what range should it be
* allocated from?
*
@@ -106,7 +101,7 @@ extern const size_t CONFIG_COROUTINE_STACK_SIZE_w5500_irq_cr;
#define CONFIG_COROUTINE_NUM ( \
1 /* usb_common */ + \
1 /* usb_keyboard */ + \
- CONFIG_W5500_NUM /* irq handler */ + \
+ 1 /* W5500 irq handler */ + \
_CONFIG_9P_NUM_SOCKS /* 9P accept()+read() */ + \
(CONFIG_9P_SRV_MAX_REQS*_CONFIG_9P_NUM_SOCKS) /* 9P work+write() */ )
diff --git a/libhw/CMakeLists.txt b/libhw/CMakeLists.txt
index d1767da..bd92e04 100644
--- a/libhw/CMakeLists.txt
+++ b/libhw/CMakeLists.txt
@@ -1,6 +1,6 @@
# libhw/CMakeLists.txt - Device drivers
#
-# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
add_library(libhw INTERFACE)
@@ -14,8 +14,9 @@ if (PICO_PLATFORM STREQUAL "rp2040")
libcr_ipc
)
target_sources(libhw INTERFACE
- rp2040_hwtimer.c
+ rp2040_gpioirq.c
rp2040_hwspi.c
+ rp2040_hwtimer.c
w5500.c
)
target_link_libraries(libhw INTERFACE
diff --git a/libhw/rp2040_gpioirq.c b/libhw/rp2040_gpioirq.c
new file mode 100644
index 0000000..2f0ceac
--- /dev/null
+++ b/libhw/rp2040_gpioirq.c
@@ -0,0 +1,75 @@
+/* libhw/rp2040_gpioirq.c - Utilities for sharing the GPIO IRQ (IO_IRQ_BANK0)
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <hardware/structs/io_bank0.h> /* for io_bank0_hw */
+#include <hardware/irq.h> /* for irq_set_exclusive_handler() */
+
+#include <libmisc/macro.h>
+
+#include "rp2040_gpioirq.h"
+
+struct gpioirq_handler_entry {
+ gpioirq_handler_t fn;
+ void *arg;
+};
+struct gpioirq_handler_entry gpioirq_handlers[NUM_BANK0_GPIOS][4] = {0};
+
+int gpioirq_core = -1;
+
+static void gpioirq_handler(void) {
+ uint core = get_core_num();
+ io_bank0_irq_ctrl_hw_t *irq_ctrl_base;
+ switch (core) {
+ case 0: irq_ctrl_base = &io_bank0_hw->proc0_irq_ctrl; break;
+ case 1: irq_ctrl_base = &io_bank0_hw->proc1_irq_ctrl; break;
+ default: assert_notreached("invalid core number");
+ }
+ for (uint regnum = 0; regnum < LM_ARRAY_LEN(irq_ctrl_base->ints); regnum++) {
+ uint32_t regval = irq_ctrl_base->ints[regnum];
+ for (uint bit = 0; bit < 32 && (regnum*8)+(bit/4) < NUM_BANK0_GPIOS; bit++) {
+ if (regval & 1u<<bit) {
+ uint gpio = (regnum*8)+(bit/4);
+ uint event_idx = bit%4;
+ struct gpioirq_handler_entry *handler = &gpioirq_handlers[gpio][event_idx];
+ if (handler->fn)
+ handler->fn(handler->arg, gpio, 1u<<event_idx);
+ }
+ }
+ /* acknowledge irq */
+ io_bank0_hw->intr[regnum] = regval;
+ }
+}
+
+void gpioirq_set_and_enable_exclusive_handler(uint gpio, enum gpio_irq_level event, gpioirq_handler_t fn, void *arg) {
+ assert(gpio < NUM_BANK0_GPIOS);
+ assert(event == GPIO_IRQ_LEVEL_LOW ||
+ event == GPIO_IRQ_LEVEL_HIGH ||
+ event == GPIO_IRQ_EDGE_FALL ||
+ event == GPIO_IRQ_EDGE_RISE);
+ assert(fn);
+
+ uint event_idx = LM_FLOORLOG2(event);
+ assert(gpioirq_handlers[gpio][event_idx].fn == NULL);
+
+ uint core = get_core_num();
+ assert(gpioirq_core == -1 || gpioirq_core == (int)core);
+
+ io_bank0_irq_ctrl_hw_t *irq_ctrl_base;
+ switch (core) {
+ case 0: irq_ctrl_base = &io_bank0_hw->proc0_irq_ctrl; break;
+ case 1: irq_ctrl_base = &io_bank0_hw->proc1_irq_ctrl; break;
+ default: assert_notreached("invalid core number");
+ }
+
+ gpioirq_handlers[gpio][event_idx].fn = fn;
+ gpioirq_handlers[gpio][event_idx].arg = arg;
+ hw_set_bits(&irq_ctrl_base->inte[gpio/8], 1u<<((4*(gpio%8))+event_idx));
+ if (gpioirq_core == -1) {
+ irq_set_exclusive_handler(IO_IRQ_BANK0, gpioirq_handler);
+ irq_set_enabled(IO_IRQ_BANK0, true);
+ gpioirq_core = core;
+ }
+}
diff --git a/libhw/rp2040_gpioirq.h b/libhw/rp2040_gpioirq.h
new file mode 100644
index 0000000..06041c9
--- /dev/null
+++ b/libhw/rp2040_gpioirq.h
@@ -0,0 +1,33 @@
+/* libhw/rp2040_gpioirq.h - Utilities for sharing the GPIO IRQ (IO_IRQ_BANK0)
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBHW_RP2040_GPIOIRQ_H_
+#define _LIBHW_RP2040_GPIOIRQ_H_
+
+#include <hardware/gpio.h> /* for enum gpio_irq_level */
+
+typedef void (*gpioirq_handler_t)(void *arg, uint gpio, enum gpio_irq_level event);
+
+/**
+ * Register `fn(arg, ...)` to be called when `event` fires on GPIO pin
+ * `gpio`.
+ *
+ * If multiple events happen close together, the handlers will not
+ * necessarily be called in-order.
+ *
+ * Your handler does not need to acknowledge the IRQ; that will be
+ * done for you after your handler is called.
+ *
+ * It is illegal to call gpioirq_set_and_enable_exclusive_handler()
+ * on multiple cores.
+ *
+ * This is better than the Pico-SDK <hardware/gpio.h> IRQ functions
+ * because their functions call the handlers for every event, and it
+ * is up to you to de-mux them in your handler.
+ */
+void gpioirq_set_and_enable_exclusive_handler(uint gpio, enum gpio_irq_level event, gpioirq_handler_t fn, void *arg);
+
+#endif /* _LIBHW_RP2040_GPIOIRQ_H_ */
diff --git a/libhw/w5500.c b/libhw/w5500.c
index 64b373b..b4ad86b 100644
--- a/libhw/w5500.c
+++ b/libhw/w5500.c
@@ -72,6 +72,7 @@
/* TODO: Write a <libhw/generic/gpio.h> to avoid w5500.c being
* pico-sdk-specific. */
#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */
+#include "rp2040_gpioirq.h"
#include <libcr/coroutine.h> /* for cr_yield() */
@@ -95,9 +96,6 @@
#ifndef CONFIG_W5500_LOCAL_PORT_MAX
#error config.h must define CONFIG_W5500_LOCAL_PORT_MAX
#endif
-#ifndef CONFIG_W5500_NUM
- #error config.h must define CONFIG_W5500_NUM
-#endif
#ifndef CONFIG_W5500_DEBUG
#error config.h must define CONFIG_W5500_DEBUG
#endif
@@ -280,13 +278,10 @@ static inline void w5500_socket_close(struct _w5500_socket *socket) {
/* init() *********************************************************************/
-static struct w5500 *w5500_chips[CONFIG_W5500_NUM] = {0};
-
-static void w5500_intrhandler(uint gpio, uint32_t LM_UNUSED(event_mask)) {
- debugf("w5500_intrhandler(): interrupt on pin %u", gpio);
- for (size_t i = 0; i < LM_ARRAY_LEN(w5500_chips); i++)
- if (w5500_chips[i] && w5500_chips[i]->pin_intr == gpio)
- cr_sema_signal_from_intrhandler(&w5500_chips[i]->intr);
+static void w5500_intrhandler(void *_chip, uint LM_UNUSED(gpio), enum gpio_irq_level LM_UNUSED(event)) {
+ struct w5500 *chip = _chip;
+ debugf("w5500_intrhandler()");
+ cr_sema_signal_from_intrhandler(&chip->intr);
}
void _w5500_init(struct w5500 *chip,
@@ -318,19 +313,11 @@ void _w5500_init(struct w5500 *chip,
}
/* Initialize the hardware. */
- gpio_set_irq_enabled_with_callback(pin_intr, GPIO_IRQ_EDGE_FALL, true, w5500_intrhandler);
+ gpioirq_set_and_enable_exclusive_handler(pin_intr, GPIO_IRQ_EDGE_FALL, w5500_intrhandler, chip);
gpio_set_dir(chip->pin_reset, GPIO_OUT);
w5500_hard_reset(chip);
/* Finally, wire in the interrupt handler. */
- bool saved = cr_save_and_disable_interrupts();
- for (size_t i = 0; i < LM_ARRAY_LEN(w5500_chips); i++) {
- if (w5500_chips[i] == NULL) {
- w5500_chips[i] = chip;
- break;
- }
- }
- cr_restore_interrupts(saved);
coroutine_add("w5500_irq", w5500_irq_cr, chip);
}