Userspace I/O (UIO)

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.

In the system you are given in this class, the buttons, LEDs, switches and interrupt controller 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:

  • Access memory-mapped device registers.
  • Notify user code of device interrupts.

Access Device Registers

Since Linux uses a virtual address system, you cannot directly access the physical addresses of devices. You can gain a virtual address pointer to the device by using mmap() on the UIO device file for the device. 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. Then in user code you can call your own interrupt handler. This user space interrupt handler can then do any “handling of the interrupt”, before notifying the UIO to re-enable the interrupt line in the operating system.

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, thus I found it necessary to notify the UIO to enable the interrupt line on initialization, before I started waiting for interrupts, and would do so again after detecting each 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.

Important Notes (DON’T IGNORE THESE!!)

  • By default, users are not allowed to access device files. You will need to execute your code as sudo.
  • The UIO will only respond to read() and write() 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.