using GPIO from within a docker container

Hi, all!
I would like to access the GPIO from within a docker container, but keep getting permission errors.
I installed the jetson nano as described in https://developer.nvidia.com/embedded/learn/get-started-jetson-nano-devkit.
For accessing the GPIO, I used the description here: https://github.com/NVIDIA/jetson-gpio
From within python in the bare system, I then can use the GPIO.
However, when accessing it from within a docker container, the respective user needs to be in the gpio group to access /sys/class/gpio/export and unexport. With a container user having sudo rights, I see that when calling python with sudo and then importing Jetson.GPIO, I can use it and have permission. To grant that permission to the container user, I add a user in the respective group in the Dockerfile:

Create a non-root user with the same uid as on the host to allow proper file

permissions created from the container in volumes. Since it is not root, allow

calling sudo without password when required.

ARG uid
ARG gid
ARG gid_gpio
RUN groupadd -f -r -g $gid_gpio gpio
RUN groupadd -f -r -g 1000 user

gpio needs to be main group, otherwise we get permission problems

RUN useradd -M --uid $uid -g $gid_gpio user --groups uucp,$gid,user
&& echo ‘user ALL=(ALL) NOPASSWD:ALL’ >> /etc/sudoers.d/user
&& echo ‘Defaults exempt_group+=user’ >> /etc/sudoers.d/user
&& chmod a=r,o= /etc/sudoers.d/user

With this, in the container’s shell, I can stat the /sys/class/gpio/export and /unexport. However, from python, I still can’t import Jetson.GPIO without permission errors: os.access() says I didn’t have access to the paths (as used in https://github.com/NVIDIA/jetson-gpio/blob/master/lib/python/Jetson/GPIO/gpio.py#L31 [the issue remains when calling os.access with effective_ids=True])
For the sake of testing, I uncommented the lines checking access, but then ran into trouble in https://github.com/NVIDIA/jetson-gpio/blob/master/lib/python/Jetson/GPIO/gpio_pin_data.py#L241. Here, paths ‘/proc/device-tree/compatible’ and ‘/proc/device-tree/chosen/plugin-manager/ids’ are needed. If I try to import them as volumes

Interestingly, I haven’t found any examples of how to use the GPIO from a docker container - If anyone can give me hints on how to properly do it, I’d greatly appreciate it.

hello h.grabmayr,

you may have an alternative way to enable GPIOs manually,
please check the gpio definitions, $l4t/public_src/kernel_src/kernel-4.4/include/dt-bindings/gpio/tegra186-gpio.h
you should enable the pin as following,
for example,

the location of the GPIOs,
$ cd /sys/class/gpio

to generate gpio220 name, and enable the gpio
$ echo 220 > export
$ cd gpio220
$ echo out > direction && echo 1 > value

you may also check discussion threads to get GPIO interrupts working from user space, Topic 1029697.
thanks

Hi JerryChang,

Is the gpio number the same as “B01 SODIMM” in “NV_Jetson_Nano_Module_Pinmux”?
Like in your example, does gpio220 mean Jetson Nano Signal Name is I2S1_DOUT, B01 SODIMM is 220, Ball Name is DMIC2_CLK and GPIO is GPIO3_PE.02?

Thanks.

hello arknights,

such 220 is just an example to demonstrate sysnode generation for operations.

you should refer to Jetson TX2 Platform Adaptation and Bring-Up Guide, and check the [GPIO Changes] session for GPIO number calculations.
or, you should check Jetson Nano J41 Header Pinout which also indicate the GPIO numbers.
thanks

Hello JerryChang,

thanks for the suggestion. In my account directly on the jetson system, I can do as you suggested:

/sys/class/gpio$ id
uid=1000(exp-deoxy) gid=1000(exp-deoxy) groups=1000(exp-deoxy),4(adm),20(dialout),24(cdrom),27(sudo),29(audio),30(dip),44(video),46(plugdev),118(lpadmin),124(gdm),127(docker),130(sambashare),998(gpio),999(weston-launch)
/sys/class/gpio$ cd /sys/class/gpio
/sys/class/gpio$ echo 220 > export
/sys/class/gpio$ cd gpio220
/sys/class/gpio/gpio220$ echo out > direction && echo 1 > value
/sys/class/gpio/gpio220$ cd ..
/sys/class/gpio$ echo 220 > unexport

However, from within the docker container, I get the response ‘Read-only file system’

user@32ae720b273e:/sys/class/gpio$ id
uid=1000(user) gid=998(gpio) groups=998(gpio),10(uucp),20(dialout),1000(user)
user@32ae720b273e:/sys/class/gpio$ ls -al
total 0
drwxr-xr-x  2 root root    0 Jan 13 10:17 .
drwxr-xr-x 90 root root    0 Jan  9 19:20 ..
--w--w----  1 root gpio 4096 Jan 13 10:17 export
lrwxrwxrwx  1 root root    0 Jan  9 19:20 gpiochip0 -> ../../devices/6000d000.gpio/gpio/gpiochip0
lrwxrwxrwx  1 root root    0 Jan  9 19:20 gpiochip504 -> ../../devices/7000d000.i2c/i2c-4/4-003c/max77620-gpio/gpio/gpiochip504
--w--w----  1 root gpio 4096 Jan 13 10:17 unexport
user@32ae720b273e:/sys/class/gpio$ echo 220 > export
bash: export: Read-only file system

hello h.grabmayr,

it should be a permission issue you should resolve to access GPIO pins,
thanks

Indeed, it was. I thought I had given all permission, but I hadn’t.
After adding ‘privileged: true’ in the corresponding service in docker-compose.yaml, it worked flawlessly.

You should be able to it without privileged by using --device or -v. If you specify the user to be in the gpio group at runtime, it should work. Should be able to do --group-add $(cut -d: -f3 < <(getent group gpio))

–group-add gpio might also work, but I am unsure if it maps the name. Worth trying since it’s less typing.

References:
https://docs.docker.com/engine/reference/run/
https://askubuntu.com/questions/639990/what-is-the-group-id-of-this-group-name

Interesting, that is what I did, but without privileged, permission issues persisted

in Dockerfile:

ARG uid
ARG gid
ARG gid_gpio
RUN groupadd -f -r -g $gid_gpio gpio
RUN groupadd -f -r -g 1000 user
# gpio needs to be main group, otherwise we get permission problems
RUN useradd -M --uid $uid -g $gid_gpio user --groups uucp,$gid,user \
   && echo 'user ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/user \
   && echo 'Defaults exempt_group+=user' >> /etc/sudoers.d/user \
   && chmod a=r,o= /etc/sudoers.d/user
#RUN useradd -M --uid $uid --user-group user --groups uucp,$gid,$gid_gpio \
#  && echo 'user ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/user \
#  && echo 'Defaults exempt_group+=user' >> /etc/sudoers.d/user \
#  && chmod a=r,o= /etc/sudoers.d/user
USER user

in docker-compose.yaml

build:
            args:
                uid: ${UID}
                gid: ${GID}
                gid_gpio: ${GID_GPIO}
 ....
        volumes:
            - ../:/usr/src
            # udev rules for GPIO
            - /dev:/dev
            - /etc/udev/rules.d:/etc/udev/rules.d
        devices:
            # - ${DEVICE}:/dev/ttyDAQ
            - /dev:/dev  # for udev rules (GPIO)

in sourcing shell file

: "${GID:=$(cut -d: -f3 < <(getent group dialout))}"  # dialout
export GID
: "${GID_GPIO:=$(cut -d: -f3 < <(getent group gpio))}"  # dialout
export GID_GPIO

This was not enough to get Jetson.GPIO to work, or the method detailed above. I did have to use privileged

Based on your Dockerfile it seems you added the UID and GID at the build stage, not at the run stage. That should still work, but Dockerfile has a USER keyword you can use to specify/create a placeholder user and group. You don’t have to adduser/addgroup unless you need to chown some files in the image to the user/group.

https://docs.docker.com/engine/reference/run/#user

You can probably remove all of this:

ARG uid
ARG gid
ARG gid_gpio
RUN groupadd -f -r -g $gid_gpio gpio
RUN groupadd -f -r -g 1000 user
# gpio needs to be main group, otherwise we get permission problems
RUN useradd -M --uid $uid -g $gid_gpio user --groups uucp,$gid,user \
   && echo 'user ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/user \
   && echo 'Defaults exempt_group+=user' >> /etc/sudoers.d/user \
   && chmod a=r,o= /etc/sudoers.d/user
#RUN useradd -M --uid $uid --user-group user --groups uucp,$gid,$gid_gpio \
#  && echo 'user ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers.d/user \
#  && echo 'Defaults exempt_group+=user' >> /etc/sudoers.d/user \
#  && chmod a=r,o= /etc/sudoers.d/user

Just build your image as root, if that’s necessary, and drop all caps with USER just before your entrypoint.

It may be your permissions error is because you aren’t mapping the parts of /sys that are needed, and --privileged gives access to everything, so it’ll work for sure (but perhaps also in ways you don’t want). You should only need to specify --group-add and mount the appropriate parts of /sys and /dev . Try this and see if it works (based on my reading of the udev rules):

docker run --rm -v /sys/class/gpio:/sys/class/gpio -v /sys/class/pwm:/sys/class/pwm --device /dev/spidev0.0:/dev/spidev0.0:rw --group-add $(cut -d: -f3 < <(getent group gpio)) yourimagenamehere

Note that I’ve never tried specifically to get gpio to work in Docker on the nano, but I have used this technique to access other devices on other platforms without using --privileged.

edit: you may have to mount more of /sys now that i’m looking at an actual running machine. You might try -v /sys:/sys to start with and narrowing it down.

Hi, I tried this now (with the standard nvidia image)

with adaptations and the generalizations you mentioned, this resulted in

docker run --rm -v /sys:/sys -v /proc:/proc --device /dev/gpiochip0:/dev/gpiochip0 --device /dev/gpiochip1:/dev/gpiochip1 --group-add $(cut -d: -f3 < <(getent group gpio)) -it nvcr.io/nvidia/l4t-base:r32.2

with which I still get the error:

$ apt-get update
$ apt-get install -y python-pip
$ pip install Jetson.GPIO
root@7122c4cf0466:/# python
Python 2.7.17 (default, Nov  7 2019, 10:07:09)
[GCC 7.4.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import Jetson.GPIO
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/Jetson/GPIO/__init__.py", line 1, in <module>
    from .gpio import *
  File "/usr/local/lib/python2.7/dist-packages/Jetson/GPIO/gpio.py", line 70, in <module>
    model, JETSON_INFO, _channel_data_by_mode = gpio_pin_data.get_data()
  File "/usr/local/lib/python2.7/dist-packages/Jetson/GPIO/gpio_pin_data.py", line 241, in get_data
    with open(compatible_path, 'r') as f:
IOError: [Errno 2] No such file or directory: '/proc/device-tree/compatible'
>>>

and indeed, /proc/device-tree/compatible doesn’t exist in the container.

So, /proc/device-tree is a symlink to /sys/firmware/devicetree/base, but you mounted /sys in it’s entirity so it should be there, but…

Docker refuses to mount it, no matter what I try ¯_(ツ)_/¯

After Googling I found multiple mostly unresolved issues, all GPIO related on other platforms:

https://forums.balena.io/t/unprivileged-container-needs-access-to-sys-firmware-devicetree/3131
https://github.com/adafruit/adafruit-beaglebone-io-python/issues/225
https://github.com/angelnu/docker-ccu/issues/4

In this case it might be easier to modify Jetson.GPIO so as not to require this. You could patch get_data() in …/Jetson/GPIO/gpio_pin_data.py to spit out what it does outside the container (or something more elegant, like finding an alternative way to determine the board).

If you clone Nvidia’s repo and make your modifications, you can pip install directly from a git url.

If you need to serialize ChannelInfo (to pass it into the container via the json module or something like that), you can replace it with this:

class ChannelInfo(object):
    def __init__(self, channel, gpio_chip_dir, chip_gpio, gpio, pwm_chip_dir, pwm_id):
        self.channel = channel
        self.gpio_chip_dir = gpio_chip_dir
        self.chip_gpio = chip_gpio
        self.gpio = gpio
        self.pwm_chip_dir = pwm_chip_dir
        self.pwm_id = pwm_id

    def __repr__(self):
        return "ChannelInfo(%s, %s, %s, %s, %s, %s,)" % (
            self.channel,
            self.gpio_chip_dir,
            self.chip_gpio,
            self.gpio,
            self.pwm_chip_dir,
            self.pwm_id,)

    def __eq__(self, other):
        return repr(self) == repr(other)

It doesn’t need the eq method, but it doesn’t hurt. I used it for testing.
You could probably use NamedTuple also but I’m unsure of whether ChannelInfo needs to be mutable or not.

Thanks for the hint, @mdegans

I’ve now forked Jetson-GPIO to https://github.com/Heerpa/jetson-gpio and hard-coded the use of Jetson Nano. I didn’t come up with a more elegant solution. I now need to add
volumes:
# udev rules for GPIO
- /etc/udev/rules.d:/etc/udev/rules.d
# system access for GPIO
- /dev:/dev
- /sys/class/gpio:/sys/class/gpio
- /sys/devices:/sys/devices
and devices:
- /dev/gpiochip0:/dev/gpiochip0 # for GPIO
- /dev/gpiochip1:/dev/gpiochip1 # for GPIO

and can run without privileged container rights.

Thanks for the help!

And I greet everyone.
I have a laptop DELL G5 5590, IPS, Intel Core i7 9750H 2.6 GGZ, 16Gb RAM, 512Гб SSD, nVidia GeForce RTX 2060 - 6144 Мб, Ubuntu 19.10.

And I have a similar problem - the computer does not use my RTX 2060 graphics card, but instead uses the integrated Intel UHD Graphics 630 (Coffeelake 3x8 GT2).
Although I tried to follow these steps in order, the problem did not resolve.
In any case, I always get one result:

$ nvidia-settings

ERROR: NVIDIA driver is not loaded

ERROR: Unable to load info from any available system

(nvidia-settings: 15939): GLib-GObject-CRITICAL **: 11: 09: 05.472: g_object_unref: assertion ‘G_IS_OBJECT (object)’ failed
** Message: 11: 09: 05.474: PRIME: Requires offloading
** Message: 11: 09: 05.474: PRIME: is it supported? yes

Please tell me what I’m missing? If I’m not mistaken, following the advice, but not correctly executing it, perhaps deleted the directory / lib / … fixable?)
I just switched to the operating system and until I figured it out)))

Please, help me))

4 hours ago I turned to the forum with a problem, but so far I have not received an answer. Could any of you help figure out the reason? Here is a link to a parsing in the forum about this problem, but either I’m doing something wrong or the solution does not suit me. I try to understand not the first day in breaks from work))

https://devtalk.nvidia.com/default/topic/1066356/linux/ubuntu-19-10-with-driver-440-31-no-nvidia-graphics-adapter-found-on-rtx2060-mobile/?offset = 9 #

Sorry, that is off topic)

It’s a holiday in USA.

Install the Nvidia driver via the “Software and Updates” app and follow it’s instructions. Please create a new thread next time since this is off topic.

Also 19.10 is not supported for SDK Manager, which is mostly required for Tegra development. Only LTS (18.04 currently).