I2C permissions issue with Mallow

I have a rust program that access an I2C based temperature sensor. My docker container is based on the rust:1.81-bookworm image. I have tried a number of permission settings, including the cgroup-rule in my compose file and nothing seems to work when accessing the device from inside the program.

I have also removed all of the device permissions settings from the compose file and simply added the “privileged: true” to the compose file. When starting up the continer with “docker compose -f …”, I can ls the /dev folder and see all of the devices (as expected with privileged=true but any access results in a permissions error. If I run my rust program under sudo, it works.

The default rust image does not have i2c or gpio or other groups defined. I have looked at the docs but can’t see what the problem is. Is there a document that exists that explains how to setup user (rustdev) access to the i2c-3 bus from inside a non-torizon based container?

Hi, I’m the Toradex AI Assistant. The Toradex Support team will follow up with you shortly!

Did you like my answer?

:+1: :-1:


Hello!

To access I2C devices from within a container on Toradex systems using Torizon, you’ll need to ensure the hardware is properly exposed to the container. Here are the key steps to follow:

  1. Running the Container:

    • Mount the /dev directory into the container.

    • Add the device cgroup rule for I2C access. This can be done using the following Docker command:

      docker run -it --rm -v /dev:/dev --device-cgroup-rule='c 89:* rmw' <container-image>
      
  2. Using Docker Compose:

    • Include the same settings in your Docker Compose file to ensure proper access.
  3. Accessing I2C Devices:

    • Torizon OS creates standardized symbolic links for I2C devices to maintain compatibility across different modules and carrier boards. For example, on a Verdin system, you can access I2C through paths like verdin-i2c1, verdin-i2c2, etc.
  4. Testing I2C Access:

    • Before running your Rust application, you can use i2c-tools within a container to verify basic connectivity and ensure everything is set up correctly.

If you encounter permission issues, consider the following troubleshooting steps:

  • Double-check that the device cgroup rule is correctly set.
  • Ensure the I2C bus number matches your hardware setup.
  • Use the standardized symbolic links instead of direct bus numbers for accessing I2C devices.

I hope this helps! If you have any more questions or need further assistance, feel free to ask.

Best regards,
Toradex AI Assistant.

Hey @Glitch,

Device access within a container should not really be affected by what programs the container is also meant to run (rust). There may be some issues with hardware access and your username, but this should be solved via the cgroup rule.

Here is our guide on container access from user space. You may have already seen this.

Can you share your full docker run command? (non root)
And what errors you get when you try and access the i2c device from within the container?

-Eric

Hi @eric.tx, thanks for the quick reply. So I use docker compose to create the container stack for this application and I have two sensors connected to the i2c-3 bus. Looking at the samples, I added the following to my dev-compose.yml file:

  volumes: # named volumes for source and caches
      - /dev:/dev
    device_cgroup_rules:
      - 'c 89:* rmw'
    devices: # needed for I2C access (e.g. Toradex) ... sets to null if not set in .env
      - ${I2C_DEVICE:-/dev/null}

I have a .env file with the following in it:

I2C_DEVICE=/dev/verdin-i2c1

On my setup with the mallow carrier board, verdin-i2c1 is mapped to i2c-3. I then create the container on the development board using the following docker compose command. This creates the container and keeps it running via an entrypoint script with an attached bash shell:

docker --context devboard-lab compose -f docker/dev-compose.yml --env-file ./.env up -d

The entrypoint in the compose yml looks like this:

entrypoint: ["/bin/bash", "-c", 'echo "$$ENTRYPOINT_SCRIPT" > /home/${USERNAME}/entrypoint.sh && chmod +x /home/${USERNAME}/entrypoint.sh && /home/${USERNAME}/entrypoint.sh "tail -f /dev/null"']

Once the contianer is up and running, I use the following docker exec command to connect to it:

docker --context devboard-lab exec -it sensor-dev bash

This sets up a terminal window into the container. I can then run cargo build and run commands from there as the user rustdev. With the moutning of the dev volume and cgroup rules, I would expect that I can then run my program that reads from the /dev/verdin-i2c1 device.

To debug, I can list the contents of the /dev folder and see all of the i2c devices and symbolic links. When running i2cdetect -l as a non-root user (rustdev), I get the following:

i2c-0   unknown         30a20000.i2c                            N/A
i2c-1   unknown         30a30000.i2c                            N/A
i2c-2   unknown         30a40000.i2c                            N/A
i2c-3   unknown         30a50000.i2c                            N/A
i2c-4   unknown         DesignWare HDMI                         N/A

If running the same command with sudo, the output shows the bus instead of “unknown” as follows:

sudo: unable to resolve host verdin-imx8mp-15132666: Name or service not known
i2c-0   i2c             30a20000.i2c                            I2C adapter
i2c-1   i2c             30a30000.i2c                            I2C adapter
i2c-2   i2c             30a40000.i2c                            I2C adapter
i2c-3   i2c             30a50000.i2c                            I2C adapter
i2c-4   i2c             DesignWare HDMI                         I2C adapter

Running i2cdetect -y 3 to show the bus 3 devices, I get a permissions error:

Error: Could not open file `/dev/i2c-3': Permission denied
Run as root?

Running the command again as sudo, it outputs the expected:

sudo: unable to resolve host verdin-imx8mp-15132666: Name or service not known
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- UU 
50: -- -- -- 53 -- -- -- UU -- -- -- -- -- -- -- 5f 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                         

When running my application, I use the following comand line:

cargo run -- --orb-name Saraswati --skip-warmup -i ${I2C_DEVICE}

The program runs and generates the expected output until it gets to the first i2c bus read and write and then gets a PermissionDenied error. The output looks like the following:

2025-03-19T20:14:11.780407Z  INFO main ThreadId(01) hazmat: src/main.rs:204: Starting hazmat sensor monitoring...
2025-03-19T20:14:11.780661Z  INFO main ThreadId(01) hazmat: src/main.rs:205: Log level set log_level=Level(Info)
2025-03-19T20:14:11.780765Z  INFO main ThreadId(01) hazmat: src/main.rs:206: I2C bus configured i2c_bus="/dev/verdin-i2c1"
2025-03-19T20:14:11.780864Z  INFO main ThreadId(01) hazmat: src/main.rs:207: Log directory configured log_path="logs"
2025-03-19T20:14:11.780975Z  INFO main ThreadId(01) hazmat: src/main.rs:208: Temperature unit configured temp_unit=Both
2025-03-19T20:14:11.781089Z  INFO main ThreadId(01) hazmat: src/main.rs:209: Polling interval set interval="1s"
2025-03-19T20:14:11.781211Z  INFO main ThreadId(01) hazmat: src/main.rs:210: EdgeDB DSN db_dsn="edgedb://edgedb:@localhost:5656"
2025-03-19T20:14:11.781335Z  INFO main ThreadId(01) hazmat: src/main.rs:211: Simulation mode: DISABLED simulation=false
2025-03-19T20:14:11.782339Z  INFO main ThreadId(01) hazmat::database: src/database.rs:141: EdgeDB client initialized
2025-03-19T20:14:11.830498Z  INFO main ThreadId(01) hazmat: src/main.rs:224: Using sensor name="HazmatAQI" type_id=3 instance_id=1
2025-03-19T20:14:11.830972Z  INFO main ThreadId(01) hazmat: src/main.rs:253: Using REAL hardware sensors
2025-03-19T20:14:11.831362Z  INFO main ThreadId(01) hazmat: src/main.rs:254: Note: ENS160 sensor requires up to 1 hour after first power-on for full accuracy
Error: Communication(Io(Os { code: 13, kind: PermissionDenied, message: "Permission denied" }))

I can run the program with sudo as follows:

2025-03-19T20:16:19.569974Z  INFO main ThreadId(01) hazmat: src/main.rs:204: Starting hazmat sensor monitoring...
2025-03-19T20:16:19.570213Z  INFO main ThreadId(01) hazmat: src/main.rs:205: Log level set log_level=Level(Info)
2025-03-19T20:16:19.570324Z  INFO main ThreadId(01) hazmat: src/main.rs:206: I2C bus configured i2c_bus="/dev/verdin-i2c1"
2025-03-19T20:16:19.570456Z  INFO main ThreadId(01) hazmat: src/main.rs:207: Log directory configured log_path="logs"
2025-03-19T20:16:19.570561Z  INFO main ThreadId(01) hazmat: src/main.rs:208: Temperature unit configured temp_unit=Both
2025-03-19T20:16:19.570677Z  INFO main ThreadId(01) hazmat: src/main.rs:209: Polling interval set interval="1s"
2025-03-19T20:16:19.570790Z  INFO main ThreadId(01) hazmat: src/main.rs:210: EdgeDB DSN db_dsn="edgedb://edgedb:@localhost:5656"
2025-03-19T20:16:19.570925Z  INFO main ThreadId(01) hazmat: src/main.rs:211: Simulation mode: DISABLED simulation=false
2025-03-19T20:16:19.571911Z  INFO main ThreadId(01) hazmat::database: src/database.rs:141: EdgeDB client initialized
2025-03-19T20:16:19.615374Z  INFO main ThreadId(01) hazmat: src/main.rs:224: Using sensor name="HazmatAQI" type_id=3 instance_id=1
2025-03-19T20:16:19.616037Z  INFO main ThreadId(01) hazmat: src/main.rs:253: Using REAL hardware sensors
2025-03-19T20:16:19.616318Z  INFO main ThreadId(01) hazmat: src/main.rs:254: Note: ENS160 sensor requires up to 1 hour after first power-on for full accuracy
2025-03-19T20:16:19.616556Z  INFO main ThreadId(01) hazmat::ens160: src/ens160.rs:68: Starting ENS160 initialization...
2025-03-19T20:16:19.821755Z  WARN main ThreadId(01) hazmat::ens160: src/ens160.rs:98: Skipping warm-up wait. Initial readings may be inaccurate.
2025-03-19T20:16:19.929436Z  INFO main ThreadId(01) hazmat::hts221: src/hts221.rs:111: Calibration data loaded
2025-03-19T20:16:19.930908Z  INFO main ThreadId(01) hazmat::hts221: src/hts221.rs:73: HTS221 Status after init: 0x03
2025-03-19T20:16:19.931144Z  INFO main ThreadId(01) hazmat: src/main.rs:276: Waiting for initial sensor stabilization...
2025-03-19T20:16:20.932210Z  INFO main ThreadId(01) hazmat: src/main.rs:279: Starting main loop...
2025-03-19T20:16:20.936090Z  INFO main ThreadId(01) hazmat: src/main.rs:293: Environmental data: temperature="24.016°C / 75.229°F" humidity="47.812 %RH"
2025-03-19T20:16:20.972493Z  INFO main ThreadId(01) hazmat: src/main.rs:313: Air quality data: eCO2="431 (ppm)" TVOC="38 (ppb)" AQI="1 (1-5)" position="lat=33.258146°, lon=-97.149276°, alt=300.0m, hdg=180.0°, spd=12.0m/s"
2025-03-19T20:16:22.007083Z  INFO main ThreadId(01) hazmat: src/main.rs:293: Environmental data: temperature="23.998°C / 75.196°F" humidity="47.935 %RH"
2025-03-19T20:16:22.030071Z  INFO main ThreadId(01) hazmat: src/main.rs:313: Air quality data: eCO2="400 (ppm)" TVOC="23 (ppb)" AQI="1 (1-5)" position="lat=33.258146°, lon=-97.149276°, alt=300.0m, hdg=180.0°, spd=12.0m/s"
2025-03-19T20:16:23.060569Z  INFO main ThreadId(01) hazmat: src/main.rs:293: Environmental data: temperature="23.961°C / 75.129°F" humidity="47.865 %RH"
2025-03-19T20:16:23.081523Z  INFO main ThreadId(01) hazmat: src/main.rs:313: Air quality data: eCO2="405 (ppm)" TVOC="26 (ppb)" AQI="1 (1-5)" position="lat=33.258146°, lon=-97.149276°, alt=300.0m, hdg=180.0°, spd=12.0m/s"

So it works under sudo but not as a user. This seems to be corroborated by the fact that i2cdetect can’t read any i2c bus using the -y parameter unless also run under sudo. I have tried using privileged: true in the compose file, but the behavior is the same.

Based on the checking I have done, it appears that the cgroup and privileged entries are ignored. I have tried other ways by adding rmw to the devices entry, but nothing seems to allow access to the i2c bus from user mode.

I would like to not have to run this program as sudo in the production container, so any help in resolving this would be most appreaciated.

Hey @Glitch,

It sounds main issue is your custom user doesn’t have the correction permissions.
Our base container has a default user of torizon, which this script is ran from the base debian container image. You can see the user/group addition is already setup to accommodate torizon to certain hardware groups.

Can you try and run your program as base user torizon (non-sudo). This will help us isolate that the issue is how the user rustdev has it’s permissions set.

-Eric

@eric.tx Thanks so much for your reply. After taking a look at your script link, I was able to figure out the problem. My rust development container is based on the rust:1.81-bookworm base image. To that image, I create a default user (1000) and then install the rust development tools. For the production container, I use debian:bookworm-slim. For both of these images, there are two problems. Some hardware groups are missing and others have different GID values.

Specifically, gpio, 12cdev,spidev, and pwm are missing. Additionally input has GID 102 instead of Torizon’s 101. The group kvm is 104 instead of 102, and renderer is 105 instead of 103.

So, as a quick test, in addition to the device_cgroup_rules:, I added group_add: to the compose file with the groups 101, 102, 103, 49, 51, and 54. After doing that, the executable now runs as a non-root user.

I guess the long term fix is to do what is done in the script you provided, and add the missing groups to image.

Again, thanks for the help.