5
\$\begingroup\$

I need to propagate an interrupt from my custom FPGA IP core to the HPS system of a DE0_nano_SoC (Cyclone V HPS-FPGA architecture) and handle in Linux. I have googled quite a lot to confidently say that this topic is not well covered.


Required functionality

The custom FPGA IP core sets an interrupt signal. The HPS registers this signal (possibly writes to the custom FPGA IP core to de-assert the interrupt signal) and copies a few bytes from registers in the FPGA to a program running in Linux.

The choice of Linux is arbitrary, preferably Angstrom/Yocto, which I have running right now, but if the FreeRTOS would offer simpler implementation I would go for it.


My assumptions (please correct me if wrong)

1) The Interrupt Controller in the HPS recognices the FPGA generated interrupts, starting at the number 73 (there is some shifting, but in principle they are mapped with constant values).

2) Linux for ARM Cortex A9, is able to recognize vendor specific interrupts (for different peripherals like I2C0/1/2, UART0/1, etc).


Question

1) Does Linux recognize the interrupts from the FPGA, mapped by HPS Interrupt Controller?

2) Do I need to develop a driver, so that the Linux can recognize the FPGA interrupts ?

3) This seems to be quite important feature of the whole Cyclone V architecture. Hasnt Altera developed such drivers already, to handle simple FPGA-to-HPS interrupts in Linux ?

\$\endgroup\$
2
  • \$\begingroup\$ This is device specific. Better post this in altera form. \$\endgroup\$
    – Mitu Raj
    Commented Dec 16, 2017 at 11:43
  • 1
    \$\begingroup\$ @MITURAJ It's not actually device specific. It's OS specific. \$\endgroup\$ Commented Dec 16, 2017 at 11:59

2 Answers 2

5
\$\begingroup\$

The HPS bridge is designed to take FPGA interrupts and feed them in to the general interrupt controller (GIC) within the ARM processor. As far as the processor is concerned, interrupts from the FPGA are no different from interrupts from any other source. Just like the I2C or UART peripherals, FPGA interrupts invoke the same response in the GIC.

The Linux kernel has already been customised for the specific processor and interrupt controller. The kernel already knows how to react to an interrupt - it simply executes whatever interrupt handler (ISR) that has been set up by the kernel to handle a specific interrupt. In the event of an interrupt source that doesn't have a handler set up, it will likely have a default handler that silently disposes of the interrupt (likely by just ignoring it).

So how do you handle it? The same way any other interrupt is handled - a driver. You have to provide some code, driver software, that registers an interrupt handler for the specific interrupt source. For example there are already drivers provided for the I2C and UART peripherals, these will have interrupt handlers.

There are many useful documents on the internet for driver handling, a quick Google search found this which seems quite good. From there we can see that interrupts all have a number, the handlers for which can be seen by running the command cat /proc/interrupts. In your case you won't see anything for interrupt number 72 (the first FPGA interrupt) as there is no driver set up to handle it.

TL;DR; You need to write a Linux driver. There is nothing special that needs to be done to account for the fact that the interrupt comes from the FPGA, that is already taken care of in hardware by the HPS bridge.

\$\endgroup\$
5
  • 2
    \$\begingroup\$ I suspect you're glossing over the hardest part of all of this -- the devicetree updates that will have to be done to let the kernel know what is hooked up to which interrupt, which memory ranges are used or the general bus configuration so you can access the FPGA side hardware. \$\endgroup\$
    – akohlsmith
    Commented Dec 16, 2017 at 16:24
  • \$\begingroup\$ @akohlsmith quite possibly, but my answer is not intended to be a complete instruction manual, as the "how" bit of writing a driver will depend on a lot of information that is not given in the question. The take away point is that going from FPGA to HPS is already taken care of in hardware, so writing a driver for a peripheral in the FPGA becomes no different from any memory mapped peripheral (e.g. a PCIe device). \$\endgroup\$ Commented Dec 16, 2017 at 16:51
  • \$\begingroup\$ Exactly -- my point was that the device driver is the easy part. Devicetree has driven many a kernel hacker insane. \$\endgroup\$
    – akohlsmith
    Commented Dec 17, 2017 at 6:20
  • \$\begingroup\$ I'm pretty sure that the 1st FPGA interrupt (FPGA IRQ 0) has the number 72 in the GIC, not 73, as stated in [Cyclone V Hard Processor System Technical Reference Manual](cdrdv2-public.intel.com/666962/cv_5v4-683126-666962.pdf p15). \$\endgroup\$
    – dpeng
    Commented May 30 at 13:07
  • \$\begingroup\$ @dpeng indeed it is 72, typo. \$\endgroup\$ Commented May 30 at 14:45
0
\$\begingroup\$

I think I could figure out the devicetree part. Here is a devicetree overlay for a Cyclone V, DE0-nano-SoC board. I'm trying to use FPGA IRQ 1, as numbered in the Platform Designer (aka QSYS).

See Cyclone V Hard Processor System Technical Reference Manual (PDF) Table 10-3.

/*
    Cyclone V platform

    Interrupt driver for FPGA interrupts from PIO IP core, pioirq.
*/

/dts-v1/;
/plugin/;

/* Add a subnode to the soc node. */

/ {

    fragment@0 {
        /*
        With the 'target' property, DTBO insertion fails with message :
        "OF: resolver: node label 'base_fpga_region' not found in live devicetree symbols table"
        target = <&soc>;
        Use an absolute path instead, with 'target-path'.
        */
        target-path = "/soc";
        __overlay__ {
            pioirq0: pioirq@0 {
                compatible = "altr,socfpga-pioirq";
                label = "pioirq0";

                /* Map button PIO.
                FPGA LW bridge slaves base address is 0xff200000. Add the base
                address of the PIO IP core visible in Platform Designer to this
                value to get the actual address.
                Platform Designer displays the base and end addresses of the PIO
                IP core. The size is the difference end - base + 1.
                https://www.intel.com/content/www/us/en/programmable/hps/cyclone-v/hps.html
                */
                #address-cells = <1>;
                #size-cells = <1>;
                reg = <0xff2100c0 0x10>;
                ranges;

                /*
                1st field: interrupt controller. Always 0 on this target.

                2nd field: IRQ number.
                The 1st GIC IRQ number is 32. The 1st GIC IRQ number dedicated to
                the FPGA is 72, i.e. at index 40 (72 - 32 = 40).
                To use IRQ #72, set 40 in the following property,
                to use IRQ #73 set 41, etc.

                3rd field: interrupt type. See include/linux/irq.h.
                1: rising, 2:falling, 3: rising/falling, 4: high, 8: low.
                See include/dt-bindings/interrupt-controller/irq.h
                Always set to 1 for rising or falling edge. Whether the interrupt
                is triggered on falling or rising edge is actually set in Quartus
                Platform Designer.
                */
                interrupts = <0 41 1> ;
                interrupt-parent = <&intc>;
            };
        };
    };

};

I started to write a basic driver to catch an interrupt supposedly generated by a pushbutton (PIO module in QSYS, rising edge, FPGA IRQ #1). It seems I can register a handler with the GIC interrupt 73 as expected:

# cat /proc/interrupts | grep pioirq                                                                  
          CPU0       CPU1
52:          0          0     GIC-0  73 Edge      pioirq0

But I just can't see any message in dmesg, nor can I see one of the two 0s in the line above increment upon a new interrupt, as I guess it should. I don't even know how to debug that.

EDIT 1:

I added the "Map button PIO" section in the devicetree overlay. I'm pretty sure I figured the address right using the Cyclone V HPS Register Address Map and Definitions. I confirmed by pressing the buttons while loading the driver, which reads the data register.

It allows me to call platform_get_resource(pdev, IORESOURCE_MEM, 0) from the driver, where pdev is a struct platform_device, then iomap the memory.

Now, the interrupt driver is triggered ONLY ONCE after reboot. I tried to clear the interrupt by writting to the edge capture register at offet 3*4 = 12 = 0x0c (see PIO Core with Avalon Interface , Register Map section). Now, I get this, and numbers don't increment when I press the buttons:

# cat /proc/interrupts | grep pioirq                                                                  
          CPU0       CPU1
52:          1          0     GIC-0  73 Edge      pioirq0

EDIT 2:

Add more comments in the devicetree overlay.

The document PIO Core with Avalon Interface is wrong. It states that "Writing any value to edgecapture clears all bits to 0". The truth is, to reset a bit in edgecapture to 0, you need to write a 1. Ain't that beautiful ?

Now, I can see the output of the driver:

[15807.170171] pioirq: pioirq_irq_handler() >> edge capture     = 0x1                              
[15808.238805] pioirq: pioirq_irq_handler() >> edge capture     = 0x2                              

and the number of interrupts increase every time I press a button:

# cat /proc/interrupts | grep pioirq
 52:         73          0     GIC-0  73 Edge      pioirq0
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.