arch: sw_isr: revamp multi-level interrupt architecture
Previously the multi-level irq lookup table is generated by looping through the devicetree nodes using macros & Kconfig, which is hard to read and flimsy. This PR shifts the heavy lifting to devicetree & DT macros such that an interrupt controller driver, which has its info in the devicetree, can register itself directly with the multi-level interrupt architecture, which is more straightforward. The previous auto-generated look up table with macros is now moved in a file of its own. A new compatibility Kconfig: `CONFIG_LEGACY_MULTI_LEVEL_TABLE_GENERATION` is added and enabled by default to compile the legacy look up table for interrupt controller drivers that aren't updated to support the new architecture yet. Signed-off-by: Yong Cong Sin <ycsin@meta.com>
This commit is contained in:
parent
84da6c8e6c
commit
c5f5b964c1
12 changed files with 307 additions and 129 deletions
|
@ -22,6 +22,8 @@ zephyr_library_sources_ifdef(
|
||||||
multilevel_irq.c
|
multilevel_irq.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
zephyr_library_sources_ifdef(CONFIG_LEGACY_MULTI_LEVEL_TABLE_GENERATION multilevel_irq_legacy.c)
|
||||||
|
|
||||||
zephyr_library_sources_ifdef(CONFIG_SHARED_INTERRUPTS shared_irq.c)
|
zephyr_library_sources_ifdef(CONFIG_SHARED_INTERRUPTS shared_irq.c)
|
||||||
|
|
||||||
if(NOT CONFIG_ARCH_HAS_TIMING_FUNCTIONS AND
|
if(NOT CONFIG_ARCH_HAS_TIMING_FUNCTIONS AND
|
||||||
|
|
|
@ -15,3 +15,13 @@ config SEMIHOST
|
||||||
https://github.com/riscv/riscv-semihosting-spec/blob/main/riscv-semihosting-spec.adoc
|
https://github.com/riscv/riscv-semihosting-spec/blob/main/riscv-semihosting-spec.adoc
|
||||||
This option is compatible with hardware and with QEMU, through the
|
This option is compatible with hardware and with QEMU, through the
|
||||||
(automatic) use of the -semihosting-config switch when invoking it.
|
(automatic) use of the -semihosting-config switch when invoking it.
|
||||||
|
|
||||||
|
config LEGACY_MULTI_LEVEL_TABLE_GENERATION
|
||||||
|
bool "Auto generates the multi-level interrupt LUT (deprecated)"
|
||||||
|
default y
|
||||||
|
select DEPRECATED
|
||||||
|
depends on MULTI_LEVEL_INTERRUPTS
|
||||||
|
depends on !PLIC
|
||||||
|
help
|
||||||
|
A make-shift Kconfig to continue generating the multi-level interrupt LUT
|
||||||
|
with the legacy way using DT macros.
|
||||||
|
|
|
@ -15,162 +15,71 @@ BUILD_ASSERT((CONFIG_NUM_2ND_LEVEL_AGGREGATORS * CONFIG_MAX_IRQ_PER_AGGREGATOR)
|
||||||
BIT(CONFIG_2ND_LEVEL_INTERRUPT_BITS),
|
BIT(CONFIG_2ND_LEVEL_INTERRUPT_BITS),
|
||||||
"L2 bits not enough to cover the number of L2 IRQs");
|
"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,
|
* @brief Get the aggregator that's responsible for the given irq
|
||||||
* this assumes only one device has the IRQN
|
*
|
||||||
* @param _irq irq number
|
* @param irq IRQ number to query
|
||||||
* @return node_id(s) that has the `_irq` number, or empty if none of them has the `_irq`
|
*
|
||||||
|
* @return Aggregator entry, NULL if irq is level 1 or not found.
|
||||||
*/
|
*/
|
||||||
#define INTC_DT_IRQN_GET(_irq) \
|
static const struct _irq_parent_entry *get_intc_entry_for_irq(unsigned int 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 unsigned int level = irq_get_level(irq);
|
||||||
const struct _irq_parent_entry *entry = NULL;
|
|
||||||
|
|
||||||
for (i = 0U; i < length; ++i) {
|
/* 1st level aggregator is not registered */
|
||||||
if (list[i].irq == parent_irq) {
|
if (level == 1) {
|
||||||
entry = &list[i];
|
return NULL;
|
||||||
break;
|
}
|
||||||
|
|
||||||
|
const unsigned int intc_irq = irq_get_intc_irq(irq);
|
||||||
|
|
||||||
|
/* Find an aggregator entry that matches the level & intc_irq */
|
||||||
|
STRUCT_SECTION_FOREACH_ALTERNATE(intc_table, _irq_parent_entry, intc) {
|
||||||
|
if ((intc->level == level) && (intc->irq == intc_irq)) {
|
||||||
|
return intc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
__ASSERT(i != length, "Invalid argument: %i", parent_irq);
|
return NULL;
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct device *z_get_sw_isr_device_from_irq(unsigned int irq)
|
const struct device *z_get_sw_isr_device_from_irq(unsigned int irq)
|
||||||
{
|
{
|
||||||
const struct device *dev = NULL;
|
const struct _irq_parent_entry *intc = get_intc_entry_for_irq(irq);
|
||||||
unsigned int level, parent_irq;
|
|
||||||
const struct _irq_parent_entry *entry = NULL;
|
|
||||||
|
|
||||||
level = irq_get_level(irq);
|
__ASSERT(intc != NULL, "can't find an aggregator to handle irq(%X)", irq);
|
||||||
|
|
||||||
if (level == 2U) {
|
return intc != NULL ? intc->dev : NULL;
|
||||||
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);
|
|
||||||
}
|
|
||||||
#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)
|
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) {
|
/* Get the IRQN for the aggregator */
|
||||||
if (_lvl2_irq_list[i].dev == dev) {
|
STRUCT_SECTION_FOREACH_ALTERNATE(intc_table, _irq_parent_entry, intc) {
|
||||||
return _lvl2_irq_list[i].irq;
|
if (intc->dev == dev) {
|
||||||
|
return intc->irq;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_3RD_LEVEL_INTERRUPTS
|
__ASSERT(false, "dev(%p) not found", dev);
|
||||||
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int z_get_sw_isr_table_idx(unsigned int irq)
|
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, local_irq;
|
||||||
const struct _irq_parent_entry *entry = NULL;
|
const struct _irq_parent_entry *intc = get_intc_entry_for_irq(irq);
|
||||||
|
const unsigned int level = irq_get_level(irq);
|
||||||
|
|
||||||
level = irq_get_level(irq);
|
if (intc != NULL) {
|
||||||
|
local_irq = level == 2 ? irq_from_level_2(irq) : irq_from_level_3(irq);
|
||||||
|
__ASSERT_NO_MSG(local_irq < CONFIG_MAX_IRQ_PER_AGGREGATOR);
|
||||||
|
|
||||||
if (level == 2U) {
|
table_idx = intc->offset + local_irq;
|
||||||
local_irq = irq_from_level_2(irq);
|
} else {
|
||||||
__ASSERT_NO_MSG(local_irq < CONFIG_MAX_IRQ_PER_AGGREGATOR);
|
/* irq level must be 1 if no intc entry */
|
||||||
parent_irq = irq_parent_level_2(irq);
|
__ASSERT(level == 1, "can't find an aggregator to handle irq(%X)", 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);
|
|
||||||
__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);
|
|
||||||
parent_offset = entry != NULL ? entry->offset : 0U;
|
|
||||||
table_idx = parent_offset + local_irq;
|
|
||||||
}
|
|
||||||
#endif /* CONFIG_3RD_LEVEL_INTERRUPTS */
|
|
||||||
else {
|
|
||||||
table_idx = irq;
|
table_idx = irq;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
72
arch/common/multilevel_irq_legacy.c
Normal file
72
arch/common/multilevel_irq_legacy.c
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018 Intel Corporation.
|
||||||
|
* Copyright (c) 2024 Meta.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/sw_isr_table.h>
|
||||||
|
#include <zephyr/sys/util.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* @brief This file houses the deprecated legacy macros-generated multi-level interrupt lookup
|
||||||
|
* table code, compiled when `CONFIG_LEGACY_MULTI_LEVEL_TABLE_GENERATION` is enabled.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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)
|
||||||
|
|
||||||
|
#define INIT_IRQ_PARENT_OFFSET_2ND(n, d, i, o) \
|
||||||
|
IRQ_PARENT_ENTRY_DEFINE(intc_l2_##n, DEVICE_DT_GET_OR_NULL(d), i, o, 2)
|
||||||
|
|
||||||
|
#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_2ND(i, INTC_DT_IRQN_GET(CONFIG_2ND_LVL_INTR_0##i##_OFFSET), \
|
||||||
|
CONFIG_2ND_LVL_INTR_0##i##_OFFSET, \
|
||||||
|
IRQ_INDEX_TO_OFFSET(i, base))
|
||||||
|
|
||||||
|
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 INIT_IRQ_PARENT_OFFSET_3RD(n, d, i, o) \
|
||||||
|
IRQ_PARENT_ENTRY_DEFINE(intc_l3_##n, DEVICE_DT_GET_OR_NULL(d), i, o, 3)
|
||||||
|
|
||||||
|
#define CAT_3RD_LVL_LIST(i, base) \
|
||||||
|
INIT_IRQ_PARENT_OFFSET_3RD(i, INTC_DT_IRQN_GET(CONFIG_3RD_LVL_INTR_0##i##_OFFSET), \
|
||||||
|
CONFIG_3RD_LVL_INTR_0##i##_OFFSET, \
|
||||||
|
IRQ_INDEX_TO_OFFSET(i, base))
|
||||||
|
|
||||||
|
LISTIFY(CONFIG_NUM_3RD_LEVEL_AGGREGATORS, CAT_3RD_LVL_LIST, (;), CONFIG_3RD_LVL_ISR_TBL_OFFSET);
|
||||||
|
|
||||||
|
#endif /* CONFIG_3RD_LEVEL_INTERRUPTS */
|
|
@ -205,6 +205,10 @@ if (CONFIG_LOG)
|
||||||
zephyr_iterable_section(NAME log_backend KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN CONFIG_LINKER_ITERABLE_SUBALIGN)
|
zephyr_iterable_section(NAME log_backend KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN CONFIG_LINKER_ITERABLE_SUBALIGN)
|
||||||
endif()
|
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)
|
if (CONFIG_HTTP_SERVER)
|
||||||
zephyr_iterable_section(NAME http_service_desc KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN CONFIG_LINKER_ITERABLE_SUBALIGN)
|
zephyr_iterable_section(NAME http_service_desc KVMA RAM_REGION GROUP RODATA_REGION SUBALIGN CONFIG_LINKER_ITERABLE_SUBALIGN)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <zephyr/kernel.h>
|
#include <zephyr/kernel.h>
|
||||||
#include <zephyr/arch/cpu.h>
|
#include <zephyr/arch/cpu.h>
|
||||||
#include <zephyr/device.h>
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/devicetree/interrupt_controller.h>
|
||||||
#include <zephyr/shell/shell.h>
|
#include <zephyr/shell/shell.h>
|
||||||
|
|
||||||
#include <zephyr/sw_isr_table.h>
|
#include <zephyr/sw_isr_table.h>
|
||||||
|
@ -576,6 +577,10 @@ SHELL_CMD_ARG_REGISTER(plic, &plic_cmds, "PLIC shell commands",
|
||||||
PLIC_INTC_IRQ_FUNC_DEFINE(n)
|
PLIC_INTC_IRQ_FUNC_DEFINE(n)
|
||||||
|
|
||||||
#define PLIC_INTC_DEVICE_INIT(n) \
|
#define PLIC_INTC_DEVICE_INIT(n) \
|
||||||
|
IRQ_PARENT_ENTRY_DEFINE( \
|
||||||
|
plic##n, DEVICE_DT_INST_GET(n), DT_INST_IRQN(n), \
|
||||||
|
INTC_INST_ISR_TBL_OFFSET(n), \
|
||||||
|
DT_INST_INTC_GET_AGGREGATOR_LEVEL(n)); \
|
||||||
PLIC_INTC_CONFIG_INIT(n) \
|
PLIC_INTC_CONFIG_INIT(n) \
|
||||||
PLIC_INTC_DATA_INIT(n) \
|
PLIC_INTC_DATA_INIT(n) \
|
||||||
DEVICE_DT_INST_DEFINE(n, &plic_init, NULL, \
|
DEVICE_DT_INST_DEFINE(n, &plic_init, NULL, \
|
||||||
|
|
60
include/zephyr/devicetree/interrupt_controller.h
Normal file
60
include/zephyr/devicetree/interrupt_controller.h
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Meta
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* @brief Interrupt controller devicetree macro public API header file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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 aggregator level of an interrupt controller
|
||||||
|
*
|
||||||
|
* @note Aggregator level is equivalent to IRQ_LEVEL + 1 (a 2nd level aggregator has Zephyr level 1
|
||||||
|
* IRQ encoding)
|
||||||
|
*
|
||||||
|
* @param node_id node identifier of an interrupt controller
|
||||||
|
*
|
||||||
|
* @return Level of the interrupt controller
|
||||||
|
*/
|
||||||
|
#define DT_INTC_GET_AGGREGATOR_LEVEL(node_id) UTIL_INC(DT_IRQ_LEVEL(node_id))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the aggregator level of a `DT_DRV_COMPAT` interrupt controller
|
||||||
|
*
|
||||||
|
* @note Aggregator level is equivalent to IRQ_LEVEL + 1 (a 2nd level aggregator has Zephyr level 1
|
||||||
|
* IRQ encoding)
|
||||||
|
*
|
||||||
|
* @param inst instance of an interrupt controller
|
||||||
|
*
|
||||||
|
* @return Level of the interrupt controller
|
||||||
|
*/
|
||||||
|
#define DT_INST_INTC_GET_AGGREGATOR_LEVEL(inst) DT_INTC_GET_AGGREGATOR_LEVEL(DT_DRV_INST(inst))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* ZEPHYR_INCLUDE_DEVICETREE_INTERRUPT_CONTROLLER_H_ */
|
|
@ -166,6 +166,23 @@ static inline unsigned int irq_parent_level_3(unsigned int irq)
|
||||||
BIT_MASK(CONFIG_2ND_LEVEL_INTERRUPT_BITS);
|
BIT_MASK(CONFIG_2ND_LEVEL_INTERRUPT_BITS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the parent interrupt controller IRQ of the given IRQ number
|
||||||
|
*
|
||||||
|
* @param irq IRQ number in its zephyr format
|
||||||
|
*
|
||||||
|
* @return IRQ of the interrupt controller
|
||||||
|
*/
|
||||||
|
static inline unsigned int irq_get_intc_irq(unsigned int irq)
|
||||||
|
{
|
||||||
|
const unsigned int level = irq_get_level(irq);
|
||||||
|
|
||||||
|
__ASSERT_NO_MSG(level > 1 && level <= 3);
|
||||||
|
|
||||||
|
return irq & BIT_MASK(CONFIG_1ST_LEVEL_INTERRUPT_BITS +
|
||||||
|
(level == 3 ? CONFIG_2ND_LEVEL_INTERRUPT_BITS : 0));
|
||||||
|
}
|
||||||
|
|
||||||
#endif /* CONFIG_MULTI_LEVEL_INTERRUPTS */
|
#endif /* CONFIG_MULTI_LEVEL_INTERRUPTS */
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,4 +14,6 @@
|
||||||
|
|
||||||
#include <zephyr/linker/common-rom/common-rom-debug.ld>
|
#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>
|
#include <zephyr/linker/common-rom/common-rom-misc.ld>
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Meta
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/linker/iterable_sections.h>
|
||||||
|
|
||||||
|
ITERABLE_SECTION_ROM(intc_table, 4)
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#if !defined(_ASMLANGUAGE)
|
#if !defined(_ASMLANGUAGE)
|
||||||
#include <zephyr/device.h>
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/sys/iterable_sections.h>
|
||||||
#include <zephyr/types.h>
|
#include <zephyr/types.h>
|
||||||
#include <zephyr/toolchain.h>
|
#include <zephyr/toolchain.h>
|
||||||
#include <zephyr/sys/util.h>
|
#include <zephyr/sys/util.h>
|
||||||
|
@ -47,10 +48,84 @@ extern struct _isr_table_entry _sw_isr_table[];
|
||||||
|
|
||||||
struct _irq_parent_entry {
|
struct _irq_parent_entry {
|
||||||
const struct device *dev;
|
const struct device *dev;
|
||||||
|
unsigned int level;
|
||||||
unsigned int irq;
|
unsigned int irq;
|
||||||
unsigned int offset;
|
unsigned int offset;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @cond INTERNAL_HIDDEN
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Mapping between aggregator level to order */
|
||||||
|
#define Z_STR_L2 2ND
|
||||||
|
#define Z_STR_L3 3RD
|
||||||
|
/**
|
||||||
|
* @brief Get the Software ISR table offset Kconfig for the given aggregator level
|
||||||
|
*
|
||||||
|
* @param l Aggregator level, must be 2 or 3
|
||||||
|
*
|
||||||
|
* @return `CONFIG_2ND_LVL_ISR_TBL_OFFSET` if second level aggregator,
|
||||||
|
* `CONFIG_3RD_LVL_ISR_TBL_OFFSET` if third level aggregator
|
||||||
|
*/
|
||||||
|
#define Z_SW_ISR_TBL_KCONFIG_BY_ALVL(l) CONCAT(CONFIG_, CONCAT(Z_STR_L, l), _LVL_ISR_TBL_OFFSET)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL_HIDDEN @endcond
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get an interrupt controller node's level base ISR table offset.
|
||||||
|
*
|
||||||
|
* @param node_id node identifier of the interrupt controller
|
||||||
|
*
|
||||||
|
* @return `CONFIG_2ND_LVL_ISR_TBL_OFFSET` if node_id is a second level aggregator,
|
||||||
|
* `CONFIG_3RD_LVL_ISR_TBL_OFFSET` if it is a third level aggregator
|
||||||
|
*/
|
||||||
|
#define INTC_BASE_ISR_TBL_OFFSET(node_id) \
|
||||||
|
Z_SW_ISR_TBL_KCONFIG_BY_ALVL(DT_INTC_GET_AGGREGATOR_LEVEL(node_id))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the SW ISR table offset for an instance of interrupt controller
|
||||||
|
*
|
||||||
|
* @param inst DT_DRV_COMPAT interrupt controller driver instance number
|
||||||
|
*
|
||||||
|
* @return Software ISR table offset of the interrupt controller
|
||||||
|
*/
|
||||||
|
#define INTC_INST_ISR_TBL_OFFSET(inst) \
|
||||||
|
(INTC_BASE_ISR_TBL_OFFSET(DT_DRV_INST(inst)) + (inst * CONFIG_MAX_IRQ_PER_AGGREGATOR))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the SW ISR table offset for a child interrupt controller
|
||||||
|
*
|
||||||
|
* @details This macro is a alternative form of the `INTC_INST_ISR_TBL_OFFSET`. This is used by
|
||||||
|
* pseudo interrupt controller devices that are child of a main interrupt controller device.
|
||||||
|
*
|
||||||
|
* @param node_id node identifier of the child interrupt controller
|
||||||
|
*
|
||||||
|
* @return Software ISR table offset of the child
|
||||||
|
*/
|
||||||
|
#define INTC_CHILD_ISR_TBL_OFFSET(node_id) \
|
||||||
|
(INTC_BASE_ISR_TBL_OFFSET(node_id) + \
|
||||||
|
(DT_NODE_CHILD_IDX(node_id) * CONFIG_MAX_IRQ_PER_AGGREGATOR))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register an interrupt controller with the software ISR table
|
||||||
|
*
|
||||||
|
* @param _name Name of the interrupt controller (must be unique)
|
||||||
|
* @param _dev Pointer to the interrupt controller device instance
|
||||||
|
* @param _irq Interrupt controller IRQ number
|
||||||
|
* @param _offset Software ISR table offset of the interrupt controller
|
||||||
|
* @param _level Interrupt controller aggregator level
|
||||||
|
*/
|
||||||
|
#define IRQ_PARENT_ENTRY_DEFINE(_name, _dev, _irq, _offset, _level) \
|
||||||
|
static const STRUCT_SECTION_ITERABLE_ALTERNATE(intc_table, _irq_parent_entry, _name) = { \
|
||||||
|
.dev = _dev, \
|
||||||
|
.level = _level, \
|
||||||
|
.irq = _irq, \
|
||||||
|
.offset = _offset, \
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Data structure created in a special binary .intlist section for each
|
* Data structure created in a special binary .intlist section for each
|
||||||
* configured interrupt. gen_irq_tables.py pulls this out of the binary and
|
* configured interrupt. gen_irq_tables.py pulls this out of the binary and
|
||||||
|
|
|
@ -407,7 +407,9 @@ static void test_multi_level_bit_masks_fn(uint32_t irq1, uint32_t irq2, uint32_t
|
||||||
const bool has_l3 = irq3 > 0;
|
const bool has_l3 = irq3 > 0;
|
||||||
const bool has_l2 = irq2 > 0;
|
const bool has_l2 = irq2 > 0;
|
||||||
const uint32_t level = has_l3 ? 3 : has_l2 ? 2 : 1;
|
const uint32_t level = has_l3 ? 3 : has_l2 ? 2 : 1;
|
||||||
const uint32_t irqn = (irq3 << l3_shift) | (irq2 << l2_shift) | irq1;
|
const uint32_t irqn_l1 = irq1;
|
||||||
|
const uint32_t irqn_l2 = (irq2 << l2_shift) | irqn_l1;
|
||||||
|
const uint32_t irqn = (irq3 << l3_shift) | irqn_l2;
|
||||||
|
|
||||||
zassert_equal(level, irq_get_level(irqn));
|
zassert_equal(level, irq_get_level(irqn));
|
||||||
|
|
||||||
|
@ -422,6 +424,17 @@ static void test_multi_level_bit_masks_fn(uint32_t irq1, uint32_t irq2, uint32_t
|
||||||
zassert_equal((hwirq3 + 1) << l3_shift, irq_to_level_3(hwirq3));
|
zassert_equal((hwirq3 + 1) << l3_shift, irq_to_level_3(hwirq3));
|
||||||
zassert_equal(hwirq2 + 1, irq_parent_level_3(irqn));
|
zassert_equal(hwirq2 + 1, irq_parent_level_3(irqn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (has_l3) {
|
||||||
|
zassert_equal(irqn_l2, irq_get_intc_irq(irqn));
|
||||||
|
} else if (has_l2) {
|
||||||
|
zassert_equal(irqn_l1, irq_get_intc_irq(irqn));
|
||||||
|
} else {
|
||||||
|
/* degenerate cases */
|
||||||
|
if (false) {
|
||||||
|
zassert_equal(irqn, irq_get_intc_irq(irqn));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ZTEST(gen_isr_table, test_multi_level_bit_masks_l1)
|
ZTEST(gen_isr_table, test_multi_level_bit_masks_l1)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue