Interrupt definitions in DTS (device tree) files for Xilinx Zynq-7000 / ARM

This post was written by eli on August 4, 2012
Posted Under: ARM,Linux kernel,Zynq

Having some trouble to figure out what I should write in my own hand-written DTS entry for my logic, I ended up reading the sources of the Linux kernel (version 3.3, which is the currently used for Zynq). The purpose is to hook up a device defined in the PL of a Zynq-7000 (FPGA-style logic fabric) for my Zedboard, but at this time, the automatic generation tool for DTS I’ve written about ignores PL modules.

There’s also a more general post about the Device Tree.

So if the UART’s entry looks like this,

uart@e0001000 {
 compatible = "xlnx,ps7-uart-1.00.a";
 reg = <0xe0001000 0x1000>;
 interrupts = <0x0 0x32 0x0>;
 interrupt-parent = <&gic>;
 clock = <50000000>;

What does the “interrupts = <0x0 0x32 0x0>” part stand for? How do I get my definition right?

This is what I figure out. If you just want the bottom line, skip the walkthrough, which I left here for future reference (future kernels etc).

The walkthrough

These values are put into an of_irq structure, which is defined in include/linux/of_irq.h as follows:

 * of_irq - container for device_node/irq_specifier pair for an irq controller
 * @controller: pointer to interrupt controller device tree node
 * @size: size of interrupt specifier
 * @specifier: array of cells @size long specifing the specific interrupt
 * This structure is returned when an interrupt is mapped. The controller
 * field needs to be put() after use
#define OF_MAX_IRQ_SPEC        4 /* We handle specifiers of at most 4 cells */
struct of_irq {
 struct device_node *controller; /* Interrupt controller node */
 u32 size; /* Specifier size */
 u32 specifier[OF_MAX_IRQ_SPEC]; /* Specifier copy */

We have of_irq_map_one() in drivers/of/irq.c fetching the “interrupt” property from the device tree, calling of_irq_map_raw() in the same file, which populates the of_irq structure with data. Specifically, we have the “specifier” field containing the data in the array exactly as it appears in the DTS file, and “size” indicating the number of elements.

Then irq_of_parse_and_map()  tears the of_irq into its three components, calling irq_create_of_mapping() with these.

This function resolves the hardware number of the interrupt by calling the function given in the dt_translate entry of the ops entry of the interrupt controller structure. In arch/arm/common/gic.c this entry is populated with the gic_irq_domain_dt_translate() function, which is defined in the same file. Note that “dt_translate” becomes “xlate” in later kernels) .

This translation function reveals what the interrupt specification means, as detailed in “the bottom line”.

irq_create_of_mapping() also calls irq_domain_to_irq() to calculate the IRQ number to be known within Linux, and more interesting, irq_set_irq_type(), which is defined in kernel/irq/chip.c. The latter function merely takes the bus lock, calls __irq_set_trigger(), and released the lock. Well, for one important exception: It does nothing if the IRQ type is set to IRQ_TYPE_NONE. In other words, the chip’s default is retained.

I didn’t bother to follow up __irq_set_trigger(), because it’s pretty obvious what this function is meant to do, and following it to the precise point is tedious, as it’s boiling down to some hardware-specific function.

The bottom line

The first value is a flag indicating if the interrupt is an SPI (shared peripheral interrupt). A nonzero value means it is an SPI.

The second value is the interrupt number. The translate function adds 16 to SPIs and 32 to non-SPIs, so for interrupts generated by fabric logic in a Zynq, the number in the DTS file should be the hardware number (as shown in Xilinx Platform Studio, XPS) minus 32.

The third value is the type of interrupt, which is ANDed wtih IRQ_TYPE_SENSE_MASK (= 0x0f), which is defined in include/linux/irq.h. Also from this file is the meaning of the IRQ types as shown at the end of this post. Refer to the values in the “enum” clause: IRQ_TYPE_LEVEL_HIGH is 4 and IRQ_TYPE_EDGE_RISING is 1.

Cortex-A9 can’t support any other interrupt types, as put in arch/arm/common/gic.c:

 return -EINVAL

Returning -EINVAL means “failed because of invalid value”, so it says it all.

An important conclusion is that the common choice of zero for the third value actually means “leave it as it was”. This is not a necessarily clever choice, in particular if some bootloader fools around setting things it shouldn’t. It’s not a likely scenario, but it will be extremely difficult to spot a problem like this, since we all naturally assume that the interrupt attributes are completely set when the system boots up.

Anyhow, the reset default value for all of the relevant interrupts is an active High level. However, software is required to the interrupts for L1 cache and parity to rising-edge sensitivity. See the Zynq-7000 EPP Technical Reference Manual (TRM), section 7.2.3.

UART example revisited

Now let’s look at the interrupts = <0x0 0x32 0x0> definition again, with some help from the TRM. From the address of the registers, 0xe0001000, it’s clear that it’s UART1 (see TRM, appendix B.33). According to Table 7-3 in TRM’s section 7.2.3, UART1′s interrupt number is 82. Since the first value is 0, it’s declared as a non-SPI, so the second value should be the hardware number minus 32, which is 50, or 0x32. The third value says to leave the interrupt type as is.

The only offbeat thing here is the name of the section, under which the interrupt is listed: “Shared Peripheral Interrupts”. And still, it’s declared as a non-SPI. So it looks like this SPI thing isn’t so important.

Excerpt from irq.h

 * IRQ line status.
 * Bits 0-7 are the same as the IRQF_* bits in linux/interrupt.h
 * IRQ_TYPE_NONE        - default, unspecified type
 * IRQ_TYPE_EDGE_RISING        - rising edge triggered
 * IRQ_TYPE_EDGE_FALLING    - falling edge triggered
 * IRQ_TYPE_EDGE_BOTH        - rising and falling edge triggered
 * IRQ_TYPE_LEVEL_HIGH        - high level triggered
 * IRQ_TYPE_LEVEL_LOW        - low level triggered
 * IRQ_TYPE_LEVEL_MASK        - Mask to filter out the level bits
 * IRQ_TYPE_SENSE_MASK        - Mask for all the above bits
 * IRQ_TYPE_PROBE        - Special flag for probing in progress
 * Bits which can be modified via irq_set/clear/modify_status_flags()
 * IRQ_LEVEL            - Interrupt is level type. Will be also
 *                  updated in the code when the above trigger
 *                  bits are modified via irq_set_irq_type()
 * IRQ_PER_CPU            - Mark an interrupt PER_CPU. Will protect
 *                  it from affinity setting
 * IRQ_NOPROBE            - Interrupt cannot be probed by autoprobing
 * IRQ_NOREQUEST        - Interrupt cannot be requested via
 *                  request_irq()
 * IRQ_NOTHREAD            - Interrupt cannot be threaded
 * IRQ_NOAUTOEN            - Interrupt is not automatically enabled in
 *                  request/setup_irq()
 * IRQ_NO_BALANCING        - Interrupt cannot be balanced (affinity set)
 * IRQ_MOVE_PCNTXT        - Interrupt can be migrated from process context
 * IRQ_NESTED_TRHEAD        - Interrupt nests into another thread
 * IRQ_PER_CPU_DEVID        - Dev_id is a per-cpu variable
enum {
 IRQ_TYPE_NONE        = 0x00000000,
 IRQ_TYPE_EDGE_RISING    = 0x00000001,
 IRQ_TYPE_EDGE_FALLING    = 0x00000002,
 IRQ_TYPE_LEVEL_HIGH    = 0x00000004,
 IRQ_TYPE_LEVEL_LOW    = 0x00000008,
 IRQ_TYPE_SENSE_MASK    = 0x0000000f,

 IRQ_TYPE_PROBE        = 0x00000010,

 IRQ_LEVEL        = (1 <<  8),
 IRQ_PER_CPU        = (1 <<  9),
 IRQ_NOPROBE        = (1 << 10),
 IRQ_NOREQUEST        = (1 << 11),
 IRQ_NOAUTOEN        = (1 << 12),
 IRQ_NO_BALANCING    = (1 << 13),
 IRQ_MOVE_PCNTXT        = (1 << 14),
 IRQ_NESTED_THREAD    = (1 << 15),
 IRQ_NOTHREAD        = (1 << 16),
 IRQ_PER_CPU_DEVID    = (1 << 17),



Reader Comments

Excellent post.

Thank you.


Written By ZynqLab on August 26th, 2012 @ 10:22

Based on the UG585 (v1.4) section 7.2.3 the UART is an SPI interrupt which means for SPI interrupts a value of 32 is added,while for PPI ( non – SPI,value 1 for 1st cell in interrupts) a value of 16 is added

Written By Thanasis on December 5th, 2012 @ 10:58

We seem to agree on that the UART interrupt is an SPI (we read the same user guide).

As for whether to add 32 or 16, that should match what the Linux kernel code does. After going through the relevant part, I reached the conclusion in the post. I don’t know what you based your statement upon.

Written By eli on December 5th, 2012 @ 12:09

Thank you for you replay. I the section “The bottom line” you state :
“The second value is the interrupt number. The translate function adds 16 to SPIs and 32 to non-SPIs, …”

According to that the IRQ of the UART1 is 50+16 = 62 which does not match the value of 82 from the TRM ( the one you also mention in the example ) . I am not saying you are wrong, since your example is correct, I am just pointing something you might accidentally mixed.

B.T.W: Thanks for the post, it helped me a lot

Written By Thanasis on December 5th, 2012 @ 14:26

I see where I am wrong. Please disregard my comments.
Sorry for the misunderstanding

Thx again for the post

Written By Thanasis on December 5th, 2012 @ 15:08

Hi everyone. Is it actually possible to handle PPI interrupts with Linux in a SMP configuration? I am thinking about the Core0_nIRQ or Core1_nIRQ signals. If so, what is different in the implementation and how do I calculate the interrupt number? Thanks.


Written By Sven on May 21st, 2013 @ 16:50
Written By eli on May 21st, 2013 @ 17:27

Hi Eli,

thanks for your response. Unfortunately I am not able to find the information I am looking for on the page you have provided.


Written By Sven on May 22nd, 2013 @ 08:15

I have now narrowed down the problem. Here it says how to configure the device tree with PPI:

So my interrupts entry in the DTS file looks like this for the signal Core1_nFIQ, rising edge:
12 = 28 – 16, the range for PPI interrupts is 0..15

Unfortunately the kernel is not willing to insert the module. irq_settings_is_per_cpu_devid fails in request_threaded_irq. The problem might be, that an IRQ with number as low as 19 don’t exist. When I list all IRQs in /proc they only start at number 16.

Written By Sven on May 22nd, 2013 @ 09:24

I’ve been working on booting a custom configuration using a Zedboard and I’m currently digging into the dtb. I noticed an inconsistency between your example and the file from the linux-xlnx-master repository in the directory arch\arm\boot\dts\zynq-zed.dts. You list your interrupt configuration values in hex whereas the downloaded dts does not appear to be in hex. Would you expect that this will cause an issue? I was thinking that it might not matter at runtime but I’ve learned that it’s not wise to assume that the provided materials are error free.

from the zynq-zed.dts file:

pmu {
compatible = “arm,cortex-a9-pmu”;
interrupt-parent = ;
interrupts = , ;
reg = , ;
reg-names = “cpu0″, “cpu1″;
} ;

Thanks for blogging.

Written By Kyle on July 24th, 2014 @ 21:39

format issues.

interrupt-parent = &ps7_axi-interconect_0
interrupts = 0 5 4, 0 6 4
reg = 0xf8891000 0x1000, 0xf8893000 0x1000

Written By Kyle on July 24th, 2014 @ 21:41


The DTS file is compiled into a DTB by the dtc compiler, and it interprets both hex numbers and decimal properly. It’s therefore quite far-fetched to blame the hex vs. decimal representation of constants.


Written By eli on July 24th, 2014 @ 21:45

Thanks again.

Written By Kyle on July 24th, 2014 @ 23:07

Thanks for the article.

One correction though: you got SPI flag values wrong. SPI corresponds to zero value, see:
- handling in gic_irq_domain_translate() (in either drivers/irqchip/irq-gic.c or drivers/irqchip/irq-gicv3.c),
- definitions in include/dt-bindings/interrupt-controller/arm-gic.h,
- description in Documentation/devicetree/bindings/interrupt-controller/arm,gic.txt.

Written By Alex Matveev on February 24th, 2017 @ 13:57

Add a Comment

required, use real name
required, will not be published
optional, your blog address