GPIO and Petalinux — Part 1
When I first started looking into Petalinux and learn the basics, I thought I should start my journey with a simple GPIO toggling and an interrupt. I’ve soon found out there was more to it than meets the eye. There were dozens of articles online and a lot of buzzwords, like device tree or binding. The simple task turned to be a challenging one, since I’m not the kind of engineer who likes to copy-paste the source code without understanding what I’m doing and gluing all parts together was not easy as I’ve expected.
In this article, and the others that will follow, I’ll try to explain what GPIO drivers are all about. I’ll give a full step-by-step guide to create a Petalinux image with UIO driver and interrupt support. Indeed, there are many tutors online, so I tried to give an added value in my tutors over the ones already exist. If you want to skip the theoretical part of the GPIO drivers, you can jump directly to part 2 (I’ll post it in the following weeks). Otherwise, grab a beer, and without further ado, let’s start at the very beginning:
User space and Kernel space
A modern computer OS, like Linux, has two distinct, separated areas in the memory region called User space and Kernel space. Gaining a solid understanding of the two will help in writing and debugging a device driver for the specified task, in our case — toggle an IO and hit an interrupt. Sounds simple, isn’t it? or maybe we should avoid writing our own driver? better keep reading…
We can divide this interface to three layers, as shown below. Each layer interacts with its neighboring layer, so eventually, when the user toggles an IO with the application, or turns on a LED, it goes through all layers:
When interacting with the hardware block, the kernel acts as a bridge between the hardware and the user-space (where all applications and programs reside). The kernel is in charge of scheduling processes to run, managing memory, interrupt handler, etc., and is located at the core of the host operating system with full control over the system’s resources. The rest of the OS serves to boot and manage the user space and communicates constantly with the kernel. In Linux the user space has access to the virtual memory, which is managed by the kernel (virtually mapped). In term of IO — the kernel provides the lowest level abstraction layer for the resources (especially IO devices) that application software must control to perform its function.
The kernel and the user space communicate via ‘system calls’. System call offers the OS its service to the user applications via API. It is the only way the kernel can interact with the user space; file and device management, communication, etc. You can see in the scheme above a few examples of common system calls used, like ‘open’ which is used when interfacing with a GPIO:
open(“/sys/class/gpio/gpio101/value, O_RDWR).
This system call is executed by the application to open and assign a file descriptor to gpio101 value. The task itself is carried out by the kernel.
The conventional driver method is that all hardware is accessed by the kernel, which means one needs to write a kernel driver. This is NOT recommended and there are many reasons one should avoid that. For start, a bug in the kernel driver can bring all systems to a halt (compared to bug in user space), and also the debugging is more difficult and time consuming.
“Few” words on GPIO drivers
There are many GPIO drivers out there, some of them are not supported by Xilinx (Petalinux), yet supported by other vendors (TI, for example, who manufactures the Beaglebone board, which has vast documentation online). Each driver has its good’s and bad’s and since I wanted to perform this very simple task with Petalinux and Zedboard, I eventually chose to work with the UIO driver, but there was a lot of interesting stuff along the way which was definitely worth talking about.
In Linux Everything is a file, which means that everything in the system (whether it is CPU’s, directories, sockets, printers, called devices or nodes) is handled by a file descriptor whenever the file is opened.
There are 2 main types to interface with these devices; /dev and /sys.
- ‘/dev’ exists from the earlier days of Unix system and includes the actual devices files created at run time by udev (udev creates nodes when devices are plugged in and remove them once plugged out). It is a real file-system, disk based.
- ‘/sys’ (abbreviated sysfs located at /sys/class/gpio), on the other hand, was added later and includes a complete hierarchy of the devices as attached to the computer, as well as an interface to a variety of device properties such as bus settings, bus speeds, device IDs, device names, etc. In /sys/class there is a directory for each different class of device. Opposed to /dev, /sys is a virtual file-system (RAM based).
Linux enables access to GPIO from kernel driver, but exposes GPIO to userspace applications through handles in sysfs (/sys/class/gpio). Obviously a kernel driver is not a recommended method for the average user, and I was looking for a much easier way through the user space. SysFs/GPIO driver is one method, explained nicely in Xilinx tutorial by Rob Armstrong here.
There are number of methods to access the GPIO in embedded Linux environment:
- SysFs driver: The SysFs interface (based on “gpiolib” framework), as mentioned above, is a very simple method accessing the various GPIO’s in command line or code from the user space. Yet, this method should not be used where interrupts are required (the polling process to catch events, like interrupts from GPIO lines, is not reliable) . I do recommend going over the xilinx-wiki page of SysFs and try accessing the IO’s just to get the hang of it, as it is very straightforward method. I’ll show later on few examples. Also, important to add this method should be removed from the Linux kernel as of version 4.8. Currently, Petalinux fully supports that.
- gpio-keys driver: A Linux Kernel driver. A powerful alternative to the SysFs interface, include interrupt support (only) to a pressed key.
- gpio-keys-polled driver: A Linux Kernel driver. It is used when GPIO line cannot generate interrupts, so it needs to be periodically polled by a timer.
- leds-gpio driver: A Linux Kernel driver. It will handle LEDs connected to GPIO lines from user space, giving the LED a sysfs interface. As the name implies, it is suitable for any output-only GPIO applications.
- UIO driver: The Userspace I/O framework is a simple and convenient way to implement a driver almost entirely in user space, and fully support interrupts. It seems almost tailored for engineers seeking to interface with FPGA’s on their boards and running embedded Linux (e.g., Petalinux).
Other two user space interfaces used in embedded Linux are shown below. Both deprecates the legacy SysFs interface to GPIOs (gpiolib based):
- chardev — explained here, supported by kernel version 4.8 and above (Petalinux kernel version is 4.19). It is considered a recommended kernel API for GPIO as SysFs is deprecated as of Linux 4.8 . It prevents manipulating GPIO with standard command line tools such as echo and cat, so less convenient, though supports read and set multiple I/O lines at once. As far as I know, Petalinux does not support that.
- gpio-cdev — explained here, supported by kernel version 4.4 (Petalinux kernel version is 4.19). Used extensively in BeagleBone boards. As far as I know, Petalinux does not support that.
Now that I have gone through the various drivers, it’s time to summarize the applicable methods. There are three main methods which we can choose to work with:
- Using one of the drivers above (kernel or user space).
- Write my own kernel driver.
- Use mmap with /dev/mem.
I’ve covered (1), so let’s discuss (2) and (3):
Regarding (2), as I wrote earlier, usually, it is not recommended and better avoided. For many types of devices, creating a Linux kernel driver is an overkill. It is much more complicated compared to the other options, and usually requires code in the kernel. Nonetheless, it is considered the fastest of the three types. Compared to user space drivers, it is much more challenging to debug Kernel space drivers. Also, in user-space, one can write the driver in Python, or any other language, compared to C in kernel space.
Regarding (3), /dev/mem is a character device which is an image of the main memory (the physical one, not the virtual, which can be accessed using /dev/kmem). /dev/mem image includes the RAM and also memory mapped IO devices. To access the device from memory space, we use mmap to map the device to memory, and then we access the device using a pointer which points to the mapped memory. It is easy to set up and debug (can do it from shell), but there’s no support with interrupts, and I did wanted to demonstrate an interrupt IO, remember?
A working code for /dev/mem method can be found here.
UIO driver
Since my article is dedicated to the design of a user space driver based on UIO (User Space IO), I wanted to dedicate a section of its own. As I wrote earlier, it is not necessary and better avoided to design a custom driver (kernel based) just for accessing memory-mapped registers in a custom IP core. Also, when interrupts are needed (like in FPGA designs), UIO driver is the way to go with.
This type of driver enables writing the majority of the code in user space with very little code in the kernel itself. Just like the /dev/mem method, it is a character device (with little help from SysFs) that the user can open, memory map, and access the device using a pointer. The UIO enables the driver to be entirely in the user space and, compared to other methods mentioned above (including the /dev/mem), it fully support interrupts. I’ll show later on few examples. To use generic UIO, we’ll need to add few lines to the device tree (I’ll show that either).
The UIO devices can be found in 2 places in Embedded Linux (or Petalinux for this guide), each location is used for different needs. To access the device, to clear interrupts, for instance, we need to go to /dev/UIOx (where ‘x’ is the running index of the device attached):
In the picture we see 2 drivers, uio0 & uio1 (they correspond to 2 PL components in my design I’ll show in my future tutorials).
In /sys/class/UIOx there’s much more information about the attached device, and many more goodies (in part 2, I’ll dive deeply into these):
Is User Space driver (and UIO) always better?
To make things more complex, user space drivers are not always the best choice to go with. The kernel reserves some ‘spare’ memory for use during “emergency” cases, but that is not a viable option for users-space drivers, so in low-memory times, the kernel will kill random user-space programs, but will never kill kernel threads. Furthermore, resource sharing is an important factor in favor of the custom kernel driver, too. When multiple applications need to share a device between them (involving concurrent access to memory, etc.), user space drivers are better left behind.
When Ethernet or DMA application is needed, the kernel custom driver is the one to go with. Using a user space driver in that case would be much more complicated and that is because DMA capable memory can be allocated from kernel space much more easily than user space. Here’s a nice solution to DMA from user space, though less recommended.
Going back to /dev/mem, what is the difference between UIO and /dev/mem, as both reside in the user space?
As a rule of thumb, it is better to use UIO driver. There are mainly 2 points to consider in this context:
- Interrupts: as discussed, supported only in UIO driver. Interrupts should be registered in the kernel, since the user space cannot deal with it, so a minimal small module exist in the kernel, containing ISR to acknowledge or disable the interrupt, yet, all other issues are handled in the user space.
- Permissions: /dev/mem enables access to all memory regions and open your system to security risks whereas UIO improved it by preventing full access to all memory regions.
There are many links and tutorials regarding the UIO. Here you can find a nice source code for the UIO and a thorough article here.
In my next tutorial I will go over the basics of creating a Petalinux image, the first step towards a simple working interrupt driven design with a UIO driver.