Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add architectural support for shared interrupts (take 2) #61422

Merged
merged 7 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions arch/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,23 @@ config DYNAMIC_INTERRUPTS
interrupt-related data structures to RAM instead of ROM, and
on some architectures increase code size.

config SHARED_INTERRUPTS
bool "Set this to enable support for shared interrupts"
depends on GEN_SW_ISR_TABLE
select EXPERIMENTAL
help
LaurentiuM1234 marked this conversation as resolved.
Show resolved Hide resolved
LaurentiuM1234 marked this conversation as resolved.
Show resolved Hide resolved
Set this to enable support for shared interrupts. Use this with
caution as enabling this will increase the image size by a
non-negligible amount.

config SHARED_IRQ_MAX_NUM_CLIENTS
int "Maximum number of clients allowed per shared interrupt"
default 2
depends on SHARED_INTERRUPTS
help
This option controls the maximum number of clients allowed
per shared interrupt. Set this according to your needs.

config GEN_ISR_TABLES
bool "Use generated IRQ tables"
help
Expand Down
2 changes: 2 additions & 0 deletions arch/common/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ zephyr_library_sources_ifdef(
sw_isr_common.c
)

zephyr_library_sources_ifdef(CONFIG_SHARED_INTERRUPTS shared_irq.c)

if(NOT CONFIG_ARCH_HAS_TIMING_FUNCTIONS AND
NOT CONFIG_SOC_HAS_TIMING_FUNCTIONS AND
NOT CONFIG_BOARD_HAS_TIMING_FUNCTIONS)
Expand Down
5 changes: 5 additions & 0 deletions arch/common/isr_tables.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,8 @@ struct _isr_table_entry __sw_isr_table _sw_isr_table[IRQ_TABLE_SIZE] = {
(void *)&z_irq_spurious},
};
#endif

#ifdef CONFIG_SHARED_INTERRUPTS
struct z_shared_isr_table_entry __shared_sw_isr_table z_shared_sw_isr_table[IRQ_TABLE_SIZE] = {
};
#endif
209 changes: 209 additions & 0 deletions arch/common/shared_irq.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* Copyright 2023 NXP
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/sw_isr_table.h>
#include <zephyr/spinlock.h>

/* 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);
LaurentiuM1234 marked this conversation as resolved.
Show resolved Hide resolved

/* 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++;
LaurentiuM1234 marked this conversation as resolved.
Show resolved Hide resolved

entry->isr = z_shared_isr;
entry->arg = shared_entry;
}

/* don't register the same ISR/arg pair multiple times */
LaurentiuM1234 marked this conversation as resolved.
Show resolved Hide resolved
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 */
43 changes: 25 additions & 18 deletions arch/common/sw_isr_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,40 +71,28 @@ 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 *),
const void *param)
unsigned int z_get_sw_isr_table_idx(unsigned int irq)
{
unsigned int table_idx;

/*
* Do not assert on the IRQ enable status for ARM GIC since the SGI
* type interrupts are always enabled and attempting to install an ISR
* for them will cause the assertion to fail.
*/
#ifndef CONFIG_GIC
__ASSERT(!irq_is_enabled(irq), "IRQ %d is enabled", irq);
#endif /* !CONFIG_GIC */

#ifdef CONFIG_MULTI_LEVEL_INTERRUPTS
unsigned int level;
unsigned int parent_irq;
unsigned int parent_offset;
unsigned int level, parent_irq, parent_offset;

level = irq_get_level(irq);

if (level == 2U) {
parent_irq = irq_parent_level_2(irq);
parent_offset = get_parent_offset(parent_irq,
lvl2_irq_list,
CONFIG_NUM_2ND_LEVEL_AGGREGATORS);
lvl2_irq_list,
CONFIG_NUM_2ND_LEVEL_AGGREGATORS);
table_idx = parent_offset + irq_from_level_2(irq);
}
#ifdef CONFIG_3RD_LEVEL_INTERRUPTS
else if (level == 3U) {
parent_irq = irq_parent_level_3(irq);
parent_offset = get_parent_offset(parent_irq,
lvl3_irq_list,
CONFIG_NUM_3RD_LEVEL_AGGREGATORS);
lvl3_irq_list,
CONFIG_NUM_3RD_LEVEL_AGGREGATORS);
table_idx = parent_offset + irq_from_level_3(irq);
}
#endif /* CONFIG_3RD_LEVEL_INTERRUPTS */
Expand All @@ -117,6 +105,25 @@ void z_isr_install(unsigned int irq, void (*routine)(const void *),
table_idx = irq - CONFIG_GEN_IRQ_START_VECTOR;
#endif /* CONFIG_MULTI_LEVEL_INTERRUPTS */

return table_idx;
}

void __weak z_isr_install(unsigned int irq, void (*routine)(const void *),
const void *param)
{
unsigned int table_idx;

/*
* Do not assert on the IRQ enable status for ARM GIC since the SGI
* type interrupts are always enabled and attempting to install an ISR
* for them will cause the assertion to fail.
*/
#ifndef CONFIG_GIC
__ASSERT(!irq_is_enabled(irq), "IRQ %d is enabled", irq);
#endif /* !CONFIG_GIC */

table_idx = z_get_sw_isr_table_idx(irq);

/* If dynamic IRQs are enabled, then the _sw_isr_table is in RAM and
* can be modified
*/
Expand Down
Loading
Loading