UserSpace IO (UIO) Introduction
How can I access memory mapped custom IP registers from userspace without writing a kernel driver?
Problem Description
In many cases it is not necessary to write a custom kernel driver simply to access some memory-mapped registers in a custom IP core. The Userspace IO (UIO) framework even permits simple userspace interrupt handling.
UIO is not a solution in all cases, in particular it currently has the following limitations and restrictions:
- It is generally not possible to use UIO for device drivers which need to integrate in core parts of the Linux kernel, for example ethernet drivers, block device and mass storage etc. It is best suited to simple access to 'random logic' IPs
- It is not possible to perform DMA to or from UIO-managed devices, without a custom kernel driver. Userspace DMA is an advanced topic - please speak to your PetaLogix services engineer to discuss possible solutions
Background
In Linux and other modern operating systems, all access to physical hardware from application programs must be mediated through the kernel. This is to protect the integrity of the hardware from erroneous or malicious application software.
However, this requirement to create a kernel device driver for every possible device access can be time consuming and in some cases unnecessary. The UIO framework was designed to allow a light weight, low overhead access to hardware devices directly from userspace.
Methodology
To use generic UIO, you will need to change the device node's compatible property in the DTS file so the the generic UIO driver can be probed:
Here is an example of a device node in the DTS file:
<INSTANCE_NAME>: <INSTANCE_TYPE>@<BASEADDR> {
compatible = "generic-uio";
reg = < BASEADDR ADDR_SIZE >;
interrupt-parent = <INTR CONTROLLER>;
interrupts = < INTR_NUM SENSITIVITY >;
} ;
- "compatible" : It is used to match the "compatible" of the uio_of_genirq driver in Linux so that the device can be probed.
- "reg" : the physical base address and the address size
- "interrupt-parent" : the interrupt controller of the system
- "interrupts" : the interrupt number of the interrupt presented in the
- interrupt controller and the interrupt sensitivity type.
E.g.:
sys_gpio: xps-gpio@81400000 {
#gpio-cells = <2>;
compatible = "generic-uio";
gpio-controller ;
reg = < 0x81400000 0x10000 >;
xlnx,all-inputs = <0x0>;
...
xlnx,tri-default-2 = <0xffffffff>;
}
Enable the uio_of_genirq (UserIO, Generic IRQ handling) driver in the kernel menuconfig:
"Device Drivers" ---> |- "Userspace I/O drivers" ---> |- <*> Userspace I/O platform driver with generic IRQ handling |-<*> Userspace I/O OF driver with generic IRQ handling
When the system boots, the UIO device is represented in the filesystem as "/dev/uioN" where N is an incrementing integer value for each seperate UIO device. To uniquely identify which uioN corresponds to a particular device, at system boot time you can look in the sysfs path
/sys/class/uio
and the various virtual files and attributes present in that subdirectory.
For more details on how to use UIO, you can refer to this document in the Linux kernel documentation sources:
linux-2.6.x/Documentation/DocBook/uio-howto.tmpl
Once enabled at the kernel level, writing an application to access and control a 'generic-uio' class device is simple. Here is some sample application code for directly managing a Xilinx GPIO device from userspace, bypassing the usual kernel GPIO driver layer.
/* * This application reads/writes GPIO devices with UIO. * */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> #include <fcntl.h> #define IN 0 #define OUT 1 #define GPIO_MAP_SIZE 0x10000 #define GPIO_DATA_OFFSET 0x00 #define GPIO_TRI_OFFSET 0x04 #define GPIO2_DATA_OFFSET 0x00 #define GPIO2_TRI_OFFSET 0x04 void usage(void) { printf("*argv[0] -d <UIO_DEV_FILE> -i|-o <VALUE>\n"); printf(" -d UIO device file. e.g. /dev/uio0"); printf(" -i Input from GPIO\n"); printf(" -o <VALUE> Output to GPIO\n"); return; } int main(int argc, char *argv[]) { int c; int fd; int direction=IN; char *uiod; int value = 0; void *ptr; printf("GPIO UIO test.\n"); while((c = getopt(argc, argv, "d:io:h")) != -1) { switch(c) { case 'd': uiod=optarg; break; case 'i': direction=IN; break; case 'o': direction=OUT; value=atoi(optarg); break; case 'h': usage(); return 0; default: printf("invalid option: %c\n", (char)c); usage(); return -1; } } /* Open the UIO device file */ fd = open(uiod, O_RDWR); if (fd < 1) { perror(argv[0]); printf("Invalid UIO device file:%s.\n", uiod); usage(); return -1; } /* mmap the UIO device */ ptr = mmap(NULL, GPIO_MAP_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); if (direction == IN) { /* Read from GPIO */ *((unsigned *)(ptr + GPIO_TRI_OFFSET)) = 255; value = *((unsigned *) (ptr + GPIO_DATA_OFFSET)); printf("%s: input: %08x\n",argv[0], value); } else { /* Write to GPIO */ *((unsigned *)(ptr + GPIO_TRI_OFFSET)) = 0; *((unsigned *)(ptr + GPIO_DATA_OFFSET)) = value; } munmap(ptr, GPIO_MAP_SIZE); return 0; }
Customer stories

