• Interrupt driven user space application with the uio driver


    I would like to present here a simple solution to write an interrupt driven user space application with the help of the generic user IO kernel driver. It permits sharing a part of the memory to the user space and catch a given interrupt without the need of programming a specific kernel driver.

    We will go through an example on a zynq platform and a programmable logic that raises an interrupt after filling some memory area with a counter.

    The programmable logic

    Unfortunately, the programmable logic was not written by myself because this is not my domain of action and I have colleagues who are good at writing VHDL. I just had to make sure that the FPGA is programmed correctly.

    On the zynq there are different flavors of doing this, but it's not the purpose of this article to explain this, you can refer to the Xilinx documentation instead.

    Kernel configuration

    Here is how you can enable the uio driver in the kernel configuration menu:

    Device Drivers  --->
      <*> Userspace I/O drivers  --->
        <*>   Userspace I/O platform driver with generic IRQ handling
        <*>   Userspace platform driver with generic irq and dynamic memory
    

    This should activate the following options in your .config:

    CONFIG_UIO=y
    CONFIG_UIO_PDRV_GENIRQ=y
    CONFIG_UIO_DMEM_GENIRQ=y
    

    Device tree

    To activate the driver, you have to update the device tree with the IRQ information and memory space that you want to share. In our example, the FPGA will write at address 0x100000 and use the IRQ 61.

    An common mistake is not defining the right address size, it must be aligned with the page size. On the zynq, it must be a multiple of 0x1000 (4Kb).

    On zynq platform, computing the IRQ number is a bit special ... For more details please read this excellent article. Basically what we need to do here is 61 - 32 that gives use the interrupt 29.

    &amba {
        counters@100000 {
            compatible = "fpga-counter";
            reg = < 0x100000 0x1000 >;
            interrupts = < 0 29 1 >;
            interrupt-parent = <&intc>;
        };
    };
    

    Command line arguments

    The uio driver need some command line argument in order to know on which compatible driver is our generic uio driver mapped (compatible). In our case, this is what need to be added:

    uio_pdrv_genirq.of_id="fpga-counter"
    

    Verifying

    Once we have uploaded the new kernel, device tree and updated the command line arguments, we can verify that the kernel implementation works as expected.

    The first thing to check is if the command line argument was given correctly:

    # cat /proc/cmdline
    console=ttyPS0,115200 quiet uio_pdrv_genirq.of_id=fpga-counter root=/dev/mmcblk0p2 rw rootwait rootfstype=ext4
    

    A new uio char device should be available in /dev and /sys/class/uio

    # ls /sys/class/uio/
    uio0
    # cat /sys/class/uio/uio0/name
    counters
    # ls -l /dev/uio0
    crw-------    1 root     root      245,   0 Sep  7 14:17 /dev/uio0
    

    Also make sure that the correct interrupt is registered:

    # cat /proc/interrupts
               CPU0       CPU1
    ...
    167:          0          0     GIC-0  61 Edge      counters
    ...
    

    If ereything look good, we can now write the user space application to catch interrupts raised by the FPGA.

    uio user space interface

    Once the char device is available, you can access it with the standard C library calls.

    open(...)

    To open the char device and get a file descriptor.

    read(...)

    Blocking read until an interrupt is raised. The result will contain the amount of interrupts that occured. It is important to read 32 bits, otherwise you will get an error from the driver.

    If you don't want to block on the read and do something else in background, a select call can be used as well.

    write(...)

    The interrupt must be acknowledged with a write. Also heremake sure that you write a 32 bits value, to avoid an error from the driver. The written value must be bigger or equal to 1, otherwise the interrupt will not be acknowledged and not be raised again.

    mmap(...)

    To map the memory region to the user space. As in the device tree, the length argument must be aligned to the page size, otherwise you will get an error from the driver.

    Example application

    Once we know everything we need to start coding, we can write a small user space application:

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/mman.h>
    
    #include <fcntl.h>
    #include <errno.h>
    #include <string.h>
    #include <stdint.h>
    #include <unistd.h>
    #include <stdio.h>
    
    #define UIO_DEVICE    "/dev/uio0"
    #define MMAP_SIZE     0x1000
    
    int main(int argc, char* argvp[])
    {
      int retCode = 0;
      int uioFd;
      volatile uint32_t* counters;
    
      // Open uio device
      uioFd = open(UIO_DEVICE, O_RDWR);
      if(uioFd < 0)
      {
        fprintf(stderr, "Cannot open %s: %s
    ", UIO_DEVICE, strerror(errno));
        return -1;
      }
    
      // Mmap memory region containing counters value
      counters = mmap(NULL, MMAP_SIZE, PROT_READ, MAP_SHARED, uioFd, 0);
      if(counters == MAP_FAILED)
      {
        fprintf(stderr, "Cannot mmap: %s
    ", strerror(errno));
        close(uioFd);
        return -1;
      }
    
      // Interrupt loop
      while(1)
      {
        uint32_t intInfo;
        ssize_t readSize;
    
        // Acknowldege interrupt
        intInfo = 1;
        if(write(uioFd, &intInfo, sizeof(intInfo)) < 0)
        {
          fprintf(stderr, "Cannot acknowledge uio device interrupt: %s
    ",
            strerror(errno));
          retCode = -1;
          break;
        }
    
        // Wait for interrupt
        readSize = read(uioFd, &intInfo, sizeof(intInfo));
        if(readSize < 0)
        {
          fprintf(stderr, "Cannot wait for uio device interrupt: %s
    ",
            strerror(errno));
          retCode = -1;
          break;
        }
    
        // Display counter value
        printf("We got %lu interrupts, counter value: 0x%08x
    ",
          intInfo, counters[0]);
      }
    
      // Should never reach
      munmap((void*)counters, MMAP_SIZE);
      close(uioFd);
    
      return retCode;
    }
    

    If everything works fine, you should get some event from the driver:

    ...
    We got 63 interrupts, counter value: 0x9e9fffa6
    We got 64 interrupts, counter value: 0x9e9fffa7
    ...
    

    The amount of interrupts should correspond to what is in /proc/interrupts

    # cat /proc/interrupts
               CPU0       CPU1
    167:         64          3     GIC-0  61 Edge      counters
  • 相关阅读:
    mysql 语句case when
    Hibernate应用SQL查询返回实体类型
    JavaBean 和 Map 之间互相转换
    基于注解风格的Spring-MVC的拦截器
    Spring MVC与表单日期提交的问题
    自适应网页设计(Responsive Web Design)
    JSP页面用EL表达式 输出date格式
    EL表达式中如何截取字符串
    DOCTYPE html PUBLIC 指定了 HTML 文档遵循的文档类型定义
    javascript对table的添加,删除行的操作
  • 原文地址:https://www.cnblogs.com/dream397/p/13555913.html
Copyright © 2020-2023  润新知