Skip to content

Instantly share code, notes, and snippets.

@vrbadev
Last active November 14, 2022 13:43
Show Gist options
  • Save vrbadev/0850428601283986f27221f16ee5ceba to your computer and use it in GitHub Desktop.
Save vrbadev/0850428601283986f27221f16ee5ceba to your computer and use it in GitHub Desktop.
Cyclone V HPS - Linux kernel driver for non-blocking polling of FPGA-to-HPS interrupts (f2h_irq0 and f2h_irq1) (tested with linux-socfpga v5.19)
Features:
- This Linux kernel driver was tested with (currently) latest altera-opensource/linux-socfpga kernel v5.19
(with Debian Bullseye rootfs)
- Supports all 64 FPGA-to-HPS interrupt signals (f2h_irq0 and f2h_irq1 on the fabric side).
- Using poll() the userspace app can work in non-blocking mode (zero CPU usage).
- When an interrupt is registered the module updates the SYSFS file with a string of length 66
(ie. char[64] with values '1' or '0' for the states of the interrupts + '\n\0').
- When the file is opened from the userspace the file value is updated and internally the status of interrupts is cleared
(so the file must be reopened when the userspace app receives poll notification).
Instructions:
1) Build the fpgaint kernel module (fpgaint.c) using this Makefile (you must provide valid KERNEL_SRC path):
/*----------------------------- Makefile start -----------------------------*/
KERNEL_SRC=../linux-socfpga
ARMMAKE=make ARCH=arm SUBARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
obj-m := fpgaint.o
fpgaint.ko: fpgaint.c
$(ARMMAKE) -C $(KERNEL_SRC) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.c *.symvers *.order
/*----------------------------- Makefile end -------------------------------*/
2) Modify the device tree so that it contains the includes and the part under soc:
/*----------------------------- Device tree excerpt start -----------------------------*/
[...]
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/interrupt-controller/arm-gic.h>
/ {
[...]
soc {
[...]
fpgaint {
compatible = "altr,fpgaint";
interrupts = <GIC_SPI 40 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ0 */
GIC_SPI 41 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ1 */
GIC_SPI 42 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ2 */
GIC_SPI 43 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ3 */
GIC_SPI 44 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ4 */
GIC_SPI 45 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ5 */
GIC_SPI 46 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ6 */
GIC_SPI 47 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ7 */
GIC_SPI 48 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ8 */
GIC_SPI 49 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ9 */
GIC_SPI 50 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ10 */
GIC_SPI 51 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ11 */
GIC_SPI 52 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ12 */
GIC_SPI 53 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ13 */
GIC_SPI 54 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ14 */
GIC_SPI 55 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ15 */
GIC_SPI 56 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ16 */
GIC_SPI 57 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ17 */
GIC_SPI 58 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ18 */
GIC_SPI 59 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ19 */
GIC_SPI 60 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ20 */
GIC_SPI 61 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ21 */
GIC_SPI 62 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ22 */
GIC_SPI 63 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ23 */
GIC_SPI 64 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ24 */
GIC_SPI 65 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ25 */
GIC_SPI 66 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ26 */
GIC_SPI 67 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ27 */
GIC_SPI 68 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ28 */
GIC_SPI 69 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ29 */
GIC_SPI 70 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ30 */
GIC_SPI 71 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ31 */
GIC_SPI 72 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ32 */
GIC_SPI 73 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ33 */
GIC_SPI 74 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ34 */
GIC_SPI 75 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ35 */
GIC_SPI 76 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ36 */
GIC_SPI 77 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ37 */
GIC_SPI 78 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ38 */
GIC_SPI 79 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ39 */
GIC_SPI 80 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ40 */
GIC_SPI 81 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ41 */
GIC_SPI 82 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ42 */
GIC_SPI 83 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ43 */
GIC_SPI 84 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ44 */
GIC_SPI 85 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ45 */
GIC_SPI 86 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ46 */
GIC_SPI 87 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ47 */
GIC_SPI 88 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ48 */
GIC_SPI 89 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ49 */
GIC_SPI 90 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ50 */
GIC_SPI 91 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ51 */
GIC_SPI 92 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ52 */
GIC_SPI 93 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ53 */
GIC_SPI 94 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ54 */
GIC_SPI 95 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ55 */
GIC_SPI 96 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ56 */
GIC_SPI 97 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ57 */
GIC_SPI 98 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ58 */
GIC_SPI 99 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ59 */
GIC_SPI 100 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ60 */
GIC_SPI 101 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ61 */
GIC_SPI 102 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ62 */
GIC_SPI 103 IRQ_TYPE_EDGE_RISING /* FPGA_IRQ63 */
>;
};
[...]
};
[...]
};
/*----------------------------- Device tree excerpt end -------------------------------*/
(Of course you can use IRQ_TYPE of your choice.)
3) Install the module inside the Linux using: insmod fpgaint.ko
(check dmesg for log, check /proc/interrupts for mapping of interrupt lines)
4) Build the userspace test app (test_fpgaint.c) using: gcc test_fpgaint.c -o test_fpgaint
5) Run the test app and toggle the f2h_irq0 and/or f2h_irq1 signals, observe the results in the terminal.
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/ioport.h>
#include <linux/wait.h>
#include <linux/semaphore.h>
#include <linux/spinlock_types.h>
#include <linux/uaccess.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#define DEVNAME "fpgaint"
#define NUM_INTERRUPTS 64
static DEFINE_SEMAPHORE(interrupt_mutex);
static DEFINE_SPINLOCK(interrupt_flag_lock);
static int irq_nums[NUM_INTERRUPTS] = {0};
static uint32_t irq_status[2];
static ssize_t irq_status_show(struct device * dev, struct device_attribute * attr, char * buf)
{
int ret;
int i;
uint32_t mask;
spin_lock(&interrupt_flag_lock);
for (i = 0; i < NUM_INTERRUPTS; i++) {
if (i % 32 == 0) {
mask = 1;
}
buf[i] = (irq_status[i / 32] & mask) ? '1' : '0';
mask <<= 1;
}
irq_status[0] = 0;
irq_status[1] = 0;
spin_unlock(&interrupt_flag_lock);
buf[NUM_INTERRUPTS] = '\n';
buf[NUM_INTERRUPTS+1] = '\0';
ret = NUM_INTERRUPTS + 2;
return ret;
}
static ssize_t irq_status_store(struct device * dev, struct device_attribute * attr, const char * buf, size_t len)
{
return -EROFS;
}
static DEVICE_ATTR_RW(irq_status);
static irq_handler_t __fpgaint_driver_isr(int irq, void* kobj)
{
int i;
for (i = 0; i < NUM_INTERRUPTS; i++) {
if (irq_nums[i] == irq) {
break;
}
}
if (i == NUM_INTERRUPTS) {
return (irq_handler_t) IRQ_NONE;
}
spin_lock(&interrupt_flag_lock);
irq_status[i / 32] |= (1 << (i % 32));
spin_unlock(&interrupt_flag_lock);
sysfs_notify((struct kobject *) kobj, NULL, "irq_status");
return (irq_handler_t) IRQ_HANDLED;
}
static int __fpgaint_driver_probe(struct platform_device* pdev)
{
int ret;
int irq_num;
int i = 0;
ret = device_create_file(&pdev->dev, &dev_attr_irq_status);
if (ret) {
printk(KERN_ALERT "%s: Error %d when executing device_create_file in function %s!\n", DEVNAME, ret, __FUNCTION__);
return ret;
}
while((irq_num = platform_get_irq_optional(pdev, i)) > 0) {
printk(KERN_INFO "%s: IRQ %d about to be registered!\n", DEVNAME, irq_num);
ret = request_irq(irq_num, (irq_handler_t) __fpgaint_driver_isr, 0, DEVNAME, &pdev->dev.kobj);
if (ret < 0) {
printk(KERN_ALERT "%s: Error %d when executing request_irq (irq_num=%d) in function %s!\n", DEVNAME, ret, irq_num, __FUNCTION__);
return ret;
}
irq_nums[i] = irq_num;
i++;
}
return 0;
}
static int __fpgaint_driver_remove(struct platform_device* pdev)
{
int irq_num;
int i = 0;
while((irq_num = platform_get_irq_optional(pdev, i)) > 0) {
printk(KERN_INFO "%s: IRQ %d about to be freed!\n", DEVNAME, irq_num);
free_irq(irq_num, &pdev->dev.kobj);
irq_nums[i] = 0;
i++;
}
device_remove_file(&pdev->dev, &dev_attr_irq_status);
return 0;
}
static const struct of_device_id __fpgaint_driver_id[] = {
{.compatible = "altr,fpgaint"},
{}
};
static struct platform_driver __fpgaint_driver = {
.driver = {
.name = DEVNAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(__fpgaint_driver_id),
},
.probe = __fpgaint_driver_probe,
.remove = __fpgaint_driver_remove
};
module_platform_driver(__fpgaint_driver);
MODULE_LICENSE("GPL");
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>
#define SYSFS_FILE "/sys/devices/platform/soc/soc:fpgaint/irq_status"
volatile int exit_program = 0;
void exit_handler(int sig)
{
exit_program = 1;
}
int main(void) {
signal(SIGINT, exit_handler);
int ret;
int fd;
struct pollfd ufds[1];
char irq_status[66];
if ((fd = open(SYSFS_FILE, O_RDONLY)) < 0) {
printf("Error when opening SYSFS_FILE!\n");
return 1;
} else {
// First dummy read - flushes notifications
read(fd, irq_status, sizeof(irq_status));
}
while (!exit_program) {
// Set poll to current file descriptor and 1s timeout
ufds[0].fd = fd;
ufds[0].events = POLLPRI | POLLERR;
ufds[0].revents = 0;
ret = poll(ufds, 1, 1000);
// Check for notification event
if ((ufds[0].revents & POLLPRI) && ret > 0) {
// Reopen the file first to get updated status
close(fd);
fd = open(SYSFS_FILE, O_RDONLY);
// Read actual status value
read(fd, irq_status, sizeof(irq_status));
// Do something when the interrupts occur
// Namely, check if FPGA_IRQn = 1 like: (irq_status[n] == '1')
printf("IRQ status: %s", irq_status);
}
}
close(fd);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment