Peripheral Access Overview
This article describes the main concepts of using peripheral devices in embedded applications. These applications often require a variety of peripherals, such as accelerometers, GPS, and humidity sensors, which use different interfaces to communicate with the Operating System (OS). Therefore, it is essential to understand how Torizon OS manages these devices and how to access them within containers.
The following topics are covered:
- Pin Connection
- Kernel Space and User Space
- Hardware Devices in Linux
- Device Drivers
- Device Trees
- Expose Hardware to Containers
Toradex SoMs offer a wide range of industry-standard interfaces, including several Low-Speed I/O interfaces:
Every IO pin of a module has a default configuration and is assigned to a specific interface. To compare the available interfaces across different SoMs, use the Toradex Pinout Designer Tool.
However, pins can also have alternate functions. It is possible to modify pin function, voltage, reset status, and more. For detailed information, refer to the Connector and I/O Pins sections in the datasheet of your specific module.
For instructions on how to modify pin functionalities on Torizon OS, refer to the Pin Multiplexing article.
Hardware Access Permissions
Kernel Space and User Space
Before accessing peripheral devices, it is crucial to understand how the OS manages user permissions. Since Torizon OS is a Linux-based OS, the virtual memory is divided into two spaces:
- Kernel space: Where the OS core executes its services that manage memory, files, processes, peripheral devices, etc.
- User space: Where user processes run.
This separation is essential for maintaining OS stability and security. This means that applications that run in user space must use system calls to communicate with hardware. System calls are requests made by active processes for kernel services, such as IO or process creation.
System Resources Sharing Between Processes
Cgroups is a kernel feature that helps organize process access permissions to kernel resources. It can also be used to manage container access permissions to hardware.
Each device that cgroup handles has the following parameters:
- Type: a (all), c (char), or b (block).
- Major and minor numbers: The Linux kernel uses a combination of major and minor numbers to identify devices with different drivers and controllers.
- Access: a composition of r (read), w (write), and m (mknod).
The Linux device major and minor numbers list is available on the Linux Kernel Github.
You can check more details about Device Whitelist Controller in the Kernel Documentation. Note that some device groups are available only in Torizon OS image. To check major and minor numbers, you can use the command
ls -l /dev.
The following example shows the output for GPIO devices:
# ls -l /dev | grep gpio
crw-rw-r-- 1 root gpio 254, 0 Sep 15 15:01 gpiochip0
crw-rw-r-- 1 root gpio 254, 1 Sep 15 15:01 gpiochip1
crw-rw-r-- 1 root gpio 254, 2 Sep 15 15:01 gpiochip2
crw-rw-r-- 1 root gpio 254, 3 Sep 15 15:01 gpiochip3
crw-rw-r-- 1 root gpio 254, 4 Sep 15 15:01 gpiochip4
Hardware Devices in Linux
Linux treats peripheral devices as files, allowing them to be opened, read, and written like regular files. These device files are located in the
/dev directory and serve as an interface to Device Drivers, which is the software that actually accesses the hardware.
The Linux kernel also exports information about hardware and its drivers to user space through virtual files. These files reside in the
/sys directory. It allows you, for example, to get the temperature of your CPU by running:
$ cat /sys/class/thermal/thermal_zone0/
Finding the exported information of a device might appear complex for users who are unfamiliar with Linux-based Operating Systems. To assist you, Toradex offers many articles for different types of interfaces.
For comprehensive information, refer to the Linux Device Model article on the Linux Kernel documentation.
Device drivers run in kernel space and provide a software interface to the hardware. Users can write high-level applications to interact with a certain type of peripheral, in a generic way. The kernel is responsible for connecting the system calls to the methods of the respective device driver. The device driver then handles the specific device configuration and use.
Hardware details and their connection with drivers are often described in data structures known as Device Trees.
Available Drivers in OS image
Writing a device driver is a challenging task that requires specialized knowledge. Therefore, it is a common practice to check the Linux Source GitHub repository for the existence of hardware drivers before using a peripheral device.
For example, the accelerometer MPU-6050 has the
inv_mpu6050 driver available. You can verify if the driver is available on Torizon OS using the following command:
# zcat /proc/config.gz | grep -i 6050
m in the command output indicates that the driver is available in the operating system as a module and can be activated using Device Tree Overlays.
After applying the overlay, you can see that the driver is activated by running the
# lsmod | grep -e Module -e 6050
Module Size Used by
inv_mpu6050_i2c 16384 0
inv_mpu6050 36864 1 inv_mpu6050_i2c
industrialio_triggered_buffer 16384 2 inv_mpu6050,ti_ads1015
industrialio 90112 4 industrialio_triggered_buffer,inv_mpu6050,kfifo_buf,ti_ads1015
Add Drivers to OS images
If a specific driver module is not available in a Torizon OS image, you can use the TorizonCore Builder Tool to customize it. For comprehensive information, refer to the Building External Kernel Modules With Torizon.
In embedded Linux, the kernel receives hardware details during boot time through data structures known as Device Trees. Before the advent of device trees, board files were used to hardcode hardware details. In such cases, even a minor modification, such as changing an IO pin address, would require recompiling the entire kernel image.
Device trees can be modified using Device Tree Overlays, without the need for recompiling the entire device tree.
Expose Hardware to Containers
As containers run in a environment that is isolated from the OS, they do not have the permission to access hardware. Hardware access is given to containers at runtime through the Docker Run command or Docker Compose files.
Docker Run Command
There are several Docker Run options that might be useful depending on your hardware or application. However, two of them are worth highlighting here:
--device-cgroup-rule: Allows the access to all devices that have specific major and minor numbers. Therefore, it is a good option for giving permissions to the container for accessing all devices that are controlled by the same type of driver. Note that this method may also require the
-vflag, which mounts the host's device repository inside the container.
For example, to enable access within the container to all GPIO pins, you can use the following command:
# docker run -it --rm -v /dev:/dev --device-cgroup-rule='c 254:* rmw' <container_namespace>
--device: Makes a specific device accessible from a container. This option is a more granular method for adding devices to containers and it is better for security, as it avoids exposing unused devices.
For example, to enable the access, within the container, to a specific GPIO chip, use the following command:
# docker run -it --rm --device /dev/gpiochip0 <container_namespace>
--privileged option gives all capabilities to containers, so the entire host system is accessible from the container, which can expose security vulnerabilities. You will not be able to leverage some of the security features of containers using the
--privileged option. Therefore, it is advised to avoid using Privileged mode for the execution of your container in production systems.
Docker Compose File
Docker Compose files allow you, besides many features, to write and store the configuration that is used to run a container in a YAML file. The previously mentioned options to access devices within containers are also available for docker composer files: