The UIO is a general purpose kernel driver that can be accessed from user space. The purpose of the driver is to act as a thin layer between user space programs and the hardware. In Linux, user programs cannot access hardware directly; so the UIO bridges this gap.
On the PYNQ board, the buttons, LEDs, switches, interrupt controller, and DMA are all accessed via UIO. See the Software Stack page.
Documentation of the UIO is available here. At minimum, read the section on How UIO works.
For this class we will use the UIO for two purposes:
- Allow user code to access memory-mapped device registers (user code cannot directly access physical addresses).
- Allow user code to query the operating system to see if the device generated an interrupt.
Access Device Registers
Since Linux uses a virtual address system, you cannot directly access physical memory addresses without a virtual address pointer that maps to the physical address. The security model of Linux does not allow user space programs to directly request such a pointer. However, the UIO driver bridges this gap, and can provide a virtual address pointer to the physical address space of the device.
This is done by using mmap() on the UIO device file. The UIO device files for each hardware device are listed on the Software Stack page. See example code below.
Interrupts
Linux provides a number of UIO variants, for different types of devices that a system might contain. In this class we are using the uio_pdrv_genirq variant (UIO, Platform Driver, Generic IRQ). This UIO variant is designed for embedded systems, and provides a generic interrupt handler. Make sure you read the short section on this variant: uio_pdrv_genirq. You can ignore anything that refers to writing your own kernel driver as we are not doing that. Just focus on how to actually use the UIO driver and the most important function calls mmap(), read(), and write().
When a UIO-managed device generates an interrupt, the UIO interrupt handler will ask Linux to disable interrupts from this device. That is all the interrupt handler does.
From user space, to check if an interrupt occurred for a device, you should perform a read() on the UIO device file. This will block until an interrupt is detected. For example, if you want to wait for the AXI Interrupt Controller, user_intc, to generate an interrupt, read from its UIO device file. If you don’t want to block, you can use the poll() system call to check if there is data to read from the UIO device file.
In order to enable and to re-enable interrupts, it is necessary to use the write() function to write a ‘1’ to the UIO device file. Note that this information is available via the uio_pdrv_genirq page that is referenced above, but it may be difficult to find. Here’s the relevant quote from that page: “After doing its work, userspace can reenable the interrupt by writing 0x00000001 to the UIO device file.”
Note: By the time your userspace code is running, it is likely that an interrupt has already occurred and the interrupt line has been disabled, so do this write() once in your init function as well, before you start waiting for interrupts.
After that, the natural place for the write() is wherever you acknowledge the interrupt, at the end of handling it. Acknowledge the hardware first: the interrupt controller holds its output asserted until it is cleared, so re-enabling Linux before that would immediately produce a duplicate interrupt.
Hint: If you need to find out how to use read() and write() for this lab, try not to ask the TAs, just hunt around the web and find your own answers. You will find lots of examples. Be sure to check the return values of these functions to see if they are successful.
Important Notes (DON’T IGNORE THESE!!)
- Don’t open a
/dev/uioNpath directly. The UIO numbers are assigned in the order devices are probed, so they change if hardware is added or removed. A udev rule on the board creates stable names under/dev/ecen427/(for example/dev/ecen427/switches), which is what theSYSTEM_*_UIO_FILEdefines in system.h point at. Always use those defines. - The UIO will only respond to
read()andwrite()operations that are 32 bits (4 bytes). Anything else will be ignored without you knowing.
Example Code
Your repo contains some example code for how to use a UIO driver.