gobot icon indicating copy to clipboard operation
gobot copied to clipboard

Add support for GPIO Character devices on Linux (specifically raspberry pi)

Open joeblubaugh opened this issue 4 years ago • 1 comments
trafficstars

The sysfs interface to gpio is deprecated and will be removed from the kernel sometime in 2020.

The kernel now has character device support for GPIO. Here's the output from some gpio commands on my Raspberry Pi 400:

pi@raspberrypi:/dev $ gpioinfo 
gpiochip0 - 54 lines:
        line   0:     "ID_SDA"       unused   input  active-high 
        line   1:     "ID_SCL"       unused   input  active-high 
        line   2:       "SDA1"       unused   input  active-high 
        line   3:       "SCL1"       unused   input  active-high 
        line   4:  "GPIO_GCLK"       unused   input  active-high 
        line   5:      "GPIO5"       unused   input  active-high 
        line   6:      "GPIO6"       unused   input  active-high 
        line   7:  "SPI_CE1_N"       unused   input  active-high 
        line   8:  "SPI_CE0_N"       unused   input  active-high 
        line   9:   "SPI_MISO"       unused   input  active-high 
        line  10:   "SPI_MOSI"       unused   input  active-high 
        line  11:   "SPI_SCLK"       unused   input  active-high 
        line  12:     "GPIO12"       unused   input  active-high 
        line  13:     "GPIO13"       unused   input  active-high 
        line  14:       "TXD1"       unused   input  active-high 
        line  15:       "RXD1"       unused   input  active-high 
        line  16:     "GPIO16"       unused   input  active-high 
        line  17:     "GPIO17"       unused   input  active-high 
        line  18:     "GPIO18"       unused   input  active-high 
        line  19:     "GPIO19"       unused   input  active-high 
        line  20:     "GPIO20"       unused   input  active-high 
        line  21:     "GPIO21"       unused   input  active-high 
        line  22:     "GPIO22"       unused   input  active-high 
        line  23:     "GPIO23"       unused   input  active-high 
        line  24:     "GPIO24"       unused   input  active-high 
        line  25:     "GPIO25"       unused   input  active-high 
        line  26:     "GPIO26"       unused   input  active-high 
        line  27:     "GPIO27"       unused   input  active-high 
        line  28: "RGMII_MDIO"       unused   input  active-high 
        line  29:  "RGMIO_MDC"       unused   input  active-high 
        line  30:       "CTS0"       unused   input  active-high 
        line  31:       "RTS0"       unused   input  active-high 
        line  32:       "TXD0"       unused   input  active-high 
        line  33:       "RXD0"       unused   input  active-high 
        line  34:    "SD1_CLK"       unused   input  active-high 
        line  35:    "SD1_CMD"       unused   input  active-high 
        line  36:  "SD1_DATA0"       unused   input  active-high 
        line  37:  "SD1_DATA1"       unused   input  active-high 
        line  38:  "SD1_DATA2"       unused   input  active-high 
        line  39:  "SD1_DATA3"       unused   input  active-high 
        line  40:  "PWM0_MISO"       unused   input  active-high 
        line  41:  "PWM1_MOSI"       unused   input  active-high 
        line  42: "STATUS_LED_G_CLK" "led0" output active-high [used]
        line  43: "SPIFLASH_CE_N" unused input active-high 
        line  44:       "SDA0"       unused   input  active-high 
        line  45:       "SCL0"       unused   input  active-high 
        line  46: "RGMII_RXCLK" unused input active-high 
        line  47: "RGMII_RXCTL" unused input active-high 
        line  48: "RGMII_RXD0"       unused   input  active-high 
        line  49: "RGMII_RXD1"       unused   input  active-high 
        line  50: "RGMII_RXD2"       unused   input  active-high 
        line  51: "RGMII_RXD3"       unused   input  active-high 
        line  52: "RGMII_TXCLK" unused input active-high 
        line  53: "RGMII_TXCTL" unused input active-high 
gpiochip1 - 8 lines:
        line   0:      "BT_ON"       unused  output  active-high 
        line   1:      "WL_ON"       unused  output  active-high 
        line   2: "PWR_LED_OFF" "led1" output active-low [used]
        line   3: "GLOBAL_RESET" unused output active-high 
        line   4: "VDD_SD_IO_SEL" "vdd-sd-io" output active-high [used]
        line   5:   "CAM_GPIO" "power_ctrl"  output  active-high [used]
        line   6:  "SD_PWR_ON" "sd_vcc_reg"  output  active-high [used]
        line   7:    "SD_OC_N"       unused   input  active-high 

Adding support for the character device would also make it straightforward to support GPIO interrupts on linux

warthog's gpiod module is an example of a project using the character device.

joeblubaugh avatar Dec 27 '20 08:12 joeblubaugh

Hi @joeblubaugh , thanks for this hint. It sounds like an important improvement.

The mentioned project looks like a good library which we could use. It seems it has some support for Pi mapping, which means it is expandable also for other devices, hopefully.

Before start implementing the interface we should consider some other libraries.

gen2thomas avatar Sep 20 '22 08:09 gen2thomas

Found this project as an further example: https://github.com/sgjava/javauio

And some more information as a second start point besides the already mentioned embeddedbits.org by adafruit: https://blog.adafruit.com/2018/11/26/sysfs-is-dead-long-live-libgpiod-libgpiod-for-linux-circuitpython/

gen2thomas avatar Nov 12 '22 13:11 gen2thomas

After some investigation we have at least this options to implement:

  1. cgo with direct kernel function calls
  2. cgo with ioctl
  3. golang with implementing read/write to character devices
  4. mixed cgo and golang with ioctl
  5. golang with CLI calls of "gpiod" package
  6. golang with full ioctl implementation
  7. golang with external go-module usage

I have started implementing the option 6, to be most aligned with existing I2C implementation. But the ioctl calls for GPIO's are much more complex. This means:

  • "dynamic" calculation of the ioctl signal, depending on the payload size and some defines and macros at kernel level
  • there is a deprecated V1 and a V2 version of ioctl
  • there are many sub structures for using the various line parameters

The circumvention of the "dynamic ioctl signal calculation" would be possible by using some cgo code with included Kernel headers, but possibly leads to problems with cross compiling. Handling of two different versions and multiple structures is needed also for all the other options.

Using the CLI call in option 5 would add the dependency to "sudo apt install gpiod" and leads to implementing some parsing functionality, which most likely is not very stable and hard to maintain.

As far as I can tell, all those is still implemented in the provided warthog's gpiod module. Therefor we should not reinventing the wheel with our own implementation and just use it for option 7.

@deadprogram this is my conclusion, what do you think about?

gen2thomas avatar Nov 19 '22 07:11 gen2thomas

@gen2thomas I think option 7 sounds like an excellent plan.

deadprogram avatar Nov 19 '22 09:11 deadprogram

@deadprogram wonderful, then I have not wasted time in the last 3 hours :smile: . I will continue in this direction.

gen2thomas avatar Nov 19 '22 10:11 gen2thomas

Hi @deadprogram I have finished the implementation and have done small interface changes:

Interface changes:

  • setter function "Direction(string) error" removed from "gobot.DigitalPinner", now done by "WithDirection..." option
  • getter function "DigitalPin(id string, dir string)" in "gobot.DigitalPinnerProvider" do not change the direction anymore, it provides the pin as is and if not exist it will be created as input, changes can be applied afterwards with "ApplyOptions()", to prevent accidentally direction changes, which could cause board damages. So it is now just "DigitalPin(id string)".

Therefore my suggestion is to increase the main-version on next merge to release branch, according to semver. Also the position of interfaces was changed in the previous PR #885 . Following the golang style - it should be defined at client side and not besides the implementation/provider. Unfortunately, because we check the fulfillment of the interfaces in the unit tests (which IMO is a good idea), the position needs to be at top level to prevent import cycles.

If there is something wrong with that in your opinion, of course I can revert/change this.

gen2thomas avatar Nov 27 '22 15:11 gen2thomas

Some benchmarks from Tinkerboard testing with a loop for On/Off one pin:

TinkerOS-sysfs (Kernel 4.4.x):

  • 40Khz @ 600Mhz
  • 70Khz @ 1200Mhz
  • 80Khz @ 1600Mhz

armbian-sysfs (Kernel 5.15.74):

  • 50Khz @ 600Mhz
  • 95Khz @ 1200Mhz
  • 125Khz @ 1600Mhz

armbian-cdev (same system as above)

  • 75Khz @ 600Mhz
  • 125Khz @ 1200Mhz
  • 160Khz @ 1600Mhz

Notes: The sysfs on cdev-Kernels is just a wrapper to cdev, so the result is more or less expected. Although this is an improvement, please have in mind, that there are unpredicted interruptions on output toggling for ~1ms (I have seen for Tinkerboard), due to this are no RT kernels. This happens independent of used gpio implementation and Kernel version.

gen2thomas avatar Nov 27 '22 16:11 gen2thomas

part of release v2.0.0

gen2thomas avatar May 15 '23 16:05 gen2thomas