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