elixir-circuits / circuits_gpio

Use GPIOs from Elixir
129 stars 23 forks source link

[question] Access denied unless root opens a pin first #45

Closed mroach closed 5 years ago

mroach commented 5 years ago

I setup a brand new project on my Raspberry Pi 3 B. I fired-up iex -S mix and tried:

iex> Circuits.GPIO.open(26, :input)
{:error, :access_denied}

Strange. So I tried the same thing in Python3 using the standard RPi.GPIO and it worked fine. I tried in Elixir again, same problem. Then I ran sudo iex -S mix to see if this were a device permission error, and then it worked fine!

So I switched back to running iex as pi, and opening the pin worked! Then I tried opening a different pin, and got {:error, :access_denied} again. Strange. I found that if I first open a pin as root, then I am able to use it as pi. I also found after every time I opened a pin, a new entry was added to /sys/class/gpio

pi@tinkerpi:~/src/tinker $ ls -lh /sys/class/gpio/
total 0
-rwxrwx--- 1 root gpio 4.0K Mar  6 17:50 export
lrwxrwxrwx 1 root gpio    0 Mar  6 18:55 gpio12 -> ../../devices/platform/soc/3f200000.gpio/gpiochip0/gpio/gpio12
lrwxrwxrwx 1 root gpio    0 Mar  6 19:32 gpio16 -> ../../devices/platform/soc/3f200000.gpio/gpiochip0/gpio/gpio16
lrwxrwxrwx 1 root gpio    0 Mar  6 19:34 gpio17 -> ../../devices/platform/soc/3f200000.gpio/gpiochip0/gpio/gpio17
lrwxrwxrwx 1 root gpio    0 Mar  6 19:25 gpio22 -> ../../devices/platform/soc/3f200000.gpio/gpiochip0/gpio/gpio22
lrwxrwxrwx 1 root gpio    0 Mar  6 18:53 gpio26 -> ../../devices/platform/soc/3f200000.gpio/gpiochip0/gpio/gpio26
lrwxrwxrwx 1 root gpio    0 Mar  6 19:32 gpio5 -> ../../devices/platform/soc/3f200000.gpio/gpiochip0/gpio/gpio5
lrwxrwxrwx 1 root gpio    0 Mar  6 19:32 gpio6 -> ../../devices/platform/soc/3f200000.gpio/gpiochip0/gpio/gpio6
lrwxrwxrwx 1 root gpio    0 Mar  6 17:50 gpiochip0 -> ../../devices/platform/soc/3f200000.gpio/gpio/gpiochip0
lrwxrwxrwx 1 root gpio    0 Mar  6 17:50 gpiochip100 -> ../../devices/gpiochip2/gpio/gpiochip100
lrwxrwxrwx 1 root gpio    0 Mar  6 17:50 gpiochip128 -> ../../devices/gpiochip1/gpio/gpiochip128
-rwxrwx--- 1 root gpio 4.0K Mar  6 17:50 unexport

Of course my pi user is a member of gpio so should have access to the devices.

$ id
uid=1000(pi) gid=1000(pi) groups=1000(pi),4(adm),20(dialout),24(cdrom),27(sudo),29(audio),44(video),46(plugdev),60(games),100(users),101(input),108(netdev),997(gpio),998(i2c),999(spi)

So the question is, what's going on here? Is this supposed to work as the pi user? And why does Circuits create entries in /sys/class/gpio when RPi.GPIO doesn't? (Not that I mind at all, just curious).

fhunleth commented 5 years ago

The reason why it creates entries in /sys/class/gpio is that Circuits still uses the old way of accessing GPIOs. Python recently switched over to the new method of using libgpiod instead of sysfs. We plan on doing the same, but haven't done it yet.

I'm not sure why you got accessed denied, but I've been only using Circuits in Nerves recently. Maybe someone else can comment, or if not, I should eventually get a chance to try it out again in Raspbian.

mroach commented 5 years ago

Ah, makes sense with the different behaviour and permission requirements then.

Note to self: try this solution: https://stackoverflow.com/a/50650398/642978 If it works, I'll open a PR to update the README for Pi users to work around the permission issue.

fhunleth commented 5 years ago

@mroach Thanks!

@mattludwigs How's your libgpiod code coming? Maybe @mroach can try it out from a branch to see if it fixes the permissions issue.

mattludwigs commented 5 years ago

Most of my work still in some bare-bones C code used for experimentation. I can start a branch that starts to pull that work into circuits_gpio.

mroach commented 5 years ago

I ran the permission modification via:

sudo /bin/sh -c 'find -L /sys/class/gpio/ -maxdepth 2 -exec chown root:gpio {} \; -exec chmod 770 {} \; || true'

Now the problem is different. The first time I run GPIO.open/2 I get {:error, :access_denied} again, but the sysfs link is created. So then if I run open a second time with the same arguments, it works! So it seems like maybe a race condition or bad feedback from sysfs?

mroach commented 5 years ago

@mattludwigs If you're looking for someone to guinea pig your branch, count me in! C is not my forte but I'll help if I'm able.

fhunleth commented 5 years ago

Yep, that's a race condition. Circuits.GPIO needs to wait for udev to fix permissions before trying to open the GPIO file.

Could you try changing this line:

https://github.com/elixir-circuits/circuits_gpio/blob/master/src/hal_sysfs.c#L177

to

       pin->fd = retry_open(value_path, O_RDWR, 1000);
mroach commented 5 years ago

That works great for opening the pins!

I also figured out that the nif wasn't being compiled with TARGET_RPI which explains why I couldn't set the the pull_mode. My hacky workaround to trigger a recompile with detecting bcm_host.h:

rm -rf _build/dev/lib/circuits_gpio
CPATH=/opt/vc/include iex -S mix
mattludwigs commented 5 years ago

Awesome! I will work on getting some of the code ready and into a branch.

Right now it's just the linux/gpio C calls, so I will need to translate the code into the nif. I will probably get things in iteratively, so we can try to exercise the code as we develop it as we will want to make sure we are testing it well.

I can ping you via mention once I a branch going.

refs: #16

mroach commented 5 years ago

@fhunleth Is the race condition unique to the Raspberry Pi? If so I'm happy to open a PR that uses the TARGET_RPI directive to use retry_open

fhunleth commented 5 years ago

I think it's fine to always call retry_open. I'd expect other platforms that use udev to fix the permissions to hit it as well.

fhunleth commented 5 years ago

Fixed in 93ca09c9ae07ecb6ae9208e317847f57dce5bfdb and the Raspbian build issue referred to above is fixed in ddea948fe00843a6c4232976cf31100da4d0d4b1.

Porting to the new "libgpiod" way of accessing GPIOs is in #16.