Tuesday, December 19, 2017

Petalinux From Scratch (Xilinx MPSoC ZCU102) - Create UIO Driver with IRQ

Create UIO Driver with IRQ


Setup IRQ pin and Interrupt ID in Vivado
For example, I have connected UIO module to pl_ps_irq0[0], see illustration below


https://lh4.googleusercontent.com/vnSdjtZ4yOUeN1N9dhAXeJ0kV4Eu-NgBhoeyZETAfdunnAfpjOBLNIjKux1Mh0iKwRTYkpfGnNS8YwQTjGnGEjYWHt_mXnGhk6b1JDC2lHrqgS0QdihyDaWW0HgFt0JzBJbXnFkR


In my example case pl_ps_irq0[0] mapped to Interrupt ID is 121
https://lh4.googleusercontent.com/9jlN9UbBPbNe4Cm4Diztq4C3C8x62UwO51R95dxZofPTZwNRcN4821AUgD2za3Ss8hlm6bJ8zgR5riU9nXu6vKkJHhe2xLToOh26xtcob-0xmODwMDdxeItdRj5NWIwfXJxuWe1-


UIO support in Linux kernel
Run kernel configuration after sourcing Petalinux tools,
petalinux-config -c kernel

In the menu go to:  “Device Drivers--->”


https://lh4.googleusercontent.com/jp4pcIQPE8eUv4055FHrPuu6jUmIRqWZLgaB7O25aQ5rp7Hk9oHu16_Yx0f_K82-Ex9JeV0GwBHZAh_vcMtEDxI9LeddoPucoVue3cWNbYZCT7Pl1Qr5E30DWnOaSfpMeydpiD7q
In the “Device Drivers” menu scroll down to the "Userspace I/O drivers --->"


https://lh6.googleusercontent.com/374fhuI_NTpe-HNQOjnNAj4-ZFDPI1vLXftVzDAFQo21nmmhXUPBhGO-YbLldIqFRJzshX8pv0OBe8VTjNwIjQ8zCAADyTky7H-yBUHaFDI1eHgKPaPUazZiBV4XX-tu80bmIcVs


The kernel is configured to support loadable modules by default, for those loadable device drivers,
we can select it as built-it or module. "<*>" means built-in and "<M>" means module. If a driver is selected
as a module, it will not be loaded when booting Linux.  We can load it after Linux boots by using the
modprobe command (see below).


https://lh6.googleusercontent.com/SwOHIqtea6wOzfkSPdLJD7HvLER6AYlP8FWIK_JlA0WlFt88eNNDNfsCCS2E2jCHzv6G01WSUGXB4fISPCu3QJDUZ90f-Mmixoz309D6KI3nJvgERrAqac8F8LSdvB3POR5GXVip


Exit the kernel “menuconfig” and save the configuration.


IRQ Configuration of interrupts and interrupt-parent
The "interrupt-parent" property is used to specify the controller to which interrupts are routed and
contains a single phandle referring to the interrupt controller node. This property is inherited, so it may be
specified in an interrupt client node or in any of its parent nodes.

Interrupts listed in the "interrupts" property are always in reference to the node's interrupt parent.

Reference
https://www.kernel.org/doc/Documentation/devicetree/bindings/interrupt-controller/interrupts.txt


The “interrupt” property contains 3 values:
1. The first value is a flag indicating if the interrupt is an SPI (shared peripheral interrupt). A nonzero value
means it is an SPI.
2. The second value is the interrupt number. The translate function adds 16 to SPIs and 32 to non-SPIs,
so for interrupts generated by fabric logic in a Zynq, the number in the DTS file should be the hardware
number (as shown in Xilinx Vivado) minus 32.
3.The third value is the type of interrupt, which is ANDed with IRQ_TYPE_SENSE_MASK (= 0x0f), which is
defined in include/linux/irq.h.
Refer to the values in the “enum” clause: IRQ_TYPE_LEVEL_HIGH is 4 and IRQ_TYPE_EDGE_RISING is 1

For example, in my case HW interrupt number is 121, and I need rising edge IRQ the first value is 0, it’s
declared as a non-SPI. The second value should be the hardware number minus 32, which is 89, or 0x59.
The third value says IRQ_TYPE_EDGE_RISING, all combined results below.
interrupts = <0x0 0x59 0x1>


From irq.h


/*
* IRQ line status.
* Bits 0-7 are the same as the IRQF_* bits in linux/interrupt.h
* IRQ_TYPE_NONE        - default, unspecified type
* IRQ_TYPE_EDGE_RISING        - rising edge triggered
* IRQ_TYPE_EDGE_FALLING    - falling edge triggered
* IRQ_TYPE_EDGE_BOTH        - rising and falling edge triggered
* IRQ_TYPE_LEVEL_HIGH        - high level triggered
* IRQ_TYPE_LEVEL_LOW        - low level triggered
* IRQ_TYPE_LEVEL_MASK        - Mask to filter out the level bits
* IRQ_TYPE_SENSE_MASK        - Mask for all the above bits
* IRQ_TYPE_PROBE        - Special flag for probing in progress
*
* Bits which can be modified via irq_set/clear/modify_status_flags()
* IRQ_LEVEL            - Interrupt is level type. Will be also
*                 updated in the code when the above trigger
*                 bits are modified via irq_set_irq_type()
* IRQ_PER_CPU            - Mark an interrupt PER_CPU. Will protect
*                 it from affinity setting
* IRQ_NOPROBE            - Interrupt cannot be probed by autoprobing
* IRQ_NOREQUEST        - Interrupt cannot be requested via
*                 request_irq()
* IRQ_NOTHREAD            - Interrupt cannot be threaded
* IRQ_NOAUTOEN            - Interrupt is not automatically enabled in
*                 request/setup_irq()
* IRQ_NO_BALANCING        - Interrupt cannot be balanced (affinity set)
* IRQ_MOVE_PCNTXT        - Interrupt can be migrated from process context
* IRQ_NESTED_TRHEAD    - Interrupt nests into another thread
* IRQ_PER_CPU_DEVID        - Dev_id is a per-cpu variable
*/
enum {
IRQ_TYPE_NONE            = 0x00000000,
IRQ_TYPE_EDGE_RISING    = 0x00000001,
IRQ_TYPE_EDGE_FALLING    = 0x00000002,
IRQ_TYPE_EDGE_BOTH    = (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING),
IRQ_TYPE_LEVEL_HIGH        = 0x00000004,
IRQ_TYPE_LEVEL_LOW        = 0x00000008,
IRQ_TYPE_LEVEL_MASK    = (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH),
IRQ_TYPE_SENSE_MASK    = 0x0000000f,
IRQ_TYPE_PROBE        = 0x00000010,
IRQ_LEVEL            = (1 <<  8),
IRQ_PER_CPU        = (1 <<  9),
IRQ_NOPROBE        = (1 << 10),
IRQ_NOREQUEST        = (1 << 11),
IRQ_NOAUTOEN        = (1 << 12),
IRQ_NO_BALANCING    = (1 << 13),
IRQ_MOVE_PCNTXT    = (1 << 14),
IRQ_NESTED_THREAD    = (1 << 15),
IRQ_NOTHREAD        = (1 << 16),
IRQ_PER_CPU_DEVID    = (1 << 17),
};


Verify amba_pl address in device-tree
  • Before changing system-user.dtsi device tree, view amba_pl settings in pl.dtsi
  • Addresses of pl.dsti & system-user.dsti must be identical, although system-user.dsti override settings of
  • pl.dtsi after complication.
    Note: In order to view pl.dtsi, you need to compile the whole project.
  • For example, our device tree generation (.dtsi) is:
    ~/workspace/peta-builds/bsp/xilinx-zcu102-2017.2/components/plnx_workspace/device-tree-generation/pl.dtsi
{
   amba_pl: amba_pl@0 {
       #address-cells = <2>;
       #size-cells = <2>;
       compatible = "simple-bus";
       ranges ;
       axi_lite_ipif_danr_0: axi_lite_ipif_danr@a0000000 {
           compatible = "xlnx,axi-lite-ipif-danr-1.0";
           reg = <0x0 0xa0000000 0x0 0x4000000>;
           xlnx,ard-addr-range-array-high = <0x00000000A3FFFFFF>;
           xlnx,ard-addr-range-array-low = <0x00000000A0000000>;
           xlnx,ard-num-ce-array-0 = <0x1>;
           xlnx,ard-num-ce-array-1 = <0x0>;
           xlnx,dphase-timeout = <0xa>;
           xlnx,s-axi-min-size = <0x03FFFFFF>;
           xlnx,use-wstrb = <0x0>;
       };
       psu_ctrl_ipi: PERIPHERAL@ff380000 {
           compatible = "xlnx,PERIPHERAL-1.0";
           reg = <0x0 0xff380000 0x0 0x80000>;
       };
       psu_message_buffers: PERIPHERAL@ff990000 {
           compatible = "xlnx,PERIPHERAL-1.0";
           reg = <0x0 0xff990000 0x0 0x10000>;
       };
   };
};


Update UIO system-user.dtsi
  • Edit ~/workspace/peta-builds/bsp/xilinx-zcu102-2017.2/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
  • Copy pl.dtsi content and modify the “compatible” value to
compatible = "xlnx,generic-uio", "uio_pdrv_genirq";
  • Add “interrupts” and “interrupt-parent” values according to your HW settings
    interrupts ​​= <0x0 ​0x59 0x1>;
interrupt-parent ​= ​<0x4>;
/include/ "system-conf.dtsi"
/ {
  amba_pl@0 {
     /delete-node/ axi_lite_ipif_danr@a0000000;
  };
};
/ {
  amba_pl@0 {
     compatible = "xlnx,zynqmp";
     axi_lite_ipif_danr@a0000000 {
         compatible = "xlnx,generic-uio", "uio_pdrv_genirq";
         reg = <0x0 0xa0000000 0x0 0x4000000>;
         xlnx,ard-addr-range-array-high = <0x00000000A3FFFFFF>;
         xlnx,ard-addr-range-array-low = <0x00000000A0000000>;
         xlnx,ard-num-ce-array-0 = <0x1>;
         xlnx,ard-num-ce-array-1 = <0x0>;
         xlnx,dphase-timeout = <0xa>;
         xlnx,s-axi-min-size = <0x03FFFFFF>;
         xlnx,use-wstrb = <0x0>;
         interrupts = <0x0 0x59 0x1>;
         interrupt-parent = <0x4>;
      };
   };
};


Compile and run the project
Compile the project again
Petalinux-build


Create BOOT.BIN package
cd ~/workspace/peta-builds/bsp/xilinx-zcu102-2017.2/images/linux petalinux-package --boot
--fsbl zynqmp_fsbl.elf --u-boot --force


Option to create BOOT.BIN with FPGA bit stream
petalinux-package --boot --fsbl zynqmp_fsbl.elf --fpga zcu102_top.bit --u-boot --force


Copy the generate files to SD Card
~/workspace/peta-builds/bsp/xilinx-zcu102-2017.2/images/linux
BOOT.BIN
image.ub
system.dtb
Copy FPGA project bitstream file from vivado project and rename it to system.bit if not included in BOOT.BIN

Verify UIO configuration
root@xilinx-zcu102-2017_2:~# ls /dev/u*
root@xilinx-zcu102-2017_2:~# /dev/uio0     /dev/urandom


Verify UIO IRQ configuration
Execute cat /proc/interrupts


Simple UIO validation
I my case I had PL logic connected to LEDs on board and with devmem I was able to turn ON/OFF LEDs.
ON - devmem 0x00A0010010 h 0xf
OFF - devmem 0x00A0010010 h 0x0


Simple IRQ validation
Before triggering the IRQ we will run again “cat /proc/interrupts” command we will see that our IRQ counter
for  [YOUR_FPGA_DEVICE] is 0


Run again your test, in my case with devmem I trigger FPGA device to raise interrupt and now execute agan
“cat /proc/interrupts” you will see that interrupt counter raised to 1


Simple UIO test C application
In my case my PL device was mapped to 0x4000000
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <sys/mman.h>
#include <fcntl.h>

#define MAP_SIZE 0x4000000

void usage(void) {
printf("usage: test_counters -d <UIO_DEV_FILE>n");
}

int main(int argc, char *argv[]) {
   int c, i, direction = 0;
   int fd = 0;
   char *uiod = 0;

   volatile unsigned *gpio;

   while ((c = getopt(argc, argv, "d:io:hg:")) != -1) {
       switch(c) {
       case 'd':
           uiod = optarg;
           break;
case 'g':
           direction = atoi(optarg);
           break;
       case 'h':
           usage();
           return 0;
       default:
           printf("invalid option: %cn", c);
           usage();
           return -1;
       }
   }

   if (!uiod) {
       usage();
       return -1;
   }

printf ("Open UIO ...\n");
   /* Open the UIO device file */
   fd = open(uiod, O_RDWR);
   if (fd < 1) {
       perror(argv[0]);
       printf("Invalid UIO device file: '%s'n", uiod);
       return -1;
   }

printf ("Map UIO ...\n");
   /* mmap the UIO device */
   gpio = (volatile unsigned *)mmap(NULL, MAP_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
   if (!gpio) {
       perror(argv[0]);
       printf("mmapn");
       return -1;
   }

unsigned char * ptr = (unsigned char *)gpio;

if (direction) {
ptr[0x10010] = 0xF;
} else {
ptr[0x10010] = 0x0;
}

printf ("Unmap UIO ... %d\n", direction);
   munmap((void*)gpio, MAP_SIZE);
   return 0;
}


Compile your test C file with tool-chain downloaded into Petalinux tmp folder while downloading sources and compiling or
if you have Xilinx SDK installed,
export SYSROOT=~/workspace/Xilinx-SDK/SDK/2017.2/gnu/aarch64/lin/aarch64-linux/bin
Compile your test
SYSROOT/aarch64-linux-gnu-gcc uio-test.c -o uio-test


Run test
./uio-test -d /dev/uio0

1 comment: