Open bjarki-andreasen opened 10 months ago
Some input from my side:
@rakons
I do believe we should get the flags of an IRQ from the devicetree, given that the flags are not hardware agnostic. Priority is only supported by some interrupt controllers, and could be encoded into the flags as well, so not sure about that one, however, just as an initial value, I think it is still valuable to store it, the application can override it later.
I can see how it would be neat to have the kernel allow that, but I do believe it could also be solved on a driver level using a switch statement, like:
foo_irq_handler(const void *data)
{
switch (radio_mode) {
case BLE:
foo_ble_isr(data);
break;
case WIFI:
foo_wifi_isr(data);
break;
};
}
which is entirely static :)
To be sure that we are using the same function for spurious vectors we could possibly create one function (like __sys_interrupt_spurious) and then generate weak aliases to that function
void __sys_interrupt_handler_1_2(void) __attribute__((weak, alias("__sys_interrupt_spurious"));
Note: static inline function cannot be weak. For the weak functions we have to have external linkage.
@rakons
The static inline handlers are not weak, they are strongly defined, its only the vectors which are weak :)
I think one level of misdirection is clearer than the attribute
static inline void __sys_interrupt_spurious(void)
{
z_fatal_error(K_ERR_SPURIOUS_IRQ, NULL);
}
static inline void __sys_interrupt_handler_1_2(void)
{
__sys_interrupt_spurious();
}
static inline void __sys_interrupt_handler_1_3(void)
{
__sys_interrupt_spurious();
}
...
Architecture WG:
Do we still need INTC_DT_INST_FLAGS
? I can implement it.
Introduction
The entire interrupt layout, including interrupt controllers, their interrupt lines, which lines are shared, and which lines are used, can be determined from the devicetree alone. Using the ordinals of interrupt controllers, interrupt generating devices, and the interrupt line numbers, we can synthesize symbols for every IRQ, including the handler and data passed to the handler, similar to encoding
struct device
with__device_dts_ord_<ord>
.This RFC details exactly how we can create a completely link optimizable interrupt controller subsystem, which can be entirely stored in ROM, while natively supporting shared interrupts and interrupt controller cascading (which may also be described as interrupt levels/aggregators in the current schema)
Note
The proposed solution has recently become possible after #66707 which allows us to get the interrupt controller of an IRQ from the devicetree, allowing us to bind an instance of an interrupt with an instance of a specific interrupt controller.
Problem description
Consider the following devicetree snippet:
From this we can determine the following:
intc
gpio0
timer0
andtimer1
shareintc
interrupt line number 0timer2
is disabled and as such does not require an IRQSolution
The solution is split into steps which follow:
Step 1: Deduce information from devicetree
We require the following for information for each IRQ entry
and the number of interrupt lines for each interrupt controller.
We can get the first 4 properties directly from the devicetree, however, the application must define the IRQ handler and IRQ data. Just like
struct device
s, we can declare both the IRQ handler and IRQ data which shall be stored in the IRQ entry as externs, and allow the application to define them. This simply requires a linkable symbol, which we can deduce from the devicetree.We will use the following properties to synthesize the linkable symbols:
intc_ord
intc_irq
igd_ord
We will need to add an additional devicetree property to interrupt-controllers to indicate the number of interrupt lines connected to generate the appropriate amound of IRQ handlers for each of them.
Step 2: Define all interrupt specifications
All interrupts in the system will be stored in a single const array. The mapping between an interrupt and its index in the array will be provided by definitions, synthesized from the interrupt's controller and line number.
The array itself contains a list of
struct sys_irq_spec
interrupt request specificationsFor example, enabling interrupt 0 of
intc0
(timer0 and timer1)Naturally,
SYS_DT_IRQN_1_0
will not be used directly, but generated from a macro likeSYS_DT_INST_IRQN(inst)
Step 3: Define IRQ handlers and data
IRQ handlers will be generated as static inline functions, which are called from the interrupt controllers. These are "normal" functions generated by the kernel, which must be called from a "safe" context (like
_isr_wrapper
or similar).The IRQ handler's name is synthesized like this:
Note that this contains the devicetree ordinal of the interrupt generating device, this is to avoid naming conflicts in the case of shared interrupts. The interrupt handler (1 handler per interrupt line) called by the interrupt controller is synthesized like this:
This handler will call every individual IRQ connected the the interrupt line, and optionally any dynamically added IRQ (see last step).
The following is a snippet of the generated handlers and externs.
The data and IRQ handler is defined by the application, just like a
struct device
, using a macro likeINTC_DT_INST_IRQ_HANDLER_DEFINE(0, handler, data)
which is used like:where
SYS_DT_INST_IRQ_HANDLER_DEFINE
for timer0 would expand to:Notice that we don't need to store the data passed to the handler outside of the macro itself.
Step 4: Define IRQ vector tables
Defining IRQ vector tables will be "offloaded" to the interrupt controller drivers themselves.
A general interrupt controller would create a static const "software" table and populate it with pointers to the
__sys_interrupt_handler_*_*
functions like this:A vectored interrupt controller would populate the table with wrappers, containing pre/post work, calling the
__sys_interrupt_handler_*_*
function in betweenThis allows the linker and compiler to fully optimize the interrupts from vector to handler.
Step 5: Defining direct IRQ handlers (vectors)
The names of the vectors in the previous step are not random :) For vectored interrupt controllers, their vectors are weakly defined, and named like this:
This allows for the application to strongly define (redefine) the interrupt vector, replacing the "default" wrapper. The application will use the following macro to redefine the IRQ vector:
This macro will use the devicetree compatible of the interrupt controller to automatically include the header/footer if required by the specific interrupt controller.
If the interrupt controller happened to have the compat
foo
, dts ord 4, and the interrupt number was 0, the macro would expand to:Connecting interrupts
Disclaimer: We should not do this, but...
To manually add a handler to an interrupt not defined in the devicetree, we can add an iterable section of irq handlers, that follow the naming of the interrupt handlers, which we can then find using elf parsing, and add to the appropriate interrupt handlers, similar to how we currently do
IRQ_CONNECT
Dynamic interrupts
Disclaimer: We should not do this, but...
Dynamic interrupts will be stored in a single linked lists stored in RAM, indexed by the
SYS_DT_IRQN_<intc_ord>_<intc_irq>
macro. This table would be iterated through by the__sys_interrupt_handler_*_*
handlers after the statically defined interrupts, if any exist. The application will define a context to be added to the single linked lists:Interrupt controller API
The interrupt controllers will expose an API to configure and control the individual interrupt lines. This API is a normal device driver API, which will be interacted with primarily through the System IRQ API.
Interrupt configuration
The configuration of an interrupt line is pretty much as vendor/device specific as it gets. All configurations will be encoded into a uint32_t, similar to GPIO flags. This includes level, priority, fast/slow irq, sense, delivery mode, etc. To do this generically, we will use the same approach as the
INTC_DT_INST_DIRECT_IRQ_HANDLER_DEFINE
to encode the interrupt cells into a uint32_t. For example, if the interrupt controller with compatible foo defines the interrupt cells:and using this specific definition as an example:
the flags would be gotten from the devicetree using
which would expand into
which would expand to the device/vendor specific macro which encodes the interrupt cells.
note that the IRQ is not encoded into the flags, it is stored separately.
System IRQ API
Users will not use the intc API directly when managing IRQs, this will be performed through the
sys_irq_*
APIs. This is necessary to ensure safe concurrent interactions with the IRQs, especially when requesting/releasing dynamic IRQs, which has to be performed with the interrupt line disabled.The system IRQ uses a global interrupt number assigned to each interrupt in the system. The following diagram shows how the global IRQ number maps to an actual interrupt line: Note that only enabled interrupts are included in the indexing, it is not possible to enable or configure a spurious interrupt :)
The following API is then used to configure and add/remove dynamic IRQs, taking the global interrupt request number:
Link-time optimization (LTO)
Very shortly, LTO allows the linker to change code like this
To this
by optimizing all modules as a single module, making symbols like
foo
andmy_irq_handler
visible from any module, allowing it to be inlined (at certain optimization levels) for example. This is only possible if symbols are preserved. This is one reason why the currently generated SW ISR table can not be optimized, since it's using addresses in memory, which the compiler/linker can not recognize.Conclusion
This can completely replace the existing solution, providing even more ROM efficiency since we only need to define a const table of
struct sys_irq_spec
for the ones that are actually used, and we don't need to store the IRQ handlers or data passed to the them in tables, allowing the compiler to optimize them. The solution also removes the need forNUM_IRQS
:)The solution also creates a clear separation of responsibilities between the system, and the interrupt controllers, and supports any combination, number of, type of, and "level" of interrupt controllers under the INTC and SYS IRQ APIs.