GPIO on Raspberry Pi 3 not working for blink program

I have a blink program. Compiling this source into a binary and the program blinks the LED successfully.

However, when packaged as a snap and installation in strict mode, with the gpio plug enabled, the program fails to blink the LED

Commands ran:

snap install blink_X_armhf.snap --dangerous

# This connect commands does not work at time of writing. This is just for reference
snap connect blink:gpio pi3:bcm-gpio-17
snap connect blink:gpio pi3:bcm-gpio-22

sudo blink 22 17

snapcraft.yml

name: blink
version: '1.0'
summary: Reads and writes to GPIO based on specified pins.
description: One read and one write pin are specified via the commandline arguments then read from or written to the pin.
grade: devel
confinement: strict

apps:
    blink:
        command: blink
        plugs: [ gpio ]

parts:
    blink:
        source: .
        plugin: make

Source for blink program


//Modified from from http://elinux.org/RPi_GPIO_Code_Samples#sysfs

#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define IN  0
#define OUT 1

#define LOW  0
#define HIGH 1

static int
GPIOExport(int pin)
{
#define BUFFER_MAX 3
	char buffer[BUFFER_MAX];
	ssize_t bytes_written;
	int fd;

	fd = open("/sys/class/gpio/export", O_WRONLY);
	if (-1 == fd) {
		fprintf(stderr, "Failed to open export for writing!\n");
		return(-1);
	}

	bytes_written = snprintf(buffer, BUFFER_MAX, "%d", pin);
	write(fd, buffer, bytes_written);
	close(fd);
	return(0);
}

static int
GPIOUnexport(int pin)
{
	char buffer[BUFFER_MAX];
	ssize_t bytes_written;
	int fd;

	fd = open("/sys/class/gpio/unexport", O_WRONLY);
	if (-1 == fd) {
		fprintf(stderr, "Failed to open unexport for writing!\n");
		return(-1);
	}

	bytes_written = snprintf(buffer, BUFFER_MAX, "%d", pin);
	write(fd, buffer, bytes_written);
	close(fd);
	return(0);
}

static int
GPIODirection(int pin, int dir)
{
	static const char s_directions_str[]  = "in\0out";

#define DIRECTION_MAX 35
	char path[DIRECTION_MAX];
	int fd;

	snprintf(path, DIRECTION_MAX, "/sys/class/gpio/gpio%d/direction", pin);
	fd = open(path, O_WRONLY);
	if (-1 == fd) {
		fprintf(stderr, "Failed to open gpio direction for writing!\n");
		return(-1);
	}

	if (-1 == write(fd, &s_directions_str[IN == dir ? 0 : 3], IN == dir ? 2 : 3)) {
		fprintf(stderr, "Failed to set direction!\n");
		return(-1);
	}

	close(fd);
	return(0);
}

static int
GPIORead(int pin)
{
#define VALUE_MAX 30
	char path[VALUE_MAX];
	char value_str[3];
	int fd;

	snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin);
	fd = open(path, O_RDONLY);
	if (-1 == fd) {
		fprintf(stderr, "Failed to open gpio value for reading!\n");
		return(-1);
	}

	if (-1 == read(fd, value_str, 3)) {
		fprintf(stderr, "Failed to read value!\n");
		return(-1);
	}

	close(fd);

	return(atoi(value_str));
}

static int
GPIOWrite(int pin, int value)
{
	static const char s_values_str[] = "01";

	char path[VALUE_MAX];
	int fd;

	snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin);
	fd = open(path, O_WRONLY);
	if (-1 == fd) {
		fprintf(stderr, "Failed to open gpio value for writing!\n");
		return(-1);
	}

	if (1 != write(fd, &s_values_str[LOW == value ? 0 : 1], 1)) {
		fprintf(stderr, "Failed to write value!\n");
		return(-1);
	}

	close(fd);
	return(0);
}

int
main(int argc, char *argv[])
{

	if(argc < 3){
		printf("Need 2 arguments, read pin and write pin like sudo ./blink 22 17\n");
		return 1;
	}

	char * readPinStr = argv[1];
	char * writePinStr = argv[2];

	int readPin = strtol(readPinStr, NULL, 10);

	if(readPin == 0){
		printf("Read Pin cannot be derived\n");
		return 1;
	}

	int writePin = strtol(writePinStr, NULL, 10);

	if(writePin == 0){
		printf("Write Pin cannot be derived\n");
		return 1;
	}

	printf("Will read from Pin %d and write to Pin %d\n", readPin, writePin);



	int repeat = 20;

	/*
	 * Enable GPIO pins
	 */
	if (-1 == GPIOExport(writePin) || -1 == GPIOExport(readPin))
		return(1);

	/*
	 * Set GPIO directions
	 */
	if (-1 == GPIODirection(writePin, OUT) || -1 == GPIODirection(readPin, IN))
		return(2);

	do {
		/*
		 * Write GPIO value
		 */
		if (-1 == GPIOWrite(writePin, repeat % 2))
			return(3);

		/*
		 * Read GPIO value
		 */
		printf("I'm reading %d in GPIO %d\n", GPIORead(readPin), readPin);

		usleep(500 * 1000);
	}
	while (repeat--);

	/*
	 * Disable GPIO pins
	 */
	if (-1 == GPIOUnexport(writePin) || -1 == GPIOUnexport(readPin))
		return(4);

	return(0);
}

Output from journalctl

sudo journalctl |grep DENIED |less
May 22 10:28:20 localhost.localdomain kernel: audit: type=1400 audit(1495448900.687:3895): apparmor="DENIED" operation="open" profile="snap.blink.blink" name="/sys/class/gpio/export" pid=1852 comm="blink" requested_mask="w" denied_mask="w" fsuid=0 ouid=0
May 22 10:28:20 localhost.localdomain audit[1852]: AVC apparmor="DENIED" operation="open" profile="snap.blink.blink" name="/sys/class/gpio/export" pid=1852 comm="blink" requested_mask="w" denied_mask="w" fsuid=0 ouid=0

this looks like an actual omission in https://github.com/snapcore/snapd/blob/master/interfaces/builtin/gpio.go
there is a:

var gpioSysfsExport = "/sys/class/gpio/export"

… in the code but nothing makes any use of it anywhere.
@jdstrand, any idea why this is not used ? (is this on purpose or just an oversight)

As an aside, I thought the go compiler will throw an error for any variable that is declared and unused :o

I believe this to be cruft. In the first iteration of this interface the snapd process itself handled the export of the pin. Since then the interface has transitioned to using the systemd backend and the variable appears to have been left behind.

while that might be, we should still allow access to that dir via sysfs like we do to the gpio devices themselves.

Any resolution for this. Not now now, but some time in the not too distant future will be good!

gpioSysfsExport can certainly be removed based on what @joc said. Looking at the code it seems like the gpio should already be exported by SystemdConnectedSlot() in interfaces/builtin/gpio.go. If that isn’t happening, then I think we should fix the bug there rather than adding access to /sys/class/gpio/export since we aren’t able to mediate what is written to /sys/class/gpio/export, only write access.

1 Like

well, the kernel actually checks internally if a device exists and is available. it also checks that you only echo numbers into the export/unexport nodes:

ogra@hummingboard:~$ sudo -s
root@hummingboard:~# echo boo > /sys/class/gpio/export
bash: echo: write error: Invalid argument
root@hummingboard:~# echo meep > /sys/class/gpio/export
bash: echo: write error: Invalid argument
root@hummingboard:~# echo 1 > /sys/class/gpio/export
root@hummingboard:~# echo 104975 > /sys/class/gpio/export
bash: echo: write error: Invalid argument
root@hummingboard:~# 
root@hummingboard:~# echo 1 > /sys/class/gpio/export
bash: echo: write error: Device or resource busy
root@hummingboard:~#

so there should be no mediation necessary, but then this is indeed a matter of how much we trust the kernel here.

It was a design decision at the time that the consumer should only have access to the individual gpio defined by the slot. Hence the interface code handles the export and the plug side security profiles just give access to the individual directory for the gpio in question.

The point is that one snap can access GPIO 2 while another gets GPIO 7. This is why the snaps cannot control the export / unexport process themselves. Exporting is done by snapd.

but on one device GPIO7 might be mapped to 5V+ while on another it is GND … you will just fry your attached HW in that case …

I don’t see how this is relevant to the conversation.

because my app snap might be able to handle different HW (know the required mapping for the devices) and manages that via export/unexport see http://wiringx.org/ your app would just use some generic lib on the top level and the lower layer manages the HW recognition and exports the right gpios …

using such a lib is a very typical use case if you dont write your app for one specific device, seems our current gpio interface setup does not cover that at all currently. you can only write your own app completely from scratch focused on one single setup … (or you need to re-write it for each board you want to run on)

if you actually want to have a generically used snap you will by default add and connect 26 plugs to all available gpio slots instead of having one global gpio interface where you can manage the specific pins via export/unexport… i think we should re-think the whole setup here.

My point is that the GPIO interface as it exists today exposes a specific pin only. We may have a gpio-control interface in the future but that would be a separate interface with separate security impact. Applications designed to use the interface hook mechanism can be wired (e.g. from the gadget snap or by the user) to work correctly on new hardware unknown to the developer of the original snap.

Is there anyway to use SPI in UbuntuCore?
It’s seem no interface for spi (like i2c or serial) I can use in the gadget’s .yaml file.

you need to edit config.txt i fear and add something like:

dtparam=spi=on

(i will check what it takes to have that on by default or if it conflicts with anything)

Where is it , I have never see “config.txt” since I use the UbuntuCore. Thanks!

it sits in /boot/uboot/config.txt

it already exists in the configured.txt "
dtparam=i2c_arm=on
dtparam=i2c_vc=on
#i2s=on
dtparam=spi=on
dtparam=act_led_trigger=heartbeat
dtparam=pwr_led_trigger=mmc0
dtparam=audio=on

device_tree_address=0x02000000
core_freq=250
enable_uart=1

dtoverlay=vc4-kms-v3d
"
but i still don’t find the spi option in the “snap interfaces list”, so what should I do??
thanks