Is GPIO V2 ABI Supported Through /dev/gpiochipX?

The duo-sdk provides duo-sdk/rootfs/usr/include/linux/gpio.h which implements the user-space GPIO_V2 ABI (see GPIO Character Device Userspace API. However when retrieving the chip_info from any of the gpioghipX devices (e.g. /dev/gpiochip0) the GPIO_GET_CHIPINFO_IOCTL and GPIO_V2_GET_LINEINFO_IOCTL, the proper number of GPIOs are returned (returns 32, even though there are 28 accessible), but does not return the .name or .consumer test information about each GPIO.

There are also warnings from the duo-sdk rootfs/usr/include/bits/ioctl.h regarding overflow in conversion – which should not occur, e.g.

<snip> redefinition warnings

In file included from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/sys/ioctl.h:10,
                 from gpio_v2-chipinfo.c:4:
gpio_v2-chipinfo.c: In function 'prn_gpio_v2_ghip_info':
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/bits/ioctl.h:1:23: warning: overflow in conversion from 'long unsigned int' to 'int' changes value from '2151986177' to '-2142981119' [-Woverflow]
    1 | #define _IOC(a,b,c,d) ( ((a)<<30) | ((b)<<8) | (c) | ((d)<<16) )
      |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/bits/ioctl.h:8:21: note: in expansion of macro '_IOC'
    8 | #define _IOR(a,b,c) _IOC(_IOC_READ,(a),(b),sizeof(c))
      |                     ^~~~
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/gpio.h:496:33: note: in expansion of macro '_IOR'
  496 | #define GPIO_GET_CHIPINFO_IOCTL _IOR(0xB4, 0x01, struct gpiochip_info)
      |                                 ^~~~
gpio_v2-chipinfo.c:87:22: note: in expansion of macro 'GPIO_GET_CHIPINFO_IOCTL'
   87 |   if (ioctl (gpiofd, GPIO_GET_CHIPINFO_IOCTL, &chip_info) == -1) {
      |                      ^~~~~~~~~~~~~~~~~~~~~~~
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/bits/ioctl.h:1:23: warning: overflow in conversion from 'long unsigned int' to 'int' changes value from '3238048773' to '-1056918523' [-Woverflow]
    1 | #define _IOC(a,b,c,d) ( ((a)<<30) | ((b)<<8) | (c) | ((d)<<16) )
      |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/bits/ioctl.h:9:22: note: in expansion of macro '_IOC'
    9 | #define _IOWR(a,b,c) _IOC(_IOC_READ|_IOC_WRITE,(a),(b),sizeof(c))
      |                      ^~~~
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/gpio.h:502:36: note: in expansion of macro '_IOWR'
  502 | #define GPIO_V2_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x05, struct gpio_v2_line_info)
      |                                    ^~~~~
gpio_v2-chipinfo.c:114:26: note: in expansion of macro 'GPIO_V2_GET_LINEINFO_IOCTL'
  114 |       if (ioctl (gpiofd, GPIO_V2_GET_LINEINFO_IOCTL, &line_info) == -1) {
      |                          ^~~~~~~~~~~~~~~~~~~~~~~~~~
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/bits/ioctl.h:1:23: warning: overflow in conversion from 'long unsigned int' to 'int' changes value from '3238048773' to '-1056918523' [-Woverflow]
    1 | #define _IOC(a,b,c,d) ( ((a)<<30) | ((b)<<8) | (c) | ((d)<<16) )
      |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/bits/ioctl.h:9:22: note: in expansion of macro '_IOC'
    9 | #define _IOWR(a,b,c) _IOC(_IOC_READ|_IOC_WRITE,(a),(b),sizeof(c))
      |                      ^~~~
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/gpio.h:502:36: note: in expansion of macro '_IOWR'
  502 | #define GPIO_V2_GET_LINEINFO_IOCTL _IOWR(0xB4, 0x05, struct gpio_v2_line_info)
      |                                    ^~~~~
gpio_v2-chipinfo.c:131:24: note: in expansion of macro 'GPIO_V2_GET_LINEINFO_IOCTL'
  131 |     if (ioctl (gpiofd, GPIO_V2_GET_LINEINFO_IOCTL, &line_info) == -1) {
      |                        ^~~~~~~~~~~~~~~~~~~~~~~~~~
/home/david/dev/milkv/duo-examples/duo-sdk/riscv64-linux-musl-x86_64/bin/riscv64-unknown-linux-musl-gcc -o gpio_v2_chipinfo gpio_v2-chipinfo.o -mcpu=c906fdv -march=rv64imafdcv0p7xthead -mcmodel=medany -mabi=lp64d -L/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/lib -L/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/lib

(the compile and link completes, but the executable is suspect at this point – which may explain the missing gpio_v2_line_info information)

Either the GPIO V2 ABI isn’t properly implemented on Duo or there is a bug in the kernel ioctl includes that appears to be causing problems.

Does anyone know the answer or can you point to documentation where I can find out more about what the Milkv intends here? I’m using the default image from December 2023.

Here is a short test-source for checking the gpio chipinfo that I used generating the compiler output above:

#include <stdio.h>
#include <stdlib.h>
#include <linux/gpio.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

/* chip info output format constant(s) */
#define INFOCOLS          3u

/* gpiochipX (0-4) depending on board */
#ifndef MILKVFS
#define GPIOCHIP         "/dev/gpiochip0"
#define NGPIOCHIPS         1
#else
#define GPIOCHIP         "/dev/gpiochip1"
#define NGPIOCHIPS         5
#endif

/* define max number of GPIO lines (32 reported) */
#define GPIOMAX           28

/* default write and read GPIO pins */
#define GPIO_WR_PIN       16
#define GPIO_RD_PIN       17

/* macro to select new /dev/gpiochipX */
#define GPIODEV           "/dev/gpiochip"
#define GPIODEVFS(_c)     (GPIODEV # _c)


/**
 * @brief open gpiochipX device for control of gpio pins via ioctl().
 * @param gpiodev path to gpiochipX in filesystem, e.g. "/dev/gpiochip0".
 * @return returns file descriptor for device on success, -1 otherwise.
 */
int gpio_dev_open (const char *gpiodev)
{
  int gpiofd;

  /* open gpiochipX */
  if ((gpiofd = open (gpiodev, O_RDONLY)) < 0) {
    perror ("open-GPIOCHIP");
  }

  return gpiofd;
}


/**
 * @brief closes gpio file descriptor associated with "/dev/gpiochipX"
 * @param gpiofd file descriptor retured by previos call to gpio_dev_open().
 * @return returns 0 on success, -1 otherwise.
 */
int gpio_dev_close (int gpiofd)
{
  if (close (gpiofd) < 0) {         /* close gpiochipX fd */
    perror ("close-gpiofd");
    return -1;
  }

  return 0;
}


/**
 * @brief print the GPIO number and function for each GPIO line (pin) in
 * a 3-column format.
 * @param gpiofd open file descriptor for gpiochipX returned from prior call
 * to gpio_dev_open().
 * @return returns 0 on success, -1 otherwise.
 */
int prn_gpio_v2_ghip_info (int gpiofd)
{
  __u8  rows = 0,
        remstart = 0;
  struct gpiochip_info chip_info = { .name = "" };

  /* gpio_v2 ioctl call to get chip information */
  if (ioctl (gpiofd, GPIO_GET_CHIPINFO_IOCTL, &chip_info) == -1) {
    perror ("ioctl-GPIO_GET_CHIPINFO_IOCTL");
    return -1;
  }

  /* validate lines returned (integer division intentional) */
  if ((rows = chip_info.lines / INFOCOLS) == 0) {
    fputs ("error: GPIO_GET_CHIPINFO_IOCTL no GPIO lines.\n", stderr);
    return -1;
  }
  remstart = rows * INFOCOLS;   /* compute no. of rows to print at end */

  /* output chip information */
  printf ("\nGPIO chip information\n\n"
          "  name  : %s\n"
          "  label : %s\n"
          "  lines : %u\n\n",
          chip_info.name, chip_info.label, chip_info.lines);

  /* loop producing output of gpios in 3-column format */
  for (__u8 r = 0; r < rows; r++) {
    for (__u32 c = 0; c < INFOCOLS; c++) {
      __u32 chip = r * INFOCOLS + c;
      struct gpio_v2_line_info line_info = { .name = "",
                                             .consumer = "",
                                             .offset = chip };

      if (ioctl (gpiofd, GPIO_V2_GET_LINEINFO_IOCTL, &line_info) == -1) {
        perror ("ioctl-GPIO_GET_LINEINFO_IOCTL");
        fprintf (stderr, "Failed getting line %u info.\n", chip);
        continue;
      }

      printf ("  %2hhu :  %-18s", chip, line_info.name);
    }
    putchar ('\n');
  }

  /* output any remaining lines (e.g. r * c <= line < chip_info.lines) */
  for (__u32 c = remstart; c < chip_info.lines; c++) {
    struct gpio_v2_line_info line_info = { .name = "",
                                           .consumer = "",
                                           .offset = c };

    if (ioctl (gpiofd, GPIO_V2_GET_LINEINFO_IOCTL, &line_info) == -1) {
      perror ("ioctl-GPIO_GET_LINEINFO_IOCTL");
      fprintf (stderr, "Failed getting line %u info.\n", c);
      continue;
    }

    printf ("  %2hhu :  %-18s", c, line_info.name);
  }
  puts ("\n");    /* tidy up with an additional (2) newlines */

  return 0;
}


int main (int argc, char * const *argv) {

  int gpiofd;                       /* file descriptor */
  const char *gpiodev = GPIOCHIP;   /* devfs gpiochipX to open (default) */
  
  /* check command line arguments for new X in gpiochipX to open */
  if (NGPIOCHIPS > 1 && argc > 1) {
    __u8 c;
    if (sscanf (argv[1], "%hhu", &c) == 1) {
      if (c >= NGPIOCHIPS) {
        fprintf (stderr, "error: chip exceeds valid range '%hhu >= %d'.\n",
                  c, NGPIOCHIPS);
        return 1;
      }
      switch (c) {
        case 0: gpiodev = GPIODEVFS(0); break;
        case 1: gpiodev = GPIODEVFS(1); break;
        case 2: gpiodev = GPIODEVFS(2); break;
        case 3: gpiodev = GPIODEVFS(3); break;
        case 4: gpiodev = GPIODEVFS(4); break;
      }
    }
  }
  
  /**
   *  gpiochipX - open and get info
   */

  /* open gpiochipX device - validate */
  if ((gpiofd = gpio_dev_open (gpiodev)) == -1) {
    return 1;
  }
  
  /* output gpiochip and each current gpio line (pin) function */
  if (prn_gpio_v2_ghip_info (gpiofd) == -1) {
    return 1;
  }
  
  /* close gpiochipX - note: has no impact on configured line requests */
  if (gpio_dev_close (gpiofd) == -1) {
    return 1;
  }
}

The output produced is the same for /dev/gpiochip0 through /dev/gpiochip4, e.g.

# gpio_v2_chipinfo 4

GPIO chip information

  name  : gpiochip4
  label : 5021000.gpio
  lines : 32

   0 :                       1 :                       2 :
   3 :                       4 :                       5 :
   6 :                       7 :                       8 :
   9 :                      10 :                      11 :
  12 :                      13 :                      14 :
  15 :                      16 :                      17 :
  18 :                      19 :                      20 :
  21 :                      22 :                      23 :
  24 :                      25 :                      26 :
  27 :                      28 :                      29 :
  30 :                      31 :

There are no errors in the executions of the code. The program returns 0 on exit.


Same Warnings With Kernel User-Space SPI Interface

Note: This seems to be a generic problem with the buildroot implementation of kernel user-space utilities. The same issue occurs using /dev/spidev. There seems to be problems in the core buildroot headers and source with type-size mismatches (likely a 32-bit/64-bit issue). Attempting to simply use the SPI kernel interface results in the following warnings regarding the buildroot headers:

$ ../../makeduo.sh
/home/david/dev/milkv/duo-examples/duo-sdk/riscv64-linux-musl-x86_64/bin/riscv64-unknown-linux-musl-gcc -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -I/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include -I./include -Wall -Wextra -pedantic -Wshadow -std=c11 -D_GNU_SOURCE -DMILKVFS -o mcp3008spi.o -c mcp3008spi.c
In file included from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/asm/ioctl.h:1,
                 from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/ioctl.h:5,
                 from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/spi/spidev.h:27,
                 from mcp3008spi.c:14:
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/asm-generic/ioctl.h:69: warning: "_IOC" redefined
   69 | #define _IOC(dir,type,nr,size) \
      |
In file included from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/sys/ioctl.h:10,
                 from mcp3008spi.c:12:
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/bits/ioctl.h:1: note: this is the location of the previous definition
    1 | #define _IOC(a,b,c,d) ( ((a)<<30) | ((b)<<8) | (c) | ((d)<<16) )
      |
In file included from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/asm/ioctl.h:1,
                 from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/ioctl.h:5,
                 from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/spi/spidev.h:27,
                 from mcp3008spi.c:14:
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/asm-generic/ioctl.h:83: warning: "_IO" redefined
   83 | #define _IO(type,nr)  _IOC(_IOC_NONE,(type),(nr),0)
      |
In file included from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/sys/ioctl.h:10,
                 from mcp3008spi.c:12:
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/bits/ioctl.h:6: note: this is the location of the previous definition
    6 | #define _IO(a,b) _IOC(_IOC_NONE,(a),(b),0)
      |
In file included from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/asm/ioctl.h:1,
                 from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/ioctl.h:5,
                 from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/spi/spidev.h:27,
                 from mcp3008spi.c:14:
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/asm-generic/ioctl.h:84: warning: "_IOR" redefined
   84 | #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
      |
In file included from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/sys/ioctl.h:10,
                 from mcp3008spi.c:12:
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/bits/ioctl.h:8: note: this is the location of the previous definition
    8 | #define _IOR(a,b,c) _IOC(_IOC_READ,(a),(b),sizeof(c))
      |
In file included from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/asm/ioctl.h:1,
                 from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/ioctl.h:5,
                 from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/spi/spidev.h:27,
                 from mcp3008spi.c:14:
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/asm-generic/ioctl.h:85: warning: "_IOW" redefined
   85 | #define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
      |
In file included from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/sys/ioctl.h:10,
                 from mcp3008spi.c:12:
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/bits/ioctl.h:7: note: this is the location of the previous definition
    7 | #define _IOW(a,b,c) _IOC(_IOC_WRITE,(a),(b),sizeof(c))
      |
In file included from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/asm/ioctl.h:1,
                 from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/ioctl.h:5,
                 from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/spi/spidev.h:27,
                 from mcp3008spi.c:14:
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/asm-generic/ioctl.h:86: warning: "_IOWR" redefined
   86 | #define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
      |
In file included from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/sys/ioctl.h:10,
                 from mcp3008spi.c:12:
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/bits/ioctl.h:9: note: this is the location of the previous definition
    9 | #define _IOWR(a,b,c) _IOC(_IOC_READ|_IOC_WRITE,(a),(b),sizeof(c))
      |
In file included from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/asm/ioctl.h:1,
                 from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/ioctl.h:5,
                 from /home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/spi/spidev.h:27,
                 from mcp3008spi.c:14:
mcp3008spi.c: In function 'spi_device_init':
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/asm-generic/ioctl.h:70:2: warning: overflow in conversion from 'long unsigned int' to 'int' changes value from '2147773188' to '-2147194108' [-Woverflow]
   70 |  (((dir)  << _IOC_DIRSHIFT) | \
      |  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   71 |   ((type) << _IOC_TYPESHIFT) | \
      |   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   72 |   ((nr)   << _IOC_NRSHIFT) | \
      |   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   73 |   ((size) << _IOC_SIZESHIFT))
      |   ~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/asm-generic/ioctl.h:84:28: note: in expansion of macro '_IOC'
   84 | #define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
      |                            ^~~~
/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/include/linux/spi/spidev.h:142:34: note: in expansion of macro '_IOR'
  142 | #define SPI_IOC_RD_MAX_SPEED_HZ  _IOR(SPI_IOC_MAGIC, 4, __u32)
      |                                  ^~~~
mcp3008spi.c:85:23: note: in expansion of macro 'SPI_IOC_RD_MAX_SPEED_HZ'
   85 |   if (ioctl (dev->fd, SPI_IOC_RD_MAX_SPEED_HZ, &dev->spi_xfer.speed_hz) == -1) {
      |                       ^~~~~~~~~~~~~~~~~~~~~~~
/home/david/dev/milkv/duo-examples/duo-sdk/riscv64-linux-musl-x86_64/bin/riscv64-unknown-linux-musl-gcc -o mcp3008spi mcp3008spi.o -mcpu=c906fdv -march=rv64imafdcv0p7xthead -mcmodel=medany -mabi=lp64d -L/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/usr/lib -L/home/david/dev/milkv/duo-examples/duo-sdk/rootfs/lib -lrt

The program builds, but it’s operation is suspect due to the overflow warnings generated by the system headers and system sources.


Deprecated GPIO V1 uABI - Same Result

Investigating further, I changed the GPIO chip_info code to use the GPIO V1 uABI to use struct gpioline_info to access the line information. The compilation warnings and the resulting output is the same regardless whether you use the VI ABI or V2.