UIO Interrupts
Testing UIO Interrupts
Over the last few posts I’ve been building what I consider to be a general purpose project for zybo. I have two GPIOs, one connected to LEDs and switches, and the other connected to the RGB led and the buttons. I have an axi-dma as well. The axi-dma and the GPIOs are all enumerated as uioX devices. I have u-dma-buf compiled in order to provide buffer space.
Being able to use the interrupts within userspace would be the next big thing on the list. Software should be able to keep running normally, not polling registers, and then reawaken when firmware completes and action. We’ll test this using the gpio1 device, which is connected to the switches and the LEDs of the zybo. All code for this example is at: https://github.com/breandan81/gpiodrv
While trying to find out how to do this I first found this example: https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842490/Testing+UIO+with+Interrupt+on+Zynq+Ultrascale
There is a problem with this example: It doesn’t work. The example says triggering the interrupt by writing to the ISR will “increment” the interrupt counter in /proc/interrupts. But it does not, since interrupts aren’t enabled by default, furthermore after the first interrupt further interrupts are masked, so it only counts to one if you do this.
The first piece: The GPIO Block Interrupt Output
The GPIO block has an output that connects to the Zynq PS block. First we enable the interrupts. We do this by writing 0x80000000 to 0x11C, the to 0x128 for channel one we write 0x1 and for channel 2 we write 0x2, for both 0x3. When an input pin changes, the interrupt pin goes high, and the bit in the ISR register (offset + 0x120) is set. The interrupt output stays high until the ISR Register is written with the bits that are high, this toggles the bits off and resets the interrupt.
The second piece: /dev/uioX
In my example the switches are connected to /dev/uio1. When the interrupt from GPIO goes high and interrupt is registered. This interrupt is masked until software unmasks it again by writing to /dev/uio1. If the interrupt pin is still high a new interrupt will immediately be generated. To wait for an interrupt, we can use “poll()” or “read()” on the file, it wont’ return until the next interrupt, allowing us to pause our thread. “poll” is more efficient.
The whole process looks like this:
GPIO Interrupt | ISR Register | Software Action |
LOW | 0x0000 0000 | write 0x11c = 0x8000 0000 |
LOW | 0x0000 0000 | write 0x128 = 0x0000 0002 (ch2 are switches in my design) |
HIGH | 0x0000 0002 | write 0x120 = 0x0000 0002 (toggle on write, so this resets) |
LOW | 0x0000 0000 | read() /dev/uio1, does not return until next interrupt |
FLIP A SWITCH | ||
HIGH | 0x0000 0000 | read() returns, do something |
LOW | 0x0000 0000 | write 0x128 = 0x0000 0002 (ch2 are switches in my design) |
LOW | 0x0000 0000 | write() to /dev/uio1, interrupts are now enabled again |
A Class for Handling UIO
A simple user space class for handling UIO is in my github. First we want a structure that matches the register space. We can then create a pointer and hand this map to it.
struct gpio_map
{
uint32_t ch1Data; // 0x00
uint32_t ch1Tristate; // 0x04
uint32_t ch2Data; // 0x08
uint32_t ch2Tristate; // 0x0C
uint32_t pad1[67];
uint32_t globalInt; //0x11C
uint32_t intStatus; //0x120
uint32_t pad2;
uint32_t intEn; //0x128
};
Now we have a class that allows us to interact through getters and setters.
class gpio
{
private:
gpio_map *map;
int fd;
public:
gpio(char *filename);
void enableInterrupt(bool ch1, bool ch2);
void enableInterruptGlobal(bool);
void waitInt();
void clearInt();
uint32_t getCh1Data();
uint32_t getCh1Tristate();
void setCh1Data(uint32_t data);
void setCh1Tristate(uint32_t tristate);
uint32_t getCh2Data();
uint32_t getCh2Tristate();
void setCh2Data(uint32_t data);
void setCh2Tristate(uint32_t tristate);
};
Walkthrough of Each Method
enableInterrupt – sets the interrupts for each channel
enableInterruptGlobal -sets the global interrupt
waitInt – calls read() on the file descriptor, does not return until interrupt happens
clearInt – writes ISR to reset, the calls write() on file descriptor to unmask interrupt
getChXData – returns a uint32_t containing the data in channel X register
setChXData – set the value of the data register
setChXTristate- sets the tristate register, allowing the direction of i/o pins to be changed
Complete Source of gpio.cpp
#include "gpio.h"
#include <iostream>
using namespace std;
gpio::gpio(char *filename)
{
char *addr;
fd = open (filename, O_RDWR | O_SYNC);
if(fd == -1)
{
cout << "failed to open device" << endl;
exit(1);
}
addr = (char *)mmap(NULL, 65536, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(addr == MAP_FAILED)
{
cout << "mmap failed";
exit(1);
}
map = (gpio_map *) addr;
}
void gpio::enableInterrupt(bool ch1, bool ch2)
{
uint32_t val = 0;
if(ch1)
{
val |= 0x1;
}
if(ch2)
{
val |= 0x2;
}
map->intEn = val;
}
void gpio::enableInterruptGlobal(bool en)
{
if(en)
{
map->globalInt = 0x80000000;
}
else
{
map->globalInt = 0;
}
}
void gpio::clearInt()
{
uint32_t info = 1;
uint32_t intStatus;
intStatus = map->intStatus;
if(intStatus)
{
map->intStatus = intStatus;
}
ssize_t nb = write(fd, &info, sizeof(info));
if( nb != (ssize_t)sizeof(info))
{
cout << "write error" << endl;
exit(1);
}
}
void gpio::waitInt()
{
uint32_t info = 1;
ssize_t nb = read(fd, &info, sizeof(info));
}
void gpio::setCh1Data(uint32_t data)
{
map->ch1Data = data;
}
void gpio::setCh2Data(uint32_t data)
{
map->ch2Data = data;
}
uint32_t gpio::getCh1Data()
{
return map->ch1Data;
}
uint32_t gpio::getCh2Data()
{
return map->ch2Data;
}
void gpio::setCh1Tristate(uint32_t tristate)
{
map->ch1Tristate = tristate;
}
uint32_t gpio::getCh1Tristate()
{
return map->ch1Tristate;
}
void gpio::setCh2Tristate(uint32_t tristate)
{
map->ch2Tristate = tristate;
}
uint32_t gpio::getCh2Tristate()
{
return map->ch2Tristate;
}
A Simple Helloworld
First we create a new gpio instance, I’ve named it “leds. Pass it the path to the device you would like to open (“/dev/uio1”). Enable the channel two interrupt (thats where my switches are). Enable global interrupt. Clear the interrupt register and unmask it. Now we just wait until a switch is moved, print the current state, and clear the interrupt, in a loop:
#include <iostream>
#include "gpio.h"
using namespace std;
int main()
{
gpio leds((char *) "/dev/uio1");
leds.enableInterrupt(false, true);
leds.enableInterruptGlobal(true);
leds.clearInt();
uint32_t i=0;
while(1)
{
leds.waitInt();
leds.setCh1Data(0xFFFF);
cout << "switch changed " << leds.getCh2Data() << endl;
leds.clearInt();
}
}
Output
LinuxBoot:~/gpiodrv$ sudo ./helloworld
Password:
switch changed 1
switch changed 5
switch changed 4
switch changed 6
switch changed 14
switch changed 10
switch changed 14
switch changed 12
Leave a Reply