arch: Add support for dynamically disconnecting shared interrupts

This commit provides the users a way to disconnect dynamic
interrupts. This is needed because we don't want to keep
piling up ISR/arg pairs until the number of registrable
clients is reached.

This feature is only relevant for shared and dynamic interrupts.

Signed-off-by: Laurentiu Mihalcea <laurentiu.mihalcea@nxp.com>
This commit is contained in:
Laurentiu Mihalcea 2023-08-11 13:09:22 +03:00 committed by Carles Cufí
parent 02f52503bb
commit b8d487e54b
4 changed files with 153 additions and 0 deletions

View file

@ -101,4 +101,109 @@ void z_isr_install(unsigned int irq, void (*routine)(const void *),
k_spin_unlock(&lock, key); k_spin_unlock(&lock, key);
} }
static void swap_client_data(struct z_shared_isr_client *a,
struct z_shared_isr_client *b)
{
struct z_shared_isr_client tmp;
tmp.arg = a->arg;
tmp.isr = a->isr;
a->arg = b->arg;
a->isr = b->isr;
b->arg = tmp.arg;
b->isr = tmp.isr;
}
static void shared_irq_remove_client(struct z_shared_isr_table_entry *shared_entry,
int client_idx, unsigned int table_idx)
{
int i;
shared_entry->clients[client_idx].isr = NULL;
shared_entry->clients[client_idx].arg = NULL;
/* push back the removed client to the end of the client list */
for (i = client_idx; i <= (int)shared_entry->client_num - 2; i++) {
swap_client_data(&shared_entry->clients[i],
&shared_entry->clients[i + 1]);
}
shared_entry->client_num--;
/* "unshare" interrupt if there will be a single client left */
if (shared_entry->client_num == 1) {
_sw_isr_table[table_idx].isr = shared_entry->clients[0].isr;
_sw_isr_table[table_idx].arg = shared_entry->clients[0].arg;
shared_entry->clients[0].isr = NULL;
shared_entry->clients[0].arg = NULL;
shared_entry->client_num--;
}
}
int __weak arch_irq_disconnect_dynamic(unsigned int irq, unsigned int priority,
void (*routine)(const void *parameter),
const void *parameter, uint32_t flags)
{
ARG_UNUSED(priority);
ARG_UNUSED(flags);
return z_isr_uninstall(irq, routine, parameter);
}
int z_isr_uninstall(unsigned int irq,
void (*routine)(const void *),
const void *parameter)
{
struct z_shared_isr_table_entry *shared_entry;
struct _isr_table_entry *entry;
struct z_shared_isr_client *client;
unsigned int table_idx;
size_t i;
k_spinlock_key_t key;
table_idx = z_get_sw_isr_table_idx(irq);
/* check for out of bounds table index */
if (table_idx >= CONFIG_NUM_IRQS) {
return -EINVAL;
}
shared_entry = &z_shared_sw_isr_table[table_idx];
entry = &_sw_isr_table[table_idx];
key = k_spin_lock(&lock);
/* note: it's important that we remove the ISR/arg pair even if
* the IRQ line is not being shared because z_isr_install() will
* not overwrite it unless the _sw_isr_table entry for the given
* IRQ line contains the default pair which is z_irq_spurious/NULL.
*/
if (!shared_entry->client_num) {
if (entry->isr == routine && entry->arg == parameter) {
entry->isr = z_irq_spurious;
entry->arg = NULL;
}
goto out_unlock;
}
for (i = 0; i < shared_entry->client_num; i++) {
client = &shared_entry->clients[i];
if (client->isr == routine && client->arg == parameter) {
/* note: this is the only match we're going to get */
shared_irq_remove_client(shared_entry, i, table_idx);
goto out_unlock;
}
}
out_unlock:
k_spin_unlock(&lock, key);
return 0;
}
#endif /* CONFIG_DYNAMIC_INTERRUPTS */ #endif /* CONFIG_DYNAMIC_INTERRUPTS */

View file

@ -70,6 +70,31 @@ irq_connect_dynamic(unsigned int irq, unsigned int priority,
flags); flags);
} }
/**
* Disconnect a dynamic interrupt.
*
* Use this in conjunction with shared interrupts to remove a routine/parameter
* pair from the list of clients using the same interrupt line. If the interrupt
* is not being shared then the associated _sw_isr_table entry will be replaced
* by (NULL, z_irq_spurious) (default entry).
*
* @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, 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);
}
/** /**
* @brief Initialize a 'direct' interrupt handler. * @brief Initialize a 'direct' interrupt handler.
* *

View file

@ -107,6 +107,11 @@ unsigned int z_get_sw_isr_table_idx(unsigned int irq);
#ifdef CONFIG_DYNAMIC_INTERRUPTS #ifdef CONFIG_DYNAMIC_INTERRUPTS
void z_isr_install(unsigned int irq, void (*routine)(const void *), void z_isr_install(unsigned int irq, void (*routine)(const void *),
const void *param); const void *param);
#ifdef CONFIG_SHARED_INTERRUPTS
int z_isr_uninstall(unsigned int irq, void (*routine)(const void *),
const void *param);
#endif /* CONFIG_SHARED_INTERRUPTS */
#endif #endif
#ifdef __cplusplus #ifdef __cplusplus

View file

@ -323,6 +323,24 @@ int arch_irq_connect_dynamic(unsigned int irq, unsigned int priority,
void (*routine)(const void *parameter), void (*routine)(const void *parameter),
const void *parameter, uint32_t flags); const void *parameter, uint32_t flags);
/**
* Arch-specific hook to dynamically uninstall a shared interrupt.
* If the interrupt is not being shared, then the associated
* _sw_isr_table entry will be replaced by (NULL, z_irq_spurious)
* (default entry).
*
* @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);
/** /**
* @def ARCH_IRQ_CONNECT(irq, pri, isr, arg, flags) * @def ARCH_IRQ_CONNECT(irq, pri, isr, arg, flags)
* *