Configure PL to PS Interrupt in Kernel Module

Hardware Design

Create a hardware design in Vivado that consists of ZYNQ PS and AXI GPIO. Set the AXI GPIO as output with 1-bit of data width. Connect this GPIO output to interrupt input of the ZYNQ PS via a concatenate IP. We are going to use the AXI GPIO to trigger the interrupt from the Linux later on.

To enable the interrupt port, go to the following settings. The number of interrupts starts from 61-68 and 84-91. The bit 0 in concatenate IP is connected to 61, and bit 15 is connected to 91.

The same design is applicable for ZYNQ UltraScale+. The only difference is the port settings.

The interrupt number of IRQ0 is from 121-128 and IRQ1 is from 136-143. The bit 0 in concatenate IP is connected to 121, and bit 7 is connected to 128.

Build the bitstream for the PYNQ overlay.

Devicetree Settings

Extract the devicetree from image.ub from your board.

Search for the following code and delete them (32-bit system).

	fabric@40000000 {
		compatible = "generic-uio";
		reg = <0x40000000 0x10000>;
		interrupt-parent = <0x4>;
		interrupts = <0x0 0x1d 0x4>;
	};

Add the following code (32-bit system). In devicetree, the interrupt number is subtracted by 32. So, 61-32=29 (0x1d). The interrupt-parent should be the same as the rest of your devicetree's interrupt-parent.

	fpga-axi@0 {
		compatible = "simple-bus";
		#address-cells = <0x1>;
		#size-cells = <0x1>;
		ranges;

		btn0 {
			compatible = "btn0,btn0";
			interrupt-parent = <0x4>;
			interrupts = <0x0 0x1d 0x1>;
		};
	};

Search for the following code and delete them (64-bit system).

	fabric@A0000000 {
		compatible = "generic-uio";
		reg = <0x0 0xa0000000 0x0 0x10000>;
		interrupt-parent = <0x4>;
		interrupts = <0x0 0x59 0x4>;
	};

Add the following code (64-bit system). In devicetree, the interrupt number is subtracted by 32. So, 121-32=89 (0x59). The interrupt-parent should be the same as the rest of your devicetree's interrupt-parent.

	fpga-axi@0 {
		compatible = "simple-bus";
		#address-cells = <0x1>;
		#size-cells = <0x1>;
		ranges;

		btn0 {
			compatible = "btn0,btn0";
			interrupt-parent = <0x4>;
			interrupts = <0x0 0x59 0x1>;
		};
	};

Compile the devicetree back to binary and pack into image.ub again, then copy to your board.

Use the following command to check the devicetree that runs on the board (fpga-axi@0).

root@pynq:/home/xilinx/workspace# cd /proc/device-tree
root@pynq:/proc/device-tree# ls
'#address-cells'   compatible       fpga-full   memory@0   pmu@f8891000   xlnk
 aliases           cpus             gpio-keys   model      replicator
 axi               fixedregulator   gpio-leds   name      '#size-cells'
 chosen            fpga-axi@0       memory      phy0       __symbols__
root@pynq:/proc/device-tree#

Kernel Module

Create a C program for the kernel module to access the interrupt. The code is for the OFDM system, so some of the code is not intended for interrupt.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/dmaengine.h>
#include <linux/of_dma.h>
#include <linux/dma/xilinx_dma.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/inetdevice.h>
#include <linux/skbuff.h>
#include <linux/ethtool.h>
#include <linux/if_ether.h> 
#include <linux/if_arp.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/icmp.h>
#include <linux/rtnetlink.h>
#include <linux/net_tstamp.h>
#include <linux/percpu.h>
#include <linux/u64_stats_sync.h>
#include <net/sock.h>
#include <net/checksum.h>
#include <net/net_namespace.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <linux/fips.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/bitmap.h>
#include <linux/list.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("deadbeef");

// Driver private data
struct ofdm_priv {
    struct platform_device *pdev;
    struct net_device *netdev;

    int irq_btn0;
};

// ### Funtion prototypes ######################################################
static irqreturn_t btn0_interrupt(int irq, void *dev_id);
static int ofdm_open(struct net_device *dev);
static int ofdm_close(struct net_device *dev);
static netdev_tx_t ofdm_xmit(struct sk_buff *skb, struct net_device *dev);
static void ofdm_setup(struct net_device *dev);
static int sdr_probe(struct platform_device *pdev);
static int sdr_remove(struct platform_device *pdev);
static int __init sdr_init(void);
static void __exit sdr_exit(void);

// ### Variable declarations ###################################################
// For platform driver
static const struct of_device_id sdr_of_ids[] = {
    { .compatible = "btn0,btn0", },
    {}
};
// For platform driver
static struct platform_driver sdr_driver = {
    .driver = {
	.name = "btn0,btn0",
	.of_match_table = sdr_of_ids,
    },
    .probe = sdr_probe,
    .remove = sdr_remove,
};
// For net device
const struct net_device_ops ofdm_netdev_ops = {
    .ndo_open = ofdm_open,
    .ndo_stop = ofdm_close,
    .ndo_start_xmit = ofdm_xmit,
};
volatile int tx_done = 0;

// ### Functions declarations ##################################################
static irqreturn_t btn0_interrupt(int irq, void *dev_id)	
{
    printk(KERN_INFO "INFO: btn0 interrupt occured\n");
    return IRQ_HANDLED;
}

static int ofdm_open(struct net_device *dev)
{
    printk(KERN_INFO "INFO: ofdm_open() called\n");
    return 0;
}

static int ofdm_close(struct net_device *dev)
{
    printk(KERN_INFO "INFO: ofdm_close() called\n");
    return 0;
}

static netdev_tx_t ofdm_xmit(struct sk_buff *skb, struct net_device *dev)
{
    printk(KERN_INFO "INFO: ofdm_xmit() called\n");
    return NETDEV_TX_OK;
}

static void ofdm_setup(struct net_device *dev)
{
    dev->mtu = 1500; // Warning: don't forget to set this whenever create a net device.
    dev->netdev_ops = &ofdm_netdev_ops;
}

static int sdr_probe(struct platform_device *pdev)
{
    struct net_device *dev;
    struct ofdm_priv *priv;
    int ret = 0;

    printk(KERN_INFO "INFO: sdr_probe() called\n");

    // *** OFDM private data ***
    // Create a net_device
    dev = alloc_netdev(sizeof(struct ofdm_priv), "sdr%d", NET_NAME_UNKNOWN, ofdm_setup);
    if (!dev)
    {
	ret = -ENOMEM;
	printk(KERN_ERR "ERROR: alloc_etherdev() failed\n");
	goto error_alloc;
    }
    // Get reference to priv
    priv = netdev_priv(dev);
    // Set reference to net_device 
    priv->netdev = dev;
    // Set reference to platform_device 
    priv->pdev = pdev;
    // Set reference to priv
    platform_set_drvdata(pdev, priv);

    // *** Register net_device ***
    ret = register_netdev(dev);
    if (ret != 0)
    {
	printk(KERN_ERR "ERROR: register_netdev() failed\n");
	goto error_register_netdev;
    }
	
    // *** Interrupt ***
    priv->irq_btn0 = irq_of_parse_and_map(priv->pdev->dev.of_node, 0);
    ret = request_irq(priv->irq_btn0, btn0_interrupt, IRQF_SHARED, "btn0,btn0_itrpt", priv);
    if (ret != 0)
    {
        printk(KERN_ERR "ERROR: RX request_irq() failed %d ret %d , EIO %d , EINVAL %d\n", priv->irq_btn0, ret, EIO, EINVAL);
        goto error_irq;
    }

    printk(KERN_INFO "INFO: sdr_probe() success\n");

    return 0;

error_irq:
    unregister_netdev(priv->netdev);
error_register_netdev:
    free_netdev(priv->netdev);
error_alloc:
    return ret;
}

static int sdr_remove(struct platform_device *pdev)
{
    struct ofdm_priv *priv = platform_get_drvdata(pdev);

    printk(KERN_INFO "INFO: sdr_remove() called\n");

    free_irq(priv->irq_btn0, priv);
    unregister_netdev(priv->netdev);
    free_netdev(priv->netdev);

    return 0;
}

static int __init sdr_init(void)
{
    printk(KERN_INFO "INFO: sdr_init() called\n");
    return platform_driver_register(&sdr_driver);
}

static void __exit sdr_exit(void)
{
    printk(KERN_INFO "INFO: sdr_exit() called\n");
    platform_driver_unregister(&sdr_driver);
}

// ### Entry and exit points ###################################################
late_initcall(sdr_init);
module_exit(sdr_exit);

Compile the kernel module and test on the board. Use devmem2 to trigger the interrupt via the AXI GPIO.

root@pynq:/home/xilinx/workspace# sudo su
root@pynq:/home/xilinx/workspace# insmod btn-itrpt.ko
root@pynq:/home/xilinx/workspace# dmesg
[ 4772.363368] INFO: sdr_init() called
[ 4772.363574] INFO: sdr_probe() called
[ 4772.366685] INFO: sdr_probe() success
root@pynq:/home/xilinx/workspace# devmem2 0x41200000 w 1
/dev/mem opened.
Memory mapped at address 0xb6f6e000.
Value at address 0x41200000 (0xb6f6e000): 0x0
Written 0x1; readback 0x1
root@pynq:/home/xilinx/workspace# dmesg
[  137.064154] [drm] bitstream 877d995d-aa11-a5cf-9f60-1998dc3a7818 unlocked, ref=0
[ 4772.363368] INFO: sdr_init() called
[ 4772.363574] INFO: sdr_probe() called
[ 4772.366685] INFO: sdr_probe() success
[ 4804.399565] INFO: btn0 interrupt occured
root@pynq:/home/xilinx/workspace#

You can monitor the intterupt from the /proc/interrupts. The interrupt will be registered here after you insert the kernel module.

root@pynq:/home/xilinx/workspace# cat /proc/interrupts
           CPU0       CPU1
 24:          0          0     GIC-0  27 Edge      gt
 25:      74726      70614     GIC-0  29 Edge      twd
 26:          0          0     GIC-0  37 Level     arm-pmu
 27:          0          0     GIC-0  38 Level     arm-pmu
 28:         43          0     GIC-0  39 Level     f8007100.adc
 30:          2          0     GIC-0  57 Level     cdns-i2c
 32:          0          0     GIC-0  35 Level     f800c000.ocmc
 33:       1956          0     GIC-0  82 Level     xuartps
 34:          0          0     GIC-0  51 Level     e000d000.spi
 35:       5056          0     GIC-0  54 Level     eth0
 36:      75419          0     GIC-0  56 Level     mmc0
 37:          0          0     GIC-0  45 Level     f8003000.dmac
 38:          0          0     GIC-0  46 Level     f8003000.dmac
 39:          0          0     GIC-0  47 Level     f8003000.dmac
 40:          0          0     GIC-0  48 Level     f8003000.dmac
 41:          0          0     GIC-0  49 Level     f8003000.dmac
 42:          0          0     GIC-0  72 Level     f8003000.dmac
 43:          0          0     GIC-0  73 Level     f8003000.dmac
 44:          0          0     GIC-0  74 Level     f8003000.dmac
 45:          0          0     GIC-0  75 Level     f8003000.dmac
 46:         40          0     GIC-0  40 Level     f8007000.devcfg
 48:          0          0     GIC-0  43 Level     ttc_clockevent
 53:          0          0     GIC-0  53 Level     e0002000.usb
 54:          0          0     GIC-0  41 Edge      f8005000.watchdog
 55:          1          0     GIC-0  61 Edge      btn0,btn0_itrpt
 56:          0          0  zynq-gpio  50 Edge      btn4
 57:          0          0  zynq-gpio  51 Edge      btn5
IPI0:          0          0  CPU wakeup interrupts
IPI1:          0          0  Timer broadcast interrupts
IPI2:      32446      52733  Rescheduling interrupts
IPI3:        763        865  Function call interrupts
IPI4:          0          0  CPU stop interrupts
IPI5:          0          0  IRQ work interrupts
IPI6:          0          0  completion interrupts
Err:          0
root@pynq:/home/xilinx/workspace#

How many interrupts occurred can be monitored here on the CPU0 column.

Last updated