<< back | index | forward >>
Interrupts are an essential functionality for embedded devices. So let's add an IRQ to our UIO driver example.
Explanations in this section do not start from scratch but based on the state after the UIO Driver Tutorial.
First we need to add an IRQ to the FPGA design we can react on from the UIO driver and application. For this, we will modify the FPGA design that was created in the section Setup.
First, we will change the fpga_base IP-Core to produce a 1Hz output signal. This signal is usually used for blinking LEDs, so it is named like this. But nothing prevents us from using this signal as IRQ. Note that we need to configure the correct clock frequency in order to get IRQs at exactly 1 Hz.
Next, the ZYNQ7 processing system must be configured to accept interrupt inputs
Now, the new output from the IP-Core must be connected to the IRQ input of the PS. The figure below shows how your diagram should look now.
The changes are done now. Please follow the description in Setup to generate a new bitstream and produce a boot image from it.
Compared to UIO Driver Tutorial, the devicetree entry is modified as shown below:
uio_fpga_base@43C10000{
status = "okay";
compatible = "generic-uio";
reg = < 0x43C10000 0x1000 >;
interrupt-parent = <&intc>;
interrupts = <0x0 (61-32) 1>
};
What do these changes mean?
-
The interrupts property is built from a < X Y Z > tuple.
- X = 1 for shared peripheral interrupts (SPI), 0 else3
- Y = IRQ number (IRQnr - 32 for non SPI IRQs). In our case we use IRQ61 which is the first FPGA fabric IRQ in a Zynq-7000.
- Z = IRQ type according to linux/interrupt.h. 1 is for rising-edge IRQs (IRQF_TRIGGER_RISING).
-
The interrupt-parent property is a link to the device-tree node of the interrupt controller the IRQ is routed to. In our case, the node intc is defined in the include file zynq-7000.dtsi which is included in the main device-tree:
... intc: interrupt-controller@f8f01000 { compatible = "arm,cortex-a9-gic" #interrupt-cells = <3> interrupt-controller; reg = <0xF8F01000 0x1000>, <0xF8F00100 0x100>; } ...The full device-tree file is available in [root]/uio_driver_irq/zx5-obru-uio-irq.dts
The easiest way to compile the edited devicetree, is copying it to the folder [root]/bsp-xilinx/sources/xilinx-linux/arch/arm/boot/dts directory of your Enclustra Build Environment.
The device-tree can then be compiled into a devicetree-blob using the command below (from within the dts drectiory mentioned above):
dtc -O dtb -o zx5-obru-uio-irq.dtb zx5-obru-uio-irq.dts
The output file zx5-obru-uio-irq.dtb must be copied to the boot partition of the SD card and renamed to devicetree.dtb (as expected by the boot process) and hence replace de default devicetree.dtb file.
This was done in UIO Driver, so it is not explained again.
This was done in UIO Driver, so it is not explained again.
The same steps as for UIO Driver are required. The command is repeated just to avoid that you have to switch documents because you do not remember the command:
modprobe uio_pdrv_genirq of_id="generic-uio"
The plain numbering of UIO devices without a human readable name can easily lead to confusion. Luckily, there are some ways to find out more about a UIO device.
One way to learn more about UIO devices is suing the /sys/class/uio directory structure. For example:
# cat /sys/class/uio/uio0/name
uio_fpga_base
Another even more handy way, is the lsuio utility. It is not enabled by default but it can easily be enabled when configuring buildroot. Just search (using '/') for "lsuio" and enable the corresponding package. If lsuio is present you can use it as described below:
# lsuio
uio0: name=uio_fpga_base, version=devicetree, events=0
map[0]: addr=0x43C10000, size=4096
The number of events is the total number of IRQs that was received by this device. So if you call lsuio after you received interrupts, this number may be different.
To find out whether the interrupt is configured correctly, we can execute the command below:
# cat /proc/interrupts
CPU0 CPU1
...
47: 21 0 GIC-0 61 Edge uio_fpga_base
...
First we can see that the IRQ61 is registered for our device. We can also see how many IRQs were handled by each CPU (in this case 21 IRQs were handled by CPU0).
A small user space application is provided along with the example in order to show how to use the UIO driver with IRQs from user space. The application is provided as Xilinx SDK project. The source file can be found in [root]/uio_driver_irq/app/src/helloworld.c.
The program only has a hand full of lines and explanatory comments. So it is not described here in more detail. Just have a look at the sources.
To build the application, follow the steps below:
- Start SDK
- Create/choose an empty workspace
- Click File > Import
- Choose General > Existing Projects into Workspace
- Select the folder [root]/uio_driver_irq
- Press Finish
In SDK you should now see a project called uio_test_irq. By default SDK should automatically build the project and produce a *.elf file. If you disabled automatic build in SDK, you have to manually build the project using Project > Build All.
Now copy the uio_test_irq.elf file to the directory /root/uio_driver_irq of your SD Card (rootfs partition).
Follow the steps below to load the kernel module:
Boot the target device.
Then navigate to the correct directory on your rootfs ...
cd /root/uio_driver_irq
... and start the application. Before, the driver must be loaded of course.
modprobe uio_pdrv_genirq of_id="generic-uio"
./uio_test_irq.elf
You should now see the following output from your application:
Hello World
version=0xAB12CD34
year=2020
sw-version=0x0000ABCD
Received IRQ
Received IRQ
Received IRQ
...
Note that the "year" output may change according to the year you built the FPGA bitstream in.
The Received IRQ messages pop up one after the other every second because an IRQ is detected every second. So our interrupts seem to work.
In this chapter, a simple UIO driver including IRQ was used. No kernel code was necessary to achieve this. Hence most FPGA IP can be supported this way without having to write drivers.
Of course only the very simplest case of a driver is covered, but this should be a good starting point to base your own development of a real (and more complex) driver on.
<< back | index | forward >>



