Firmware Development

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 InterruptISR RegisterSoftware Action
LOW0x0000 0000write 0x11c = 0x8000 0000
LOW0x0000 0000write 0x128 = 0x0000 0002 (ch2 are switches in my design)
HIGH0x0000 0002write 0x120 = 0x0000 0002 (toggle on write, so this resets)
LOW0x0000 0000read() /dev/uio1, does not return until next interrupt
FLIP A SWITCH
HIGH0x0000 0000read() returns, do something
LOW0x0000 0000write 0x128 = 0x0000 0002 (ch2 are switches in my design)
LOW0x0000 0000write() 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