The most basic way to communicate with peripheral devices in embedded computing is to use GPIO — General Purpose Input/Output. GPIOs are used to connect to wide range of peripherals — buttons, LEDs, and more complex ones. You can control GPIO directly, setting it to "0" or "1" — or read its current state to use it as input. There are 2 ways to work with GPIOs in OpenWRT — let's start with the common one.

Slow but universal: /sys/class/gpio

For some time past, Linux — including OpenWRT — has a standard way to access GPIOs from userspace: special files in /sys/class/gpio that you can read or write to. First, to make GPIO available to you, you have to write its number to /sys/class/gpio/export (let's use GPIO 27 for example):

echo "27" > /sys/class/gpio/export

If you prefer C languange:

void gpioExport(int gpio)
{
    int fd;
    char buf[255];
    fd = open("/sys/class/gpio/export", O_WRONLY);
    sprintf(buf, "%d", gpio); 
    write(fd, buf, strlen(buf));
    close(fd);
}

Now you have /sys/class/gpio27/ directory and you need to set direction — input or output:

echo "out" > /sys/class/gpio27/direction

Or

void gpioDirection(int gpio, int direction) // 1 for output, 0 for input
{
    sprintf(buf, "/sys/class/gpio/gpio%d/direction", gpio);
    fd = open(buf, O_WRONLY);

    if (direction)
    {
        write(fd, "out", 3);
    }
    else
    {
        write(fd, "in", 2);
    }
    close(fd);
}

Let's use it as output and set to "1":

echo "1" > /sys/class/gpio27/value

Or just call gpioSet(27, 1):

void gpioSet(int gpio, int value)
{
    sprintf(buf, "/sys/class/gpio/gpio%d/value", gpio);
    fd = open(buf, O_WRONLY);
    sprintf(buf, "%d", value);
    write(fd, buf, 1);
    close(fd);
}

Looks simple, isn't it? Unfortunately, every magic comes with a price: /sys/class/gpio method is easy to understand and implement in almost any language (for example, you can control GPIO directly from the web interface with PHP), but painfully slow. It is fast enough for switching relay on or off, but on the brink of useable even for controlling a single LED's brightness with software PWM. Fortunately, there is another way to control GPIO — directly with AR9331's registers. And in fact in C/C++ it is no more complicated than using /sys/class/gpio, though may not be usable in some higher-level languages.

Fast but just a bit more complicated: direct access to registers

All the peripheral modules of the AR9331 SoC controlled through setting or clearing bits in registers mapped at some memory addresses — and we can access those addresses with the standard /dev/mem interface. Addresses and bits are described in AR9331 datasheet, page 65, and base address is 0x18040000.

First, let's open /dev/mem and obtain a pointer to the block we need with mmap function:

#define GPIO_ADDR 0x18040000 // base address
#define GPIO_BLOCK 48 // memory block size

volatile unsigned long *gpioAddress;

int gpioSetup()
{
    int  m_mfd;
    if ((m_mfd = open("/dev/mem", O_RDWR)) < 0)
    {
        return -1;
    }
    gpioAddress = (unsigned long*)mmap(NULL, GPIO_BLOCK, PROT_READ|PROT_WRITE, MAP_SHARED, m_mfd, GPIO_ADDR);
    close(m_mfd);

    if (gpioAddress == MAP_FAILED)
    {
        return -2;
    }

    return 0;
}

Obviously there's no need to export GPIOs, they are already available to us. Let's set direction — to do it, we need to write "1" (output) or "0" (input) to the corresponding bit of the register located exactly at the base address:

void gpioDirection(int gpio, int direction)
{
    unsigned long value = *(gpioAddress + 0); // obtain current settings
    if (direction == 1)
    {
        value |= (1 < gpio); // set bit to 1
    }
    else
    {
        value &= ~(1 < gpio); // clear bit
    }
    *(gpioAddress + 0) = value;
}

Call gpioDirection(27, 1) to set GPIO27 as output. There are 2 ways to set GPIO value: writing "0" or "1" to the register at *(gpioAddress + 2) sets GPIO to the corresponding value, writing "1" to *(gpioAddress + 3) sets it to "1", and writing "1" to *(gpioAddress + 4) sets it to "0". Writing "0" to the two latter registers doesn't change anything. First way:

void gpioDirection(int gpio, int direction)
{
    unsigned long value = *(gpioAddress + 2); // obtain current settings
    if (direction == 1)
    {
        value |= (1 < gpio); // set bit to 1
    }
    else
    {
        value &= ~(1 < gpio); // clear bit
    }
    *(gpioAddress + 2) = value;
}

Second way (no need to obtain current value as writing "0" doesn't actually change anything):

void gpioSet(int gpio, int value)
{
    if (value == 0)
    {
        *(gpioAddress + 4) = (1 < gpio);
    }
    else
    {
        *(gpioAddress + 3) = (1 < gpio);
    }
}

When using GPIO as input to read it we need to check another register — at *(gpioAddress + 1) address:

int gpioRead(int gpio)
{
    unsigned long value = *(gpioAddress + 1);
    return (value & (1 < gpio));
}

Функция возвращает значение 0 или 1.

Speed comparison

To compare the speed of GPIO access methods, let's write a simple program which switches some GPIO out without additional delays — and lets us choose the method to switch it:

while(!do_exit)
{
    if (fast)
    {
        gpioSet(GPIO_OUT, 1);
        gpioSet(GPIO_OUT, 0);
    }
    else
    {
        _gpioSet(GPIO_OUT, 1);
        _gpioSet(GPIO_OUT, 0);
    }
}

where gpioSet is a fast method (throug direct registry access) and _gpioSet is a traditional slow method through /sys/class/gpio. Function's internal code exactly as described above.

Now let's attach frequency meter to the GPIO and check what we get here:

  • Access via sysfs — 4.8 kHz switching speed
  • Direct access — 7.5 MHz switching speed

That is to say, we've got more than 1500-times performance boost with direct registry access compared with sysfs access. There are some ways to optimize sysfs performance a bit — e.g., usually it's OK to open GPIO file once globally, not in every single call of the _gpioSet function. But even with such optimizations, there will be more than 500-times difference in performance.