> For the complete documentation index, see [llms.txt](https://weenslab.gitbook.io/pages/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://weenslab.gitbook.io/pages/fpga-tutorials/zynq-fpga-linux-kernel-module/configure-pl-to-ps-interrupt-in-kernel-module.md).

# 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.

<figure><img src="/files/M4CLzNYbimGDIJuCfRA5" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/gEzaO3084gUz4pSI4qXE" alt=""><figcaption></figcaption></figure>

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

<figure><img src="/files/ycuTBjfWNbuMppAiPPWp" alt=""><figcaption></figcaption></figure>

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.

<figure><img src="/files/KgPRYo0mfSUH6XgUqiTl" alt=""><figcaption></figcaption></figure>

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.

```c
#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.
