/* * Copyright 2023 NXP * * SPDX-License-Identifier: Apache-2.0 */ #include #include /* an interrupt line can be considered shared only if there's * at least 2 clients using it. As such, enforce the fact that * the maximum number of allowed clients should be at least 2. */ BUILD_ASSERT(CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS >= 2, "maximum number of clients should be at least 2"); void z_shared_isr(const void *data) { size_t i; const struct z_shared_isr_table_entry *entry; const struct z_shared_isr_client *client; entry = data; for (i = 0; i < entry->client_num; i++) { client = &entry->clients[i]; if (client->isr) { client->isr(client->arg); } } } #ifdef CONFIG_DYNAMIC_INTERRUPTS static struct k_spinlock lock; void z_isr_install(unsigned int irq, void (*routine)(const void *), const void *param) { struct z_shared_isr_table_entry *shared_entry; struct _isr_table_entry *entry; struct z_shared_isr_client *client; unsigned int table_idx; int 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; } shared_entry = &z_shared_sw_isr_table[table_idx]; entry = &_sw_isr_table[table_idx]; key = k_spin_lock(&lock); /* have we reached the client limit? */ __ASSERT(shared_entry->client_num < CONFIG_SHARED_IRQ_MAX_NUM_CLIENTS, "reached maximum number of clients"); if (entry->isr == z_irq_spurious) { /* this is the first time a ISR/arg pair is registered * for INTID => no need to share it. */ entry->isr = routine; entry->arg = param; k_spin_unlock(&lock, key); return; } else if (entry->isr != z_shared_isr) { /* INTID is being used by another ISR/arg pair. * Push back the ISR/arg pair registered in _sw_isr_table * to the list of clients and hijack the pair stored in * _sw_isr_table with our own z_shared_isr/shared_entry pair. */ shared_entry->clients[shared_entry->client_num].isr = entry->isr; shared_entry->clients[shared_entry->client_num].arg = entry->arg; shared_entry->client_num++; entry->isr = z_shared_isr; entry->arg = shared_entry; } /* don't register the same ISR/arg pair multiple times */ for (i = 0; i < shared_entry->client_num; i++) { client = &shared_entry->clients[i]; __ASSERT(client->isr != routine && client->arg != param, "trying to register duplicate ISR/arg pair"); } shared_entry->clients[shared_entry->client_num].isr = routine; shared_entry->clients[shared_entry->client_num].arg = param; shared_entry->client_num++; 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 */