Skip to content

Commit

Permalink
arch: sw_isr: rework the multilevel lookup table
Browse files Browse the repository at this point in the history
Previously the multilevel irq lookup table is generated by
looping through the devicetree nodes using macros & Kconfig.

This PR shifts the heavy lifting to devicetree & DT macros such
that an interrupt controller can register itself directly with
the lookup table.

Signed-off-by: Yong Cong Sin <[email protected]>
  • Loading branch information
ycsin committed Dec 22, 2023
1 parent 3dfade8 commit f2ccfdc
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 175 deletions.
160 changes: 31 additions & 129 deletions arch/common/multilevel_irq.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,164 +15,66 @@ BUILD_ASSERT((CONFIG_NUM_2ND_LEVEL_AGGREGATORS * CONFIG_MAX_IRQ_PER_AGGREGATOR)
BIT(CONFIG_2ND_LEVEL_INTERRUPT_BITS),
"L2 bits not enough to cover the number of L2 IRQs");

/*
* Insert code if the node_id is an interrupt controller
*/
#define Z_IF_DT_IS_INTC(node_id, code) \
IF_ENABLED(DT_NODE_HAS_PROP(node_id, interrupt_controller), (code))

/*
* Expands to node_id if its IRQN is equal to `_irq`, nothing otherwise
* This only works for `_irq` between 0 & 4095, see `IS_EQ`
*/
#define Z_IF_DT_INTC_IRQN_EQ(node_id, _irq) IF_ENABLED(IS_EQ(DT_IRQ(node_id, irq), _irq), (node_id))

/*
* Expands to node_id if it's an interrupt controller & its IRQN is `irq`, or nothing otherwise
*/
#define Z_DT_INTC_GET_IRQN(node_id, _irq) \
Z_IF_DT_IS_INTC(node_id, Z_IF_DT_INTC_IRQN_EQ(node_id, _irq))

/**
* Loop through child of "/soc" and get root interrupt controllers with `_irq` as IRQN,
* this assumes only one device has the IRQN
* @param _irq irq number
* @return node_id(s) that has the `_irq` number, or empty if none of them has the `_irq`
*/
#define INTC_DT_IRQN_GET(_irq) \
DT_FOREACH_CHILD_STATUS_OKAY_VARGS(DT_PATH(soc), Z_DT_INTC_GET_IRQN, _irq)

/* If can't find any matching interrupt controller, fills with `NULL` */
#define INTC_DEVICE_INIT(node_id) .dev = DEVICE_DT_GET_OR_NULL(node_id),

#define INIT_IRQ_PARENT_OFFSET(d, i, o) { \
INTC_DEVICE_INIT(d) \
.irq = i, \
.offset = o, \
}

#define IRQ_INDEX_TO_OFFSET(i, base) (base + i * CONFIG_MAX_IRQ_PER_AGGREGATOR)

#define CAT_2ND_LVL_LIST(i, base) \
INIT_IRQ_PARENT_OFFSET(INTC_DT_IRQN_GET(CONFIG_2ND_LVL_INTR_0##i##_OFFSET), \
CONFIG_2ND_LVL_INTR_0##i##_OFFSET, IRQ_INDEX_TO_OFFSET(i, base))
const struct _irq_parent_entry _lvl2_irq_list[CONFIG_NUM_2ND_LEVEL_AGGREGATORS]
= { LISTIFY(CONFIG_NUM_2ND_LEVEL_AGGREGATORS, CAT_2ND_LVL_LIST, (,),
CONFIG_2ND_LVL_ISR_TBL_OFFSET) };

#ifdef CONFIG_3RD_LEVEL_INTERRUPTS

BUILD_ASSERT((CONFIG_NUM_3RD_LEVEL_AGGREGATORS * CONFIG_MAX_IRQ_PER_AGGREGATOR) <=
BIT(CONFIG_3RD_LEVEL_INTERRUPT_BITS),
"L3 bits not enough to cover the number of L3 IRQs");

#define CAT_3RD_LVL_LIST(i, base) \
INIT_IRQ_PARENT_OFFSET(INTC_DT_IRQN_GET(CONFIG_3RD_LVL_INTR_0##i##_OFFSET), \
CONFIG_3RD_LVL_INTR_0##i##_OFFSET, IRQ_INDEX_TO_OFFSET(i, base))

const struct _irq_parent_entry _lvl3_irq_list[CONFIG_NUM_3RD_LEVEL_AGGREGATORS]
= { LISTIFY(CONFIG_NUM_3RD_LEVEL_AGGREGATORS, CAT_3RD_LVL_LIST, (,),
CONFIG_3RD_LVL_ISR_TBL_OFFSET) };

#endif /* CONFIG_3RD_LEVEL_INTERRUPTS */

static const struct _irq_parent_entry *get_parent_entry(unsigned int parent_irq,
const struct _irq_parent_entry list[],
unsigned int length)
{
unsigned int i;
const struct _irq_parent_entry *entry = NULL;

for (i = 0U; i < length; ++i) {
if (list[i].irq == parent_irq) {
entry = &list[i];
break;
}
}

__ASSERT(i != length, "Invalid argument: %i", parent_irq);

return entry;
}

const struct device *z_get_sw_isr_device_from_irq(unsigned int irq)
{
const struct device *dev = NULL;
unsigned int level, parent_irq;
const struct _irq_parent_entry *entry = NULL;

level = irq_get_level(irq);
STRUCT_SECTION_FOREACH_ALTERNATE(intc_table, _irq_parent_entry, intc) {
level = irq_get_level(irq);
if (level == 2) {
parent_irq = irq_parent_level_2(irq);
} else if (level == 3) {
parent_irq = irq_parent_level_3(irq);
} else {
return NULL;
}

if (level == 2U) {
parent_irq = irq_parent_level_2(irq);
entry = get_parent_entry(parent_irq,
_lvl2_irq_list,
CONFIG_NUM_2ND_LEVEL_AGGREGATORS);
}
#ifdef CONFIG_3RD_LEVEL_INTERRUPTS
else if (level == 3U) {
parent_irq = irq_parent_level_3(irq);
entry = get_parent_entry(parent_irq,
_lvl3_irq_list,
CONFIG_NUM_3RD_LEVEL_AGGREGATORS);
if ((intc->level == level) && (intc->irq == parent_irq)) {
entry = intc;
}
}
#endif /* CONFIG_3RD_LEVEL_INTERRUPTS */

dev = entry != NULL ? entry->dev : NULL;

return dev;
}

unsigned int z_get_sw_isr_irq_from_device(const struct device *dev)
{
for (size_t i = 0U; i < CONFIG_NUM_2ND_LEVEL_AGGREGATORS; ++i) {
if (_lvl2_irq_list[i].dev == dev) {
return _lvl2_irq_list[i].irq;
STRUCT_SECTION_FOREACH_ALTERNATE(intc_table, _irq_parent_entry, intc) {
if ((intc->level == level) && (intc->dev == dev)) {
return intc->irq;
}
}

#ifdef CONFIG_3RD_LEVEL_INTERRUPTS
for (size_t i = 0U; i < CONFIG_NUM_3RD_LEVEL_AGGREGATORS; ++i) {
if (_lvl3_irq_list[i].dev == dev) {
return _lvl3_irq_list[i].irq;
}
}
#endif /* CONFIG_3RD_LEVEL_INTERRUPTS */

return 0;
}

unsigned int z_get_sw_isr_table_idx(unsigned int irq)
{
unsigned int table_idx, level, parent_irq, local_irq, parent_offset;
unsigned int table_idx = irq, level, parent_irq, local_irq, parent_offset;
const struct _irq_parent_entry *entry = NULL;

level = irq_get_level(irq);

if (level == 2U) {
local_irq = irq_from_level_2(irq);
__ASSERT_NO_MSG(local_irq < CONFIG_MAX_IRQ_PER_AGGREGATOR);
parent_irq = irq_parent_level_2(irq);
entry = get_parent_entry(parent_irq,
_lvl2_irq_list,
CONFIG_NUM_2ND_LEVEL_AGGREGATORS);
parent_offset = entry != NULL ? entry->offset : 0U;
table_idx = parent_offset + local_irq;
}
#ifdef CONFIG_3RD_LEVEL_INTERRUPTS
else if (level == 3U) {
local_irq = irq_from_level_3(irq);
STRUCT_SECTION_FOREACH_ALTERNATE(intc_table, _irq_parent_entry, intc) {
level = irq_get_level(irq);
if (level == 2) {
local_irq = irq_from_level_2(irq);
parent_irq = irq_parent_level_2(irq);
} else if (level == 3) {
local_irq = irq_from_level_3(irq);
parent_irq = irq_parent_level_3(irq);
} else {
break;
}
__ASSERT_NO_MSG(local_irq < CONFIG_MAX_IRQ_PER_AGGREGATOR);
parent_irq = irq_parent_level_3(irq);
entry = get_parent_entry(parent_irq,
_lvl3_irq_list,
CONFIG_NUM_3RD_LEVEL_AGGREGATORS);
if ((intc->level == level) && (intc->irq == parent_irq)) {
entry = intc;
}
parent_offset = entry != NULL ? entry->offset : 0U;
table_idx = parent_offset + local_irq;
}
#endif /* CONFIG_3RD_LEVEL_INTERRUPTS */
else {
table_idx = irq;
}

table_idx -= CONFIG_GEN_IRQ_START_VECTOR;

Expand Down
4 changes: 4 additions & 0 deletions cmake/linker_script/common/common-rom.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ if (CONFIG_LOG)
zephyr_iterable_section(NAME log_backend KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN 4)
endif()

if (CONFIG_MULTI_LEVEL_INTERRUPTS)
zephyr_iterable_section(NAME intc_table KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN 4)
endif()

if (CONFIG_HTTP_SERVER)
zephyr_iterable_section(NAME http_service_desc KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN 4)
endif()
Expand Down
11 changes: 7 additions & 4 deletions drivers/interrupt_controller/intc_plic.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <zephyr/kernel.h>
#include <zephyr/arch/cpu.h>
#include <zephyr/device.h>
#include <zephyr/devicetree/interrupt_controller.h>
#include <zephyr/shell/shell.h>
#include <soc.h>

Expand Down Expand Up @@ -578,11 +579,13 @@ SHELL_CMD_ARG_REGISTER(plic, &plic_cmds, "PLIC shell commands",
PLIC_INTC_IRQ_FUNC_DEFINE(n)

#define PLIC_INTC_DEVICE_INIT(n) \
IRQ_PARENT_ENTRY_DEFINE( \
plic##n, DEVICE_DT_INST_GET(n), DT_INST_IRQN(n), \
INTC_ISR_TBL_OFFSET(n), \
DT_INST_INTC_GET_LEVEL(n)); \
PLIC_INTC_CONFIG_INIT(n) \
PLIC_INTC_DATA_INIT(n) \
DEVICE_DT_INST_DEFINE(n, &plic_init, NULL, \
PLIC_INTC_DATA(n), &plic_config_##n, \
PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, \
NULL);
DEVICE_DT_INST_DEFINE(n, &plic_init, NULL, PLIC_INTC_DATA(n), &plic_config_##n, \
PRE_KERNEL_1, CONFIG_INTC_INIT_PRIORITY, NULL);

DT_INST_FOREACH_STATUS_OKAY(PLIC_INTC_DEVICE_INIT)
70 changes: 28 additions & 42 deletions include/zephyr/devicetree.h
Original file line number Diff line number Diff line change
Expand Up @@ -2534,58 +2534,36 @@
DT_IRQ_INTC_BY_IDX(node_id, 0)

/**
* @cond INTERNAL_HIDDEN
* @brief Get a node's interrupt level
*
* @param node_id node identifier
* @return IRQ level
*/
#define DT_IRQ_LEVEL(node_id) DT_CAT(node_id, _IRQ_LEVEL)

/* DT helper macro to get interrupt-parent node */
#define DT_PARENT_INTC_INTERNAL(node_id) DT_PROP(node_id, interrupt_parent)
/* DT helper macro to get the node's interrupt grandparent node */
#define DT_GPARENT_INTC_INTERNAL(node_id) DT_PARENT_INTC_INTERNAL(DT_PARENT_INTC_INTERNAL(node_id))
/* DT helper macro to check if a node is an interrupt controller */
#define DT_IS_INTC_INTERNAL(node_id) DT_NODE_HAS_PROP(node_id, interrupt_controller)
/* DT helper macro to check if the node has a parent interrupt controller */
#define DT_HAS_PARENT_INTC_INTERNAL(node_id) \
/* node has `interrupt-parent`? */ \
IF_ENABLED(DT_NODE_HAS_PROP(node_id, interrupt_parent), \
/* `interrupt-parent` node is an interrupt controller? */ \
(IF_ENABLED(DT_IS_INTC_INTERNAL(DT_PARENT_INTC_INTERNAL(node_id)), \
/* `interrupt-parent` node has interrupt cell(s) ? 1 : 0 */ \
(COND_CODE_0(DT_NUM_IRQS(DT_PARENT_INTC_INTERNAL(node_id)), (0), \
(1))))))
/* DT helper macro to check if the node has a grandparent interrupt controller */
#define DT_HAS_GPARENT_INTC_INTERNAL(node_id) \
IF_ENABLED(DT_HAS_PARENT_INTC_INTERNAL(node_id), \
(DT_HAS_PARENT_INTC_INTERNAL(DT_PARENT_INTC_INTERNAL(node_id))))

/**
* DT helper macro to get the as-seen interrupt number in devicetree,
* or ARM GIC IRQ encoded output from `gen_defines.py`
*/
#define DT_IRQN_BY_IDX_INTERNAL(node_id, idx) DT_IRQ_BY_IDX(node_id, idx, irq)

/* DT helper macro to get the node's parent intc's (only) irq number */
#define DT_PARENT_INTC_IRQN_INTERNAL(node_id) DT_IRQ(DT_PARENT_INTC_INTERNAL(node_id), irq)
/* DT helper macro to get the node's grandparent intc's (only) irq number */
#define DT_GPARENT_INTC_IRQN_INTERNAL(node_id) DT_IRQ(DT_GPARENT_INTC_INTERNAL(node_id), irq)

/**
* @cond INTERNAL_HIDDEN
*/
/* DT helper macro to encode a node's IRQN to level 1 according to the multi-level scheme */
#define DT_IRQN_L1_INTERNAL(node_id, idx) DT_IRQ_BY_IDX(node_id, idx, irq)
/* DT helper macro to encode a node's IRQN to level 2 according to the multi-level scheme */
#define DT_IRQN_L2_INTERNAL(node_id, idx) \
(IRQ_TO_L2(DT_IRQN_BY_IDX_INTERNAL(node_id, idx)) | \
DT_PARENT_INTC_IRQN_INTERNAL(node_id))
(IRQ_TO_L2(DT_IRQN_L1_INTERNAL(node_id, idx)) | DT_IRQ(DT_IRQ_INTC(node_id), irq))
/* DT helper macro to encode a node's IRQN to level 3 according to the multi-level scheme */
#define DT_IRQN_L3_INTERNAL(node_id, idx) \
(IRQ_TO_L3(DT_IRQN_BY_IDX_INTERNAL(node_id, idx)) | \
IRQ_TO_L2(DT_PARENT_INTC_IRQN_INTERNAL(node_id)) | \
DT_GPARENT_INTC_IRQN_INTERNAL(node_id))
(IRQ_TO_L3(DT_IRQN_L1_INTERNAL(node_id, idx)) | \
IRQ_TO_L2(DT_IRQ(DT_IRQ_INTC(node_id), irq)) | \
DT_IRQ(DT_IRQ_INTC(DT_IRQ_INTC(node_id)), irq))
/* DT helper macro for the macros above */
#define DT_IRQN_LVL_IMPL_INTERNAL(node_id, idx, level) DT_IRQN_L##level##_INTERNAL(node_id, idx)
#define DT_IRQN_LVL_INTERNAL(...) DT_IRQN_LVL_IMPL_INTERNAL(__VA_ARGS__)

/**
* DT helper macro to encode a node's interrupt number according to the Zephyr's multi-level scheme
* See doc/kernel/services/interrupts.rst for details
*/
#define DT_MULTI_LEVEL_IRQN_INTERNAL(node_id, idx) \
COND_CODE_1(DT_HAS_GPARENT_INTC_INTERNAL(node_id), (DT_IRQN_L3_INTERNAL(node_id, idx)), \
(COND_CODE_1(DT_HAS_PARENT_INTC_INTERNAL(node_id), \
(DT_IRQN_L2_INTERNAL(node_id, idx)), \
(DT_IRQN_BY_IDX_INTERNAL(node_id, idx)))))
DT_IRQN_LVL_INTERNAL(node_id, idx, DT_IRQ_LEVEL(node_id))

/**
* INTERNAL_HIDDEN @endcond
Expand All @@ -2602,7 +2580,7 @@
#define DT_IRQN_BY_IDX(node_id, idx) \
COND_CODE_1(IS_ENABLED(CONFIG_MULTI_LEVEL_INTERRUPTS), \
(DT_MULTI_LEVEL_IRQN_INTERNAL(node_id, idx)), \
(DT_IRQN_BY_IDX_INTERNAL(node_id, idx)))
(DT_IRQ_BY_IDX(node_id, idx, irq)))

/**
* @brief Get a node's (only) irq number
Expand Down Expand Up @@ -3979,6 +3957,14 @@
#define DT_INST_IRQ_BY_IDX(inst, idx, cell) \
DT_IRQ_BY_IDX(DT_DRV_INST(inst), idx, cell)

/**
* @brief Get a `DT_DRV_COMPAT` interrupt level
*
* @param inst instance number
* @return IRQ level
*/
#define DT_INST_IRQ_LEVEL(inst) DT_IRQ_LEVEL(DT_DRV_INST(inst))

/**
* @brief Get a `DT_DRV_COMPAT` interrupt specifier's interrupt controller by index
* @param inst instance number
Expand Down
54 changes: 54 additions & 0 deletions include/zephyr/devicetree/interrupt_controller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* @file
* @brief Interrupt controller devicetree macro public API header file.
*/

/*
* Copyright (c) 2023 Meta
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_INCLUDE_DEVICETREE_INTERRUPT_CONTROLLER_H_
#define ZEPHYR_INCLUDE_DEVICETREE_INTERRUPT_CONTROLLER_H_

#ifdef __cplusplus
extern "C" {
#endif

#include <zephyr/devicetree.h>
#include <zephyr/sys/util_macro.h>

/**
* @defgroup devicetree-interrupt_controller Devicetree Interrupt Controller API
* @ingroup devicetree
* @{
*/

/**
* @brief Get the order of an interrupt controller
*
* @param node_id node identifier of an interrupt controller
*
* @return Order of the interrupt controller
*/
#define DT_INTC_GET_LEVEL(node_id) UTIL_INC(DT_CAT(node_id, _IRQ_LEVEL))

/**
* @brief Get the `DT_DRV_COMPAT` order of an interrupt controller
*
* @param inst instance of an interrupt controller
*
* @return Order of the interrupt controller
*/
#define DT_INST_INTC_GET_LEVEL(inst) DT_INTC_GET_LEVEL(DT_DRV_INST(inst))

/**
* @}
*/

#ifdef __cplusplus
}
#endif

#endif /* ZEPHYR_INCLUDE_DEVICETREE_INTERRUPT_CONTROLLER_H_ */
2 changes: 2 additions & 0 deletions include/zephyr/linker/common-rom.ld
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@

#include <zephyr/linker/common-rom/common-rom-debug.ld>

#include <zephyr/linker/common-rom/common-rom-interrupt-controllers.ld>

#include <zephyr/linker/common-rom/common-rom-misc.ld>
Loading

0 comments on commit f2ccfdc

Please sign in to comment.