-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
drivers: Add interrupt controller API #66505
base: main
Are you sure you want to change the base?
drivers: Add interrupt controller API #66505
Conversation
b2fb53d
to
47d66a9
Compare
47d66a9
to
2fdb4b7
Compare
Arch WG meeting:
|
451f810
to
7fac99e
Compare
878730f
to
d25a73c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So... I don't hate the API, it's clean and reasonable (though it's also wordy, requiring three steps to do what a single IRQ_CONNECT() does right now).
But this seems like only about 1/3 of the solution. This is all about the user/driver/subsystem-facing API. But the API we have for that is really... not so bad right now. What's the story for why we want to merge this? What can we do with it that we can't right now?
The problems with our interrupt layer (and it has problems!) are on the other side:
-
There's no management of interrupt controller cascade by the framework. Every platform needs to write its own glue to call child interrupt controller handlers out of their parents, even though the structure of that tree is known at build time by devicetree. This PR seems targetted at only NVIC platforms without a secondary controller, meaning that it's really not seeing the mess we're currently living with.
-
The interrupt ID/number/index/offset management (i.e. the mapping between a given device interrupt and the index into the _sw_isr_table[] array) is likewise ad-hoc and platform dependent, and is generally implemented with a bunch of crazy magic numbers stored in kconfig. And again, a clean mapping could be trivially derived from the graph of controllers known to DTS.
Also, it's worth pointing out that this scheme is throwing out efficiency features we've had for a long time: Interrupts are 100% runtime now, you have to write and call C code to get an interrupt registered, spending bytes on code that has to execute and RAM on data structures that have to be mutated. Some platforms even today are still RAM-constrained and would like their ISR tables in pre-built flash. Maybe we don't care about apps like that anymore, it's an argument worth having. But we should be clear about what we're walking away from.
tl;dr: I guess my feeling is that if we're going to rewrite interrupt handling, we should sharpen the machetes and do it all, for everyone.
#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING) | ||
#define IRQ_TYPE_LEVEL_HIGH (1U << 2) | ||
#define IRQ_TYPE_LEVEL_LOW (1U << 3) | ||
#define IRQ_TYPE_MASK (IRQ_TYPE_EDGE_BOTH | IRQ_TYPE_LEVEL_HIGH | IRQ_TYPE_LEVEL_LOW) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: use BIT() instead of manual shifts if you must have these be bitwise.
But I think having this be a simple integer enumerant (though not an enum, as this is DTS code) is probably better, the trickery with BOTH doesn't strike me as having much value vs. just testing for RISING/FALLING separately. In almost all cases code (especially devicetree code!) doesn't actually do runtime inspection of interrupt types anyway, it just sets the value and assumes the driver does it right.
include/zephyr/drivers/intc.h
Outdated
* @note Identical to INTC_DT_GET_IRQ_SPEC_BY_NAME() if IRQ exists | ||
* @see INTC_DT_GET_IRQ_SPEC_BY_NAME() | ||
*/ | ||
#define INTC_DT_GET_OPT_IRQ_SPEC_BY_NAME(node_id, name) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having a hard time understanding the need for this API? Generally with static data like devicetree you either know or don't know if a node exists, which you can already test in the consumer's C code with more readable constructs like "#if". Or maybe you have an array of things and want a FOR_EACH enumerator. What's the situation where we have a device and a name for an interrupt that... might not exist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bus attached devices like sensors and RTCs are the rational for this API. Sensors often have 2-3 GPIO routed optional APIs, and there may be more than one instance of them, making IS_ENABLED
and similar quite complex. See
zephyr/drivers/sensor/lis2dh/lis2dh.c
Lines 515 to 526 in 30c6601
#define LIS2DH_CFG_INT(inst) \ | |
.gpio_drdy = \ | |
COND_CODE_1(ANYM_ON_INT1(inst), \ | |
({.port = NULL, .pin = 0, .dt_flags = 0}), \ | |
(GPIO_DT_SPEC_INST_GET_BY_IDX_COND(inst, irq_gpios, 0))), \ | |
.gpio_int = \ | |
COND_CODE_1(ANYM_ON_INT1(inst), \ | |
(GPIO_DT_SPEC_INST_GET_BY_IDX_COND(inst, irq_gpios, 0)), \ | |
(GPIO_DT_SPEC_INST_GET_BY_IDX_COND(inst, irq_gpios, 1))), \ | |
.int1_mode = DT_INST_PROP(inst, int1_gpio_config), \ | |
.int2_mode = DT_INST_PROP(inst, int2_gpio_config), | |
#else |
include/zephyr/drivers/intc.h
Outdated
|
||
/** Interrupt controller API structure */ | ||
struct intc_driver_api { | ||
intc_api_request_irq request_irq; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't generally pass callback pointers as typed data. Putting the types into the api struct directly instead of typedefing them all would save a bunch of lines of code. Surely those typedefs aren't actually used anywhere else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it looks good, it may be more lines, but every API in the struct does not have to be split into multiple lines :)
include/zephyr/drivers/intc.h
Outdated
/** @cond INTERNAL_HIDDEN */ | ||
|
||
/** Interrupt request specification */ | ||
struct intc_irq_spec { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, API-wise, this is eliminating the old-style "interrupt number" scheme that our API has exposed, where there was a single integer for each registrable interrupt. That was a simpler API for users. This one is more complicated.
But it was also a simpler API for the backend, where that ID became an index into a big table of function pointers. I don't see how interpreting this thing in the interrupt entry path (literally the hottest of hot paths!) is anything but a loss? Or is the global index secretly stored somewhere else?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue with the old-style number is that while being simple, it is lacking vital information, namely which interrupt controller the interrupt belongs to. The old style number is therefore only reflective of the devicetree, allowing DT_IRQN
to return the correct IRQ number, if there is only one interrupt controller, at which point the instance can be omitted, and the API can just be static (as it is now).
An interrupt number is the interrupt line number of a specific interrupt controller. To clearly visualize the issue, consider how you would rewrite the GPIO API to only use an integer for the pin number, that also has to work with bus attached IO expanders (implementing the GPIO API)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is lacking vital information
It's not. Or rather it doesn't have to be. The controller is extractable if you know how the table is packed, which right now requires interpreting a bunch of kconfig values storing sizes and offets, and it's a mess. Which is why I'd like to see it reworked.
But that's not an indictment of using an integer. We can totally continue to use integers to identify interrupts. And (implemented correctly) integers have some excellent efficiency benefits which would be good to preserve.
(Also, just to repeat: this API requires a separate step to construct and extract that spec struct, vs. just using a by-value macro like DT_IRQN(). That right off the bat makes this a worse API, not a better one. It's not a critical problem, but it's still a cost we're paying for exposing the spec abstraction to the user)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you see the parallel I'm drawing between the GPIO API and the IRQ API? We have no problem pairing a GPIO controller to a pin and passing this to the GPIO API (in fact, its rather simple and elegant IMO), why treat IRQs differently?
include/zephyr/drivers/intc.h
Outdated
/** Interrupt request priority */ | ||
uint16_t priority; | ||
/** Interrupt request flags */ | ||
uint32_t flags; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're using restricted types for irq and priority but giving a whole word to flags? My suggestion would be to bit-pack these into a single word, maybe even encode the intc as a small integer index (since those will always be DTS-defined and known at build time).
But also note that this isn't sufficient in all cases anyway, in partcular x86 MSI has a very different model for how to target an interrupt. That's not a blocker (MSI is just weird and probably will always be special-cased), but is an argument for maybe not overdesigning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the priority and irq number are integers, which will probably never exceed an 65365, but do exceed 255, where the flags is a bitfield, so it should be as large as we can provide :)
That said, I may update flags to be a size_t, but it does seem MSI fields can fit inside a uint32 https://i.stack.imgur.com/RwenJ.jpg
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this is fine for MSI.
Anyway, the implementation of MSI/MSI-X is currently very much arch specific, that is: x86 specific. So there won't be any collision with such generic intc API.
It could change someday, since interrupt management implementation in x86 is messy to say the least (you always need to allocate an irq to get a vector for instance, that does not make much sense in case of MSI-X or multi-vector MSI).
@bjarki-andreasen Ask x86 maintainers if they would be willing to redo-x86 interrupt management, then it could affect your API for MSI multi-vector and MSI-X since these play with vectors and not irq. MSI/MSI-X interrupt management would need to be reworked as well then (from an API point of view I mean).
include/zephyr/drivers/intc.h
Outdated
|
||
/** Interrupt request handle */ | ||
struct intc_irq { | ||
/** Interrupt request single-linked list node */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A dynamic list pointer here tells me this probably doesn't go in a simple table of function pointers anymore?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it doesn't go anywhere? I can't find code that uses this. Is this a forgotten attempt to implement shared interrupts? As of right now it doesn't seem to work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its supposed to be used for shared IRQs, and for secondary interrupt controllers like GPIOs, which usually only store a few IRQs relative to how many pins they have.
drivers/intc/intc_arm_v8m_nvic.c
Outdated
if (irq != NULL) { \ | ||
irq->handler(irq); \ | ||
} else { \ | ||
while (1) { \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not understanding this at all. Presumably you get here on a spurious interrupt? We have an existing spurious interrupt handler API the arch layers all honor, as that's a pretty routine glitch during device bringup (e.g. "oops typo in the interrupt number", or "oops, the bootloader left the UART RX interrupt enabled" -- absolutely routine things). Entering a silent infinite loop seems like exactly the wrong thing to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a reason this PR is a draft :D The driver I added here is very naive and only to test the viability of the API, it is not an optimal driver
Looks like the bot didn't assign reviewers, maybe because it's a draft? For sure @dcpleung @peter-mitsis @carlocaione @npitre want to get in on this. @tbursztyka should expand my note about MSI. Lots of platform folks will have important input too, especially ones who've had to deal with cascaded controllers. |
@andyross This API aims to solve points 1. and 2. while adding support for "secondary" interrupt controllers, like GPIO ports and other IO expanders which can route interrupts through IO pins.
|
Hope to continue this within a couple of weeks |
Add interrupt-lines property to interrupt-controller.yaml This value will initially be optional, but it is required when using the new system IRQ subsystem. Signed-off-by: Bjarki Arge Andreasen <[email protected]>
2ab95ef
to
b9bad34
Compare
Update
|
b9bad34
to
2f5932c
Compare
Update
|
2f5932c
to
c874df7
Compare
Update
|
c874df7
to
79b10f2
Compare
Update
|
This commit adds the interface for the system IRQ subsystem, which is split into three files: - include/zephyr/sys/irq.h contains the user facing APIs and macros, which includes creating interrupt handlers, and enabling/configuring/disabling interrupts. - include/zephyr/sys/irq_handler.h contains the interrupt line handlers interrupt controllers will call. - include/zephyr/sys/internal/irq.h contains internal structures required by irq.c The headers all require snippets generated by the script gen_sys_irq.py included with this commit. This commit adds a target and custom command to the top CMakeLists.txt to ensure snippets are generated if SYS_IRQ=y. Lastly, the source implementing the APIs provided with the interface, irq.c, is added. Signed-off-by: Bjarki Arge Andreasen <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a sample dts
file in the comment? The binding-API does not have full context, which confuses users (including me) :(
Like this
* intc1: interrupt-controller@... {
* compatible = "vnd,interrupt";
* #interrupt-cells = <2>;
* };
*
* intc2: interrupt-controller@... {
* compatible = "vnd,interrupt";
* #interrupt-cells = <2>;
* };
*
* n: node {
* interrupts-extended = <&intc1 3 2 15>,
* <&intc2 4 3 16>;
* };
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the following to the descriptions of the macros for getting IRQ flags from devicetree in include/zephyr/drivers/intc/irq_flags.h
:
* @details The encoding of the IRQ's flags is specific to each interrupt
* controller, tied to its devicetree compatible, defined in
* include/zephyr/drivers/intc/irq_flags/
Add intc.h device driver API header and inital Kconfig, CMakeLists.txt and driver folder. Signed-off-by: Bjarki Arge Andreasen <[email protected]>
Add irq_vector.h interface which wraps the existing ISR_DIRECT_DECLARE() macro adapting it to the system IRQ schema. ISR_DIRECT_DECLARE(name) {} is wrapped within this macro INTC_DT_DEFINE_IRQ_VECTOR(node_id) {} which uses the node identifier to synthesize the name of the declared function, strongly defining the weak default vector in the vector table. This allows for the existing PM macros, swap return value etc. to be reused with the system IRQ schema. Signed-off-by: Bjarki Arge Andreasen <[email protected]>
With the system IRQ INTC_DT_DEFINE_IRQ_VECTOR wrapper, the argument must be unwrapped before being used in the function. Using UTIL_CAT is therefore required. (it also looks nicer) Signed-off-by: Bjarki Arge Andreasen <[email protected]>
Add arm v8m nvic device driver implementing the new intc.h device driver API and interfacing with the new system IRQ subsystem. Signed-off-by: Bjarki Arge Andreasen <[email protected]>
Add ARM NVIC V7M device driver Signed-off-by: Bjarki Arge Andreasen <[email protected]>
Add interrupt-lines properties to the nrf5340 cpuapp and cpunet. Signed-off-by: Bjarki Arge Andreasen <[email protected]>
Update nrf_rtc_timer.c to use system IRQ. This includes updating the rtc1 nodes to be reserved rather than disabled. Signed-off-by: Bjarki Arge Andreasen <[email protected]>
Update clock_control_nrf.c device driver to use system IRQ Signed-off-by: Bjarki Arge Andreasen <[email protected]>
Update uart_nrfx_uarte.c to use system IRQ Signed-off-by: Bjarki Arge Andreasen <[email protected]>
Update gpio_nrfx.c to use system IRQ Signed-off-by: Bjarki Arge Andreasen <[email protected]>
Add power subsystem and power driver for nrf_power using system IRQ. TODO: Update nrfxlib to return handled or not, otherwise this or clock isr will never be called if both are implemented. Signed-off-by: Bjarki Arge Andreasen <[email protected]>
Add test suites to validate the sys_irq API, shared IRQs, dynamic IRQs and direct IRQs. Signed-off-by: Bjarki Arge Andreasen <[email protected]>
Add sys_irq section to docs Signed-off-by: Bjarki Arge Andreasen <[email protected]>
79b10f2
to
4dff6e0
Compare
Any and all configuration of INTLs is performed by SYS IRQ through the INTC device | ||
driver API. | ||
|
||
INTD (INTerrupt generating Device) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the abbreviation and the full name should be separated. Simply put, the whole process should be capitalized according to the first letter of the sentence, without deliberately showing the abbreviation.
For example
INTD (Interrupt generating device)
(Applicable to all changes.)
This PR has now been updated to implement the solution described in this issue #67583
Size comparisons:
(note that no dynamic irq is registered, so LTO optimizes out the dynamic irq list)
Some example output of the generated code :)
Generated handlers:
Generated IRQ enumerations:
A very cool solution I want to note as well is this macro:
which wraps the existing ARCH_ISR_DIRECT_DECLARE(name) macro synthesizing the name of the function to match the weakly defined default vector in the vector table, allowing the existing infrastructure to be used, including ISR_DIRECT_PM()
For arch review
This PR introduces two new requirements:
To generate interrupt handlers, but exclude device drivers, the status "reserved" is used to mean "supported by kernel, don't include a driver for it".
IRQ handlers will signal whether they handled the IRQ or not by returning 1 if handled, 0 if not handled.
Analyzing code
INTC_DT_DEFINE_IRQ_VECTOR with and without LTO
SYS_DT_DEFINE_IRQ_HANDLER without LTO
SYS_DT_DEFINE_IRQ_HANDLER with LTO
ISR_DIRECT_DECLARE without LTO
ISR_CONNECT with and without LTO