Skip to content

Commit

Permalink
arch: Add support for dynamically disconnecting shared interrupts
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
LaurentiuM1234 committed Sep 6, 2023
1 parent b5049aa commit edd7097
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 0 deletions.
105 changes: 105 additions & 0 deletions arch/common/shared_irq.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,109 @@ void z_isr_install(unsigned int irq, void (*routine)(const void *),
k_spin_unlock(&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.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 shared_irq_data *irq_data,
int client_idx, unsigned int table_idx)
{
int i;

irq_data->clients[client_idx].isr = NULL;
irq_data->clients[client_idx].arg = NULL;

/* push back the removed client to the end of the client list */
for (i = client_idx; i <= (int)irq_data->client_num - 2; i++) {
swap_client_data(&irq_data->clients[i],
&irq_data->clients[i + 1]);
}

irq_data->client_num--;

/* "unshare" interrupt if there will be a single client left */
if (irq_data->client_num == 1) {
_sw_isr_table[table_idx].isr = irq_data->clients[0].isr;
_sw_isr_table[table_idx].arg = irq_data->clients[0].arg;

irq_data->clients[0].isr = NULL;
irq_data->clients[0].arg = NULL;

irq_data->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 shared_irq_data *irq_data;
struct _isr_table_entry *entry;
struct shared_irq_client *client;
unsigned int table_idx;
size_t i;
k_spinlock_key_t key;

table_idx = get_sw_isr_table_idx(irq);

/* check for out of bounds table index */
if (table_idx >= CONFIG_NUM_IRQS) {
return -EINVAL;
}

irq_data = &_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 (!irq_data->client_num) {
if (entry->isr == routine && entry->arg == parameter) {
entry->isr = z_irq_spurious;
entry->arg = NULL;
}

goto out_unlock;
}

for (i = 0; i < irq_data->client_num; i++) {
client = &irq_data->clients[i];

if (client->isr == routine && client->arg == parameter) {
/* note: this is the only match we're going to get */
shared_irq_remove_client(irq_data, i, table_idx);
goto out_unlock;
}
}

out_unlock:
k_spin_unlock(&lock, key);
return 0;
}

#endif /* CONFIG_DYNAMIC_INTERRUPTS */
25 changes: 25 additions & 0 deletions include/zephyr/irq.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,31 @@ irq_connect_dynamic(unsigned int irq, unsigned int priority,
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.
*
Expand Down
5 changes: 5 additions & 0 deletions include/zephyr/sw_isr_table.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ unsigned int get_sw_isr_table_idx(unsigned int irq);
#ifdef CONFIG_DYNAMIC_INTERRUPTS
void z_isr_install(unsigned int irq, void (*routine)(const void *),
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

#ifdef __cplusplus
Expand Down
18 changes: 18 additions & 0 deletions include/zephyr/sys/arch_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,24 @@ int arch_irq_connect_dynamic(unsigned int irq, unsigned int priority,
void (*routine)(const void *parameter),
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)
*
Expand Down

0 comments on commit edd7097

Please sign in to comment.