diff --git a/arch/Kconfig b/arch/Kconfig index 332bb49455f7..a8d2befe8861 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -403,6 +403,22 @@ config DYNAMIC_INTERRUPTS interrupt-related data structures to RAM instead of ROM, and on some architectures increase code size. +config SHARED_INTERRUPTS + bool "Select this to enable support for shared interrupts" + default n + depends on DYNAMIC_INTERRUPTS + depends on ARM64 + help + This enables support for shared interrupts. This is still a WIP so use + with caution. + +config SHARED_IRQ_MAX_NUM_CLIENTS + int "Maximum number of clients able to use the same INTID" + default 16 + depends on SHARED_INTERRUPTS + help + Maximum number of clients able to use the same INTID + config GEN_ISR_TABLES bool "Use generated IRQ tables" help diff --git a/arch/common/CMakeLists.txt b/arch/common/CMakeLists.txt index 21ddbd24ea49..b886ede9d12c 100644 --- a/arch/common/CMakeLists.txt +++ b/arch/common/CMakeLists.txt @@ -7,6 +7,10 @@ if(CONFIG_GEN_ISR_TABLES) CONFIG_GEN_ISR_TABLES sw_isr_common.c ) + zephyr_library_sources_ifdef( + CONFIG_SHARED_INTERRUPTS + shared_irq.c + ) endif() if(NOT CONFIG_ARCH_HAS_TIMING_FUNCTIONS AND diff --git a/arch/common/shared_irq.c b/arch/common/shared_irq.c new file mode 100644 index 000000000000..079758684e2d --- /dev/null +++ b/arch/common/shared_irq.c @@ -0,0 +1,162 @@ +/* + * Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +struct shared_irq_client { + void (*routine)(const void *arg); + const void *arg; +}; + +struct shared_irq_data { + bool enabled; + struct shared_irq_client clients[CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS]; + uint32_t client_num; + struct k_spinlock lock; +}; + +static struct shared_irq_data shared_irq_table[CONFIG_NUM_IRQS]; + +void shared_irq_isr(const void *data) +{ + int i; + const struct shared_irq_data *irq_data = data; + + for (i = 0; i < irq_data->client_num; i++) + /* note: due to how the IRQ disconnect function works + * this should be impossible but it's better to safe than + * sorry I guess. + */ + if (irq_data->clients[i].routine) + irq_data->clients[i].routine(irq_data->clients[i].arg); +} + +void z_isr_install(unsigned int irq, void (*routine)(const void *), + const void *param) +{ + struct shared_irq_data *irq_data; + unsigned int table_idx; + int i; + k_spinlock_key_t key; + + table_idx = irq - CONFIG_GEN_IRQ_START_VECTOR; + + /* check for out of bounds table index */ + if (table_idx >= CONFIG_NUM_IRQS) + return; + + irq_data = &shared_irq_table[table_idx]; + + /* seems like we've reached the limit of clients */ + /* TODO: is there a better way of letting the user know this is the case? */ + if (irq_data->client_num == CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS) + return; + + key = k_spin_lock(&irq_data->lock); + + if (!irq_data->enabled) { + _sw_isr_table[table_idx].arg = irq_data; + _sw_isr_table[table_idx].isr = shared_irq_isr; + + irq_data->enabled = true; + } + + /* don't register the same routine/arg pair again */ + for (i = 0; i < irq_data->client_num; i++) { + if (irq_data->clients[i].routine == routine + && irq_data->clients[i].arg == param) { + k_spin_unlock(&irq_data->lock, key); + return; + } + } + + + irq_data->clients[irq_data->client_num].routine = routine; + irq_data->clients[irq_data->client_num].arg = param; + irq_data->client_num++; + + k_spin_unlock(&irq_data->lock, key); +} + +static void swap_client_data(struct shared_irq_client *a, + struct shared_irq_client *b) +{ + struct shared_irq_client tmp; + + tmp.arg = a->arg; + tmp.routine = a->routine; + + a->arg = b->arg; + a->routine = b->routine; + + b->arg = tmp.arg; + b->routine = tmp.routine; +} + +static inline void shared_irq_remove_client(struct shared_irq_data *irq_data, + int client_idx) +{ + int i; + + irq_data->clients[client_idx].routine = NULL; + irq_data->clients[client_idx].arg = NULL; + + /* push back the NULL client data to the end of the array to avoid + * having holes in the shared IRQ table/ + */ + for (i = client_idx; i <= (int)irq_data->client_num - 2; i++) + swap_client_data(&irq_data->clients[i], + &irq_data->clients[i + 1]); + +} + +int arch_irq_disconnect_dynamic(unsigned int irq, + unsigned int priority, + void (*routine)(const void *), + const void *parameter, + uint32_t flags) +{ + ARG_UNUSED(flags); + ARG_UNUSED(priority); + + struct shared_irq_data *irq_data; + unsigned int table_idx; + int i; + k_spinlock_key_t key; + + table_idx = irq - CONFIG_GEN_IRQ_START_VECTOR; + + /* check for out of bounds table index */ + if (table_idx >= CONFIG_NUM_IRQS) + return -EINVAL; + + irq_data = &shared_irq_table[table_idx]; + + if (!irq_data->client_num) + return 0; + + key = k_spin_lock(&irq_data->lock); + + for (i = 0; i < irq_data->client_num; i++) { + if (irq_data->clients[i].routine == routine + && irq_data->clients[i].arg == parameter) { + shared_irq_remove_client(irq_data, i); + irq_data->client_num--; + if (!irq_data->client_num) + irq_data->enabled = false; + /* note: you can't have duplicate routine/arg pairs so this + * is the only match we're going to get. + */ + goto out_unlock; + } + } + +out_unlock: + k_spin_unlock(&irq_data->lock, key); + return 0; +} diff --git a/arch/common/sw_isr_common.c b/arch/common/sw_isr_common.c index 538c03603f28..5b494eb0329b 100644 --- a/arch/common/sw_isr_common.c +++ b/arch/common/sw_isr_common.c @@ -71,7 +71,7 @@ unsigned int get_parent_offset(unsigned int parent_irq, #endif /* CONFIG_MULTI_LEVEL_INTERRUPTS */ -void z_isr_install(unsigned int irq, void (*routine)(const void *), +void __weak z_isr_install(unsigned int irq, void (*routine)(const void *), const void *param) { unsigned int table_idx; diff --git a/include/zephyr/irq.h b/include/zephyr/irq.h index b68d91738196..74afedbe38c3 100644 --- a/include/zephyr/irq.h +++ b/include/zephyr/irq.h @@ -70,6 +70,32 @@ irq_connect_dynamic(unsigned int irq, unsigned int priority, flags); } +#ifdef CONFIG_SHARED_INTERRUPTS + +/** + * Unregister a dynamic interrupt. + * + * Use this in conjunction with the shared interrupts feature to + * unregister an ISR/arg pair. + * + * @param irq IRQ line number + * @param priority Interrupt priority + * @param routine Interrupt service routine + * @param parameter ISR parameter + * @param flags Arch-specific IRQ configuration flags + * + * @return 0 in case of success, a negative value otherwise. + */ +static inline int +irq_disconnect_dynamic(unsigned int irq, unsigned int priority, + void (*routine)(const void *parameter), + const void *parameter, uint32_t flags) +{ + return arch_irq_disconnect_dynamic(irq, priority, routine, parameter, flags); +} + +#endif /* CONFIG_SHARED_INTERRUPTS */ + /** * @brief Initialize a 'direct' interrupt handler. * diff --git a/include/zephyr/sys/arch_interface.h b/include/zephyr/sys/arch_interface.h index 0cf8cfb24a47..13633427c00a 100644 --- a/include/zephyr/sys/arch_interface.h +++ b/include/zephyr/sys/arch_interface.h @@ -323,6 +323,25 @@ int arch_irq_connect_dynamic(unsigned int irq, unsigned int priority, void (*routine)(const void *parameter), const void *parameter, uint32_t flags); +#ifdef CONFIG_SHARED_INTERRUPTS + +/** + * Arch-specific hook to uninstall a dynamic interrupt. + * + * @param irq IRQ line number + * @param priority Interrupt priority + * @param routine Interrupt service routine + * @param parameter ISR parameter + * @param flags Arch-specific IRQ configuration flag + * + * @return 0 in case of success, negative value otherwise. + */ +int arch_irq_disconnect_dynamic(unsigned int irq, unsigned int priority, + void (*routine)(const void *parameter), + const void *parameter, uint32_t flags); + +#endif /* CONFIG_SHARED_INTERRUPTS */ + /** * @def ARCH_IRQ_CONNECT(irq, pri, isr, arg, flags) *