Timeline
Timeline
2025-11-30
init
2025-12-01
add devicetree
2025-12-03
add devicetree plugin
This article introduces the concept and role of the Linux Device Tree, discusses in detail basic concepts such as DTS, DTSI, DTB, and DTC, the storage location and compilation method of device tree source code, and summarizes basic syntax rules of the device tree including root node, child nodes, and property definitions.
Linux Driver Notes
| Directory | Links |
|---|---|
| 1. Linux Driver Framework | |
| 2. Linux Driver Loading Logic | |
| 3. Character Device Basics | |
| 4. Concurrency and Race Conditions | |
| 5. Advanced Character Device Progression | |
| 6. Interrupts | |
| 7. Platform Bus | |
| 8. Device Tree | |
| 9. Device Model | |
| 10. Hot Plug | |
| 11. pinctrl Subsystem | |
| 12. GPIO Subsystem | |
| 13. Input Subsystem | |
| 14. Single Bus | |
| 15. I2C | |
| 16. SPI | |
| 17. UART | |
| 18. PWM | |
| 19. RTC | |
| 20. Watchdog | |
| 21. CAN | |
| 22. Network Device | |
| 23. ADC | |
| 24. IIO | |
| 25. USB | |
| 26. LCD |
Introduction to Device Tree
Background
Device Tree is a hardware description mechanism used to describe the characteristics, connection relationships, and configuration information of hardware devices in embedded systems and operating systems. It provides a platform-independent way to describe hardware, reducing the coupling between the kernel and hardware, and improving system portability and maintainability.
In the platform bus model, we useplatform_devicestructures to describe hardware devices, which is a traditional way of describing platform bus devices.
Eachplatform_devicestructure represents a specific hardware device, and by registering it on the platform bus, the kernel can communicate and interact with the device. This structure contains information such as the device name, resources (e.g., memory addresses, interrupt numbers), and device driver.
However, over time, the ARM portion of the Linux kernel contained a large amount of platform-specific configuration code, which was often messy and repetitive, leading to maintenance difficulties and increased workload. The introduction of the device tree brought revolutionary changes to the Linux kernel on the ARM architecture. It provides a unified way to describe hardware, making support for different chips and board levels simpler and more flexible. Additionally, the device tree offers visualization and readability of hardware configurations, making it easier for developers to understand and debug hardware.
DTS(Device Tree Source):DTS is the source file of the device tree, using a text-like syntax to describe the structure, properties, and connections of hardware devices. DTS files have the .dts extension and are typically written by developers. They are human-readable forms used to describe the hierarchical structure and attribute information of the device tree.
DTSI(Device Tree Source Include):DTSI files are include files for device tree source files. They extend the functionality of DTS files and are used to define reusable device tree fragments. DTSI files have the .dtsi extension and can be included and shared across multiple DTS files. Using DTSI improves the reusability and maintainability of the device tree (similar to the role of header files in the C language).
DTB(Device Tree Blob):DTB is the binary representation of the device tree. DTB files are binary files compiled from DTS or DTSI files, with the .dtb extension. DTB files contain the structure, properties, and connection information of the device tree and are loaded and parsed by the operating system. At runtime, the operating system uses DTB files to dynamically identify and manage hardware devices.
DTC(Device Tree Compiler):DTC is the device tree compiler. It is a command-line tool used to compile DTS and DTSI files into DTB files. DTC converts the text-format device tree source code into a binary device tree representation so that the operating system can load and parse it. DTC is an important tool in device tree development.
Location of device tree in source code
Device tree source files under the ARM64 architecture are typically stored in thearch/arm64/boot/dts/directory and its subdirectories. This directory is also the root directory for device tree source files and contains subdirectories for different ARM64 platforms and devices.
Within the ARM64 subdirectories, they are also organized and categorized by hardware platform, device type, or manufacturer. The naming of these subdirectories may be related to specific chip manufacturers (such as Qualcomm, NVIDIA, Samsung). Since the SoC we use is Rockchip’s rk3568, the matching device tree directory isarch/arm64/boot/dts/rockchip. Each subdirectory may contain multiple device tree files to describe different hardware configurations and device types.
Compilation of the device tree
In the Linux kernel source code, the source code and related tools of DTC (Device Tree Compiler) are usually stored in thescripts/dtc/directory
Device tree compilation
1 | dtc -I dts -O dtb -o output.dtb input.dts |
-Iindicates the input file type-Oindicates the output file type-oindicates the compiled output file name
Device tree decompilation
1 | dtc -I dtb -O dts -o output.dts input.dtb |
Compilation in Linux
1 | # Compile all arm64 DTB files |
For debugging, exposing the DT to user space may be useful. Use the
CONFIG_PROC_DEVICETREEconfiguration variable to achieve this. Then, you can browse the/proc/devicetreeDT in
Basic syntax of device tree
Root node
1 | /dts-v1/; // Device Tree Version Information |
Child Node
1 | [label:] node-name@[unit-address] { |
Node Label(Optional): A node label is an optional identifier used to reference the node in the device tree. Labels allow other nodes to directly reference this node, establishing reference relationships within the device tree.
Node Name: The node name is a string that uniquely identifies the node’s location in the device tree. The node name is typically the name of the hardware device but must be unique within the device tree.
Unit Address(Optional): The unit address is used to identify an instance of a device. It can be an integer, a hexadecimal value, or a string, depending on the device’s requirements. The purpose of the unit address is to distinguish different instances of the same type of device.
Property Definitions: Property definitions are a set of key-value pairs used to describe the configuration and characteristics of a device. Properties can be defined based on the device’s requirements, such as register addresses, interrupt numbers, clock frequencies, etc.
Child Nodes: Child nodes are sub-items of the current node, used to further describe sub-components or configurations of the hardware device. Child nodes can contain their own property definitions and deeper child nodes, forming the hierarchical structure of the device tree.
reg property
The reg property is used tospecify the register address and size of a device in the device tree, providing the register mapping relationship with the physical device in the device tree.
The reg property can havesingle-value formatandlist-value formatthese two common formats.
- The single-value format is as follows:
1 | reg = <address size>; |
This format is suitable for describing a single register. Here, address is the starting register address of the device, which can be an integer or hexadecimal value (u32). size indicates the size of the register, i.e., the number of bytes it occupies.
Example:
1 | my_device { |
- List-value format
1 | reg = <address1 size1 address2 size2 ...>; |
When a device has multiple register regions, the list-value format of the reg property can be used to describe the address and size of each register region. In this way, the positions and sizes of multiple registers can be specified to describe the complete register mapping of the device.
Example:
1 | my_device { |
Each device has at least one node in the DT. Some properties are common to many device types, especially those on buses known to the kernel (SPI, I2C, platform, MDIO, etc.). These properties are reg,
#address-cellsand#size-cellswhose purpose is device addressing on their respective bus.Every addressable device has a reg property.
That is, the primary addressing property is reg, a generic property whose meaning depends on the bus the device is on.
#address-cellsand#size-cellsProperties
#address-cellsand#size-cellsproperties are used to specify, in the device tree to be set up in the previous subsection,address cellsandaddress sizethe number of bits. They provide the metadata needed for device tree parsing to correctly interpret the address and size information of devices.
size-cellandaddress-cellprefix of#(sharp) can be translated as length.Addressable devices inherit from their parent node’s
#size-celland#address-cell, the parent node represents the bus controller. Specifying that a device has#size-celland#address-celldoes not affect the device itself, but affects its child devices. In other words, when interpreting a given node’sregproperty, the parent node’s#address-cellsand#size-cellsvalues must be known. The parent node can freely define the addressing scheme applicable to its child nodes (children).
#address-cells
The property is a special property located at the root node of the device tree, which specifies the number of bits in an address cell within the device tree. An address cell is a single unit used to represent a device address in the device tree. It is typically an integer, which can be a decimal or hexadecimal value. Its value tells the software parsing the device tree how many bits to use to represent one address cell when interpreting device addresses.
By default,#address-cellsthe value of is 2, meaning two cells are used to represent a device address. This means the device’s address will consist of two integers (each using a specified number of bits).
For example, for a device usingtwo 32 bit integerFor devices representing addresses (64-bit), the #address-cells property can be set to <2> in the root node of the device tree.
#size-cells
#size-cells The property is also a special property located in the root node of the device tree, which specifies the number of bits for size cells in the device tree. A size cell is a single unit used to represent the size of a device in the device tree. It is typically an integer, which can be a decimal or hexadecimal value.
example
1 | node1 { |
- Address part: 0x02200000 is interpreted as one address cell, with the address being 0x02200000.
- Size part: 0x4000 is interpreted as one size cell, with the size being 0x4000.
model property
In the device tree,the model property is used to describe the model or name of the device. It is typically an attribute of a device node, used to provide identification information about the device. The model property is optional but is frequently used in practice.
1 | my_device { |
The model property is often used to identify and distinguish different devices, especially when the compatible property of device nodes is the same or similar. By using different model property values, the type of device in use can be determined more accurately.
status property
In the device tree, the status propertyis used to describe the state of a device or node. It is one of the common properties in the device tree, used to indicate the availability or operational status of a device or node.
The values of the status property can be the following:
“okay"okay”: Indicates that the device or node is working normally and available.
“disabled"disabled”: Indicates that the device or node is disabled and unavailable.
“reserved"reserved”: Indicates that the device or node is reserved and temporarily unavailable.
“fail"fail”: Indicates that the device or node has failed initialization or operation and is unavailable.
Example:
1 | my_device { |
The compatible property
In the device tree, the compatible property is used to describe the compatibility information of a device. It is one of the important properties in the device tree,used to identify the matching relationship between a device node and a driver.。
The value of the compatible property is a string or a list of strings, used to specify the rules for compatibility between a device node and the corresponding driver or device descriptor. Typically, the value of the compatible property is defined by the device manufacturer and used in the device tree.
The following are some examples of common compatible property values:
- Single string value: for example
"vendor,device", used to specify that a device node is compatible with a specific device from a specific manufacturer. - String list: for example
["vendor,device1", "vendor,device2"], used to specify that a device node is compatible with multiple devices, typically when the device node has multiple variants or configurations. - Wildcard matching: for example
"vendor,*", used to specify that a device node is compatible with all devices from a specific manufacturer, regardless of the specific device identifier.
By using the compatible property, the device tree can provide matching information between devices and drivers. When the device tree is parsed by the operating system or device management software, the appropriate driver is selected based on the compatible property value of the device node to initialize and configure the device.
aliases node
The aliases node is a special node used todefine device aliases. This nodeis located at the root of the device tree, and has a node path/aliases
1 | aliases { |
In the definition of aliases,the & symbol is used to reference nodes in the device tree. The purpose of aliases is to provide more readable names, making the device tree easier to understand and maintain. By using aliases, associations between device nodes can be simplified, and repeated input of device node paths can be reduced.
**Aliases defined in the aliases node are only visible within the device tree and cannot be referenced outside the device tree.**They are mainly used for internal organization and referencing within the device tree to improve readability and maintainability.
chosen node
The chosen node is a special node in the device tree, used topass and store information related to system boot and configuration. It has a path/chosen。
The chosen node typically contains the following child nodes and properties:
- bootargs: used tostore the command-line parameters passed when booting the kernel. It can contain information such as kernel parameters, device tree parameters, etc. During the boot process, the operating system or bootloader can read this property to obtain startup parameters.
- stdout-path: used toSpecifies the device path for standard outputDuring the boot process, the operating system can use this property to determine which device to send console output to, such as a serial port or display.
- firmware-name: Used to specify the name of the system firmware. It can be used to identify the type and version of the bootloader or firmware being used.
- linux,initrd-start and linux,initrd-end: These properties are used to specify the start and end addresses of the Linux kernel initial RAM disk (initrd). This information is used by the bootloader during the boot process to load the initrd into memory for the kernel.
- Other custom properties: The chosen node can also contain other custom properties for storing information specific to system boot and configuration. The specific meaning and usage of these properties depend on the device tree’s usage and context.
Example
1 | chosen { |
By using the chosen node, relevant information during the system boot process can be conveniently passed to the operating system or bootloader. This allows various components of system boot and configuration to share and access this information, enabling a more flexible and configurable system boot flow.
device_type node
In the device tree,the device_type node is a node used to describe the device type. Ittypically exists as a property of a device node。
The value of the device_type property is a string used to identify the type of the device.
The existence of the device_type node helps the operating system or other software identify and handle devices. It provides basic classification information about the device, enabling drivers, device tree parsers, or other system components to perform corresponding operations based on the device type.
Common device types include but are not limited to:
- cpu: Represents the central processing unit.
- memory: Represents a memory device.
- display: Represents a display device, such as an LCD screen.
- serial: Represents a serial communication device, such as a serial port.
- ethernet: Represents an Ethernet device.
- usb: Represents a Universal Serial Bus device.
- i2c: Represents a device that communicates using the I2C (Inter-Integrated Circuit) bus.
- spi: Represents a device that communicates using the SPI (Serial Peripheral Interface) bus.
- gpio: Represents a general-purpose input/output device.
- pwm: Represents a pulse-width modulation device.
These are just some examples of common device types. In practice, device types can be customized and extended based on specific hardware and device tree usage.
Custom Properties
Custom properties in the device tree are attributes added by users based on specific needs. These properties can be used to provide additional information, configuration parameters, or metadata to meet the specific requirements of a device or system.
For example, you can define a custom pin number attribute ‘pinnum’ in the device tree.
1 | my_device { |
Definitions of some data types used in the device tree:
- Text strings are represented with double quotes. Commas can be used to create a list of strings.
- Cells are 32-bit unsigned integers separated by angle brackets.
- Boolean data is simply an empty property. Its value is true or false depending on whether the property exists.
Case Analysis
Case Analysis: Interrupts
arch/arm64/boot/dts/rk3568.dtsi
1 | pinctrl: pinctrl { |
arch/arm64/boot/dts/rockchip/topeet-screen-lcds.dts
1 | &i2c1 { |
The ‘interrupts’ Property
The ‘interrupts’ property is used to specify interrupt-related information for a device. It describesthe type of interrupt controller, the interrupt number, and the interrupt trigger type.。
1 | gpio0: gpio0@fdd60000 { |
- Interrupt Controller Type
The first parameter of the ‘interrupts’ property specifies the type of interrupt controller.
Common types include**GIC (Generic Interrupt Controller)、IRQ (Basic Interrupt Handling)**etc. For example, in the given code snippet, GIC_SPI indicates that the interrupt controller type is GIC SPI interrupt.
The interrupt controller is responsible for managing interrupt signals in the system. It can be a dedicated interrupt controller in hardware or an interrupt controller within the processor.
- Interrupt number
The second parameter of the interrupts property specifies the interrupt number used by the device.
The interrupt number is a unique identifier used to distinguish different interrupt signal sources. The system uses the interrupt number to identify the interrupt source and perform corresponding interrupt handling.
The interrupt number can be an integer value, a macro definition, or a symbolic reference. In the given code snippet, 33 indicates that the interrupt number used by the device is 33.
- Interrupt trigger type
The third parameter of the interrupts property specifies the interrupt trigger type, i.e., the triggering condition of the interrupt signal. Common trigger types include edge-triggered and level-triggered.
Level-triggered means the interrupt signal triggers when maintaining a specific level state, which can be high-level triggered or low-level triggered.
In the given code snippet, IRQ_TYPE_LEVEL_HIGH indicates that the interrupt trigger type is high-level triggered. The macro definitions for trigger types are in the kernel source codeinclude/dt-bindings/interrupt-controller/irq.hdirectory
1 |
interrupt-controller property
interrupt-controllerThis property is used toidentify that the device described by the current node is an interrupt controller。
An interrupt controller is ahardware or software module, responsible for managing and distributing interrupt signals. It receives interrupt requests from various devices and distributes interrupts to the corresponding processors or devices based on priority and configuration rules.
interrupt-controllerThis property itself has no specific value; it only needs to appear in the node’s property list.
interrupt-parent property
interrupt-parentThis property is used in the device tree to establish an association between an interrupt signal source and an interrupt controller. Itspecifies the interrupt controller node to which the interrupt signal source belongs, ensuring correct interrupt handling and distribution。
interrupt-parentThe property value is a reference that points to the path or label of the interrupt controller node.
You can use a path to reference the interrupt controller node, such as/interrupt-controller-node, or use a label to reference the interrupt controller node, such as&interrupt-controller-label
1 | ft5x061:ft5x06@38 { |
Not present in gpio0
interrupt-parent, but because there is a default in the root DTSinterrupt-parent = GIC, GPIO0 does not need to be explicitly written.
#interrupt-cellsattribute
#interrupt-cellsThe attribute is used todescribe the number of interrupt number units for each interrupt signal source in the interrupt controller。
An interrupt number unit refers to a fixed-size unit used to represent the interrupt number and other related information. By specifying the number of interrupt number units, the operating system can correctly parse and process interrupt information and associate it with the interrupt controller and interrupt signal source.
#interrupt-cellsThe value of the attribute is an integer representing the number of interrupt number units. Typically, this value is a positive integer, such as 1, 2, or 3, depending on the requirements of the interrupt controller and device.
1 | gpio0: gpio0@fdd60000 { |
In gpio0, the interrupt controller is gic, and in the gic node#interrupt-cellsthe attribute is set to 2, which is why the interrupts attribute in the gpio0 node has two values;
while the interrupt controller of ft5x061 is gpio3, and in the gpio3 node#interrupt-cellsthe attribute is set to 2, so the interrupts attribute of the ft5x06 node has only two values.
Comparison of other SoC device trees
NXP
1 | gpio1: gpio@0209c000 { |
Samsung
1 | gpio_c: gpioc { |
Case analysis: clock
Clock is used todescribe the clock sources, clock-related configurations, and connection relationships in hardware devices and systems.。
Clocks play a crucial role in computer systems, used to synchronize and time the operations of various hardware devices.
Clocks can be divided into two main roles:clock providerandclock consumer。
Clock provider
- Definition: A clock provider isa hardware or software module responsible for generating and providing clock signals. It can be a clock controller, PLL, clock generator, etc.
- Device tree node: A clock provider is represented in the device treein the form of a clock node。
Clock provider property
clock-cells
This property is used to specify the number of bits for the clock ID. It is an integer value representing the number of bits for the clock ID.
Typically, whenclock-cellsis 0, it indicates one clock; when it is 1, it indicates multiple clocks.
1 | osc24m: osc24m { |
clock-frequency
is a property in the device tree used to specify the clock frequency. It describes the frequency of the clock signal provided by a clock node, using Hertz (Hz) as the unit.
For a clock producer node,clock-frequencyproperty indicates the frequency of the clock signal generated by that node. It is used to describe the output frequency of hardware or software modules that generate clock signals, such as clock controllers, crystal oscillators, and PLLs.
1 | osc24m: osc24m { |
Clock consumer
A clock consumer isa hardware device or module that depends on a clock signal. They obtain clock signals by referencing the clock source provided by a clock producer node.
Clock consumer properties
clock
This property is used to specify the clock sources required by a clock consumer node. It is an integer array, where each element is a clock number representing a clock source needed by the clock consumer.
clock-names
An optional property used to specify the names of the clock sources required by a clock consumer node. It is a string array, corresponding one-to-one with the clocks array, to provide descriptive names for the clock sources.
An example of a clock consumer is as follows:
1 | clock: clock { |
The clocks property specifies the clock sources used by this node, referencingcruin the nodeCLK_VOPclock source.clock-namesThe attribute specifies the name of the clock source, here it isclk_vop。
assigned-clocksandassigned-clock-rates
are attributes in the device tree used to describe multiple clocks, typically used together.
assigned-clocksThe attribute is used toidentify the clock source used by the clock consumer node。
It is an integer array, where each element corresponds to a clock number. The clock number refers to the index of the clock source provided by the clock producer node (such as a clock controller).
By using theassigned-clocksattribute in the clock consumer node, the required clock source for that node can be specified.
assigned-clock-ratesThe attribute is used tospecify the clock frequency for each clock source。
It is an integer array, where each element corresponds to the frequency of a clock source. The clock frequency is expressed in Hz (Hertz).
assigned-clock-ratesThe number and order of elements in the attribute should correspond toassigned-clocksthe clock numbers in the attribute.
1 | cru: clock-controller@fdd20000 { |
clock-indices
clock-indicesThe attribute is used tospecify the index value of the clock source used by the clock consumer node。
It is an integer array, where each element corresponds to the index of a clock source.
The clock index refers to the number of the clock source provided by the clock producer node (such as a clock controller). By using theclock-indicesattribute in the clock consumer node, the required clock source for that node can be explicitly specified and matched in a specific order.
1 | scpi_dvfs: clocks-0 { |
In the first node,atlclk,aplclk,gpuclkthe indices of the three clock sources are set to 0, 1, and 2 respectively; in the second node,pxlclkthe index value of the clock source is set to 3.
assigned-clock-parents
Used tospecify the parent clock source of the clock source used by the clock consumer node。
It is an array of clock source references, where each element corresponds to a reference to a parent clock source.
In the clock hierarchy,some clock sources may be parent clock sources of other clock sources, meaning they provide clock signals as input to other clock sources.
By using theassigned-clock-parentsattribute in the clock consumer node, the required parent clock source for that node can be explicitly specified and matched in a specific order.
1 | clock: clock { |
assigned-clocksThe attribute specifies the clock source used by this node, referencing two clock source nodes:clkcon 0andpll 2。
assigned-clock-parentsThe attribute specifies the parent clock source for these clock sources, referencing thepll 2clock source node.
assigned-clock-ratesThe attribute specifies the clock frequency for each clock source, which are 115200 and 9600 respectively.
Case Study: CPU
The device tree’scpus nodeis an important node used to describe the processors in the system. It is thetop-level node of the processor topology, containing all processor-related information.
Node Structure:
The cpus node is a container node that contains child nodes for each processor in the system.
The name of each child node is typicallycpu@X, where X is the index number of the processor. Each child node contains processor-related attributes, such as clock frequency, cache size, etc.
Processor Properties:
cpu@XThe attributes in the child node may include the following information:
device_type: Indicates the device type as a processor (e.g., “cpu”).reg: Specifies the address range of the processor, typically a physical address or register address.compatible: Specifies the compatibility information of the processor, used to match the corresponding device driver.clock-frequency: Specifies the clock frequency of the processor.cache-size: Specifies the cache size of the processor.
Processor Topology Relationships
In addition to the basic attributes of the processor,the cpus node can also contain other nodes used to describe processor topology relationships, to provide more detailed processor topology information. These nodes can help the operating system and software understand the connection relationships, organizational structure, and characteristics among processors.
cpu-map: Describes the mapping relationship of processors, typically used in multi-core processor systems.socket: Describes the physical sockets or chipset in a multi-processor system.cluster: Describes a processor cluster, which is a logical group formed by organizing multiple processors together.core: Describes a processor core, which is an independent execution unit within a physical processor.thread: Describes a processor thread, which is a thread within a physical processor core. (Multiple threads exist only if hyper-threading is enabled; otherwise, one core has only one thread.)
The nesting relationship of these nodes can form a hierarchical structure under the cpus node, reflecting the processor’s topology.
Single-core CPU:
1 | cpus { |
Multi-core CPU:
1 | cpus { |
The cpus node is a container node that contains the cpu0 child node. This node uses the#address-cellsand#size-cellsattributes to specify the number of units for address and size.
cpu-map, socket, cluster nodes
cpu-mapnode isone of the nodes in the device tree used to describe the mapping relationship of processors with big.LITTLE architecture。- Its parent node must be the
cpusnode, whilechild nodes can be one or moreclusterandsocketnodes. - Through
cpu-mapnodes, the connections and organizational structure between different cores and clusters can be defined.
- Its parent node must be the
socketnodes are used to describe the mapping relationship between processor sockets (socket).- Each
socketchild node represents a processor socket, which canusecpu-map-maskattribute to specify the core used by that socket. - By specifying the appropriate
socketfor eachcpu-map-mask, you can define the cores used in different sockets. This allows the operating system and software to understand the core allocation across different sockets.
- Each
clusterThe node is used to describe the mapping relationship between cores (cluster).- Each
clusterchild node represents a core cluster, which canuse thecpu-map-maskattribute to specify the cores used by that cluster. - By specifying the appropriate
clusterfor eachcpu-map-maskchild node, you can define the cores used in each cluster. This allows the operating system and software to understand the core allocation across different clusters
- Each
A specific example of a big.LITTLE architecture
1 | cpus { |
This device tree describes a system with multiple CPU cores, including four Cortex-A53 cores and two Cortex-A72 cores.
core and thread nodes
coreandthreadNodes are typically used to describe the configuration of processor cores and threads.
coreNodes are used to describe processor cores. A processor usually consists of multiple cores, each of which can independently execute instructions and tasks.
threadNodes are used to describe processor threads. A thread is the basic execution unit running on a processor core, and each core can support multiple threads. (Unless hyper-threading is enabled, a core has only one thread.)
1 | cpus { |
Case Study: GPIO
1 | gpio0: gpio@fdd60000 { |
gpio-controller property
gpio-controllerThis property is used toidentify a device node as a GPIO controller。
A GPIO controller is a hardware module or driver responsible for managing and controlling GPIO pins。
gpio-controllerThis property typically appears as an attribute of a device node, located in the device node’s property list.
When a device node is identified as a GPIO controller, it usually defines a set of GPIO pins and provides related GPIO control and configuration functions. Other device nodes can use this GPIO controller to control and manage their GPIO pins.
#gpio-cellsproperty
#gpio-cellsThis property is used tospecify the encoding method of GPIO pin descriptors. GPIO pin descriptors are a set of values used to identify and configure GPIO pins, such as pin numbers, pin attributes, etc.
#gpio-cellsThe property value of this attribute is an integer, indicating the encoding used forGPIO pin descriptorThe number of cells. Usually this value is 2.
1 | ft5x06: ft5x06@38 { |
gpio-ranges property
Background
EachGPIO inside the controllerassigns to each of its pins alocal number, for example, internal GPIO controller numbers: 0, 1, 2, 3, … 31
Howeverin the entire system or in other hardware modules, these GPIOs may have their ownglobal numberexternal numbers: 100, 101, 102, 103, … 131
That is,The internal and external numbers of the controller may be inconsistent.。
To facilitate other devices using GPIO, a mapping table is needed to map the controller’s internal numbers to the system’s external numbers. This is the
gpio-rangesfunction.
gpio-rangesThe attribute is a device tree property used for**describing GPIO range mapping.**It is typically used to describe GPIO controllers with a large number of GPIO pins, simplifying the encoding and access of GPIO pins.gpio-rangesThe attribute is a list containing a series of integer values, each corresponding to a GPIO controller in the device tree. Each integer value in the list provides the following information in a specific order:
- The starting value of the external pin number
- The starting value of the GPIO controller’s internal local number
- The size of the pin range (number of pins)
Example:
1 | ft5x06: ft5x06@38 { |
<&pinctrl>Indicates a reference to the pin controller node named pinctrl0 0 32Indicates- External pins start from 0
- The controller’s local number starts from 0
- A total of 32 pins are mapped.
GPIO description attributes and gpio-cells
1 | ft5x06: ft5x06@38 { |
The number of GPIO pin description attributes is determined by#gpio-cellsbecause in the gpio0 node,#gpio-cellsthe attribute is set to 2, so the number of GPIO pin description attributes in the device tree above is also 2.
Among them,RK_PB6is defined in the kernel source directory underinclude/dt-bindings/pinctrl/rockchip.hthe header file, which defines macro definitions for RK pin names and GPIO numbers.
1 |
GPIO_ACTIVE_LOWis defined in the source directory underinclude/dt-bindings/gpio/gpio.hindicating it is set to low level. Similarly,GPIO_ACTIVE_HIGHindicates setting this GPIO to high level. However, this is only a description of the device; the actual configuration must match the driver.
Other attributes
1 | gpio-controller@00000000 { |
- ngpios attribute
Specifies thenumber of GPIO pins supported by the GPIO controller. It indicates the total number of GPIO pins available on the device.
In this example,ngpiosthe value is 18, meaning the GPIO controller supports 18 GPIO pins.
- The gpio-reserved-ranges property
**defines the reserved GPIO ranges.**Each range is represented by two integer values enclosed in angle brackets.
A reserved GPIO range means these GPIO pins are unavailable or have been reserved by other devices or functions.
In this example, there are two reserved ranges: <0 4> and <12 2>. <0 4> indicates that 4 consecutive pins starting from pin 0 are reserved, while <12 2> indicates that 2 consecutive pins starting from pin 12 are reserved.
- The gpio-line-names property
**defines the names of the GPIO pins,**separated by commas. Each name corresponds to a GPIO pin. These names are used to identify and recognize the function or connected device of each GPIO pin.
In this example,gpio-line-namesthe property lists the names of multiple GPIO pins, such as “MMC-CD”, “MMC-WP”, “voD eth”, and so on. Through these names, the function or purpose of each GPIO pin can be clearly understood.
Example

From the above schematic, the pin net label of the LED can be obtained asWorking_LEDEN_H_GPIO0_B7, and the corresponding pin isGPIO0_B7。
Then, check the kernel source directory for thedrivers/leds/leds-gpio.cfile, which is the LED driver file, and find the part related to the compatible match value, as shown below:
1 | static const struct of_device_id of_gpio_leds_match[] = { |
It can be seen that the compatible match value is gpio-leds.
Finally, in the kernel source directory, theinclude/dt-bindings/pinctrl/rockchip.hheader file defines macros for RK pin names and GPIO numbers, as shown below:
1 | /* SPDX-License-Identifier: GPL-2.0-or-later */ |
include/dt-bindings/gpio/gpio.hThe file defines macros for pin polarity settings.
1 | /* SPDX-License-Identifier: GPL-2.0 */ |
Therefore, the device tree is as follows:
1 | /dts-v1/; |
&gpio0is a reference to the pin controller,RK_PB7is the pin number or identifier,GPIO_ACTIVE_HIGHindicates that the active level of this GPIO pin is high level.
Comparison with other SoCs
NXP
1 | gpio1: gpio@0209c000 { |
samsung
1 | gpio_c: gpioc { |
Case analysis: pinctrl
Introduction to pinmux
**Pinmux (pin multiplexing) refers to the process of configuring and managing pin functions in a system.**In many modern integrated circuits,**a single pin can have multiple functions,**such as GPIO, UART, SPI, or I2C. By using pin multiplexing, it is possible to switch between these different functions.
Pin multiplexing is implemented through both hardware and software methods.
- At the hardware level,
chip designs provide multiple function options for each pin. These functions are typically defined by the chip manufacturer in the chip specification documentation. By programming registers or switches, a specific function can be selected to connect to the pin. This hardware-level configuration is usually managed by thePin ControllerorPin Mux Controller.

- At the software level,
the operating system or device drivers need to understand and configure pin functions. They use Device Tree or Device Tree Bindings to describe and configure pin functions. In the Device Tree, the multiplexing function of a pin can be specified, connecting it to a specific hardware interface or function. The operating system or device driver parses the Device Tree during startup and initializes and sets the pins according to the configuration.
From the above diagram, it can be seenUART4_RX_M1The corresponding pins can be multiplexed into the following 6 functionsLCDC_D16、VOP_BT1120_D7、GMAC1_RXD0_M0、UART4_RX_M1、PWM8_M0、GPIO3_B1_d, and the corresponding BGA pin label is AG1
InBGA (Ball Grid Array) package, pin labels are identifiers used to uniquely identify each pin. These labels are typically defined by the chip manufacturer and provided in the chip’s specification documents or datasheets.
The pin labels of a BGA chip usually consist of a combination of letters and numbers. They are used to mark the pads on the bottom of the chip package. Each pin label corresponds to a function or signal inside the chip, so as to correctly connect to the target position on the printed circuit board (PCB). The pin label diagram of the RK3568 is shown below:

It can be seen that the vertical direction has 28 letter-type labels from A to AH, and the horizontal direction has 28 number-type labels from 1 to 28. Rockchip has also added a multiplexing function diagram based on BGA positions in the corresponding 3568 datasheet, part of which is shown in the figure below.

Among them, the black boxes represent reserved pins, the other colored boxes are generally power and ground, and the white boxes represent pins with specific multiplexing functions.
Using pinctrl to Set Multiplexing Relationships
pinctrl (pin control) is used to describe and configure the pin functions and connection methods on hardware devices. It is part of the device tree, used to pass pin configuration information to the operating system and device drivers during the boot process, so as to correctly initialize and control the pins.
In the device tree, pinctrl (pin control) uses the concepts of client and server to describe the relationships and configurations of pin control.
Client
1 | node { |
In the above example,pinctrl-namesThe attribute defines a state name: default.
pinctrl-0The attribute specifies the pin configuration corresponding to the first state, default.
<&pinctrl_hog_1>is a pin descriptor that references a pin controller node namedpinctrl_hog_1. This means that in the default state, the device’s pin configuration will use the configuration defined in thepinctrl_hog_1node.
1 | node { |
In the example,pinctrl-namesthe attribute defines two state names: default and wake up.
pinctrl-0The attribute specifies the pin configuration corresponding to the first state, default, referencing thepinctrl_hog_1node.
pinctrl-1The attribute specifies the pin configuration corresponding to the second state, wake up, referencing thepinctrl_hog_2node.
This means the device can be in one of two different states, each using a different pin configuration.
1 | node { |
In this example,pinctrl-namesthe attribute still defines a state name: default.
pinctrl-0The attribute specifies the pin configuration corresponding to the first state, default, but unlike the previous example, it references two pin descriptors:pinctrl_hog_1andpinctrl_hog_2。
This means that in the default state, the device’s pin configuration will usepinctrl_hog_1andpinctrl_hog_2the configurations defined in the two nodes.
This method cancombine the configurations of multiple pin controllers togetherto meet the pin requirements in a specific state.
Server
The server is the part of the device tree that defines pin configurations. It contains pin groups and pin descriptors, providing pin configuration options for the client.
The server defines the pinctrl node in the device tree, which contains the definitions of pin groups and pin descriptors.
Here, Rockchip’s RK3568 is taken as an example to explain the pinctrl server. To facilitate users in setting pin multiplexing relationships through pinctrl, the Rockchip BSP engineers wrote the configurations containing all multiplexing relationships in the kernel directory’sarch/arm64/boot/dts/rockchip/rk3568-pinctrl.dtsidevice tree:
1 | // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
In the pinctrl node, it is the multiplexing function of each node. Then we take the pin multiplexing of uart4 as an example
1 | uart4 { |
Among them,<3 RK_PB1 4 &pcfg_pull_up>and<3 RK_PB2 4 &pcfg_pull_up>respectively indicate setting the PB1 pin of GPIO3 to function 4, and setting the PB2 pin of GPIO3 to function 4 as well, with the electrical properties set to pull-up. By looking up the schematic, the BGA package positions of the two pins are AG1 and AF2 respectively.

It can be seen that Function 4 corresponds to the transmit and receive ends of UART 4, and the configuration of the pinctrl server corresponds one-to-one with the pin multiplexing functions in the datasheet.
If you want to setRK_PB1andRK_PB2to GPIO function, how should it be configured? From the figure above, it can be seen that GPIO corresponds to Function 0, so the following pinctrl content can be used to setRK_PB1andRK_PB2to GPIO function (in fact, if no function multiplexing is performed on this pin, it will be set to GPIO function by default):
<3 RK_PB1 0 &pcfg_pull_up><3 RK_PB2 0 &pcfg_pull_up>
Finally, let’s look at the client’s reference to the uart4 server. The specific content is in the kernel source directoryarch/arm64/boot/dts/rockchip/topeet-rk3568-linux.dts
1 | &uart4{ |
By referencing the server’s pin descriptors in the client, the device tree can associate the pin configurations of the client and server.
pinctrl Example Writing
In the SDK source directory, thedevice/rockchip/rk356x/BoardConfig-rk3568-evb1-ddr4-v10.mkdefault configuration file can reveal that the compiled device tree isrk3568-evb1-ddr4-v10-linux.dts, and the inclusion relationships between device trees are listed as follows:

The above device tree is for the 4.x version of the kernel
The LED has been properly configured in the device tree:
arch/arm64/boot/dts/rockchip/topeet-rk3568-linux.dts
1 | //LED |
Pinctrl is not configured here, so why can the LED still work normally? As mentioned above, in the RK3568, when the GPIO0_B7 pin is not multiplexed for any function, it defaults to GPIO functionality. Therefore, even without pinctrl, the LED can function normally.
We can write our own LED node:
1 | my_led: led { |
Server
1 | rk_led{ |
DTB File Format Parsing
The Device Tree Blob (DTB) format is a flat binary encoding of device tree data. It is used to exchange device tree data between software programs. For example, when booting an operating system, firmware passes the DTB to the OS kernel.
The DTB format encodes device tree data in a single, linear, pointer-free data structure.
It consists ofa small headerand three variable-sized parts:memory reservation block、structure blockandstrings block. These should appear in the flattened device tree in that order. Therefore, the device tree structure as a whole, when loaded into a memory address, will resemble the following diagram

Take the following device tree file as an example
1 | /dts-v1/; |
After compiling into dtb, open it with Binary Viewer:

Header
1 | struct fdt_header { |
| Core function classification | Field | Key Description |
|---|---|---|
| File Identifier | magic | Fixed magic number of DTB (0xd00dfeed, big-endian), used to verify file validity |
| Size and Offset Positioning | totalsize | Total size of DTB (including all blocks and gaps), determines file read range |
off_dt_struct | Offset of the structure block (stores hardware nodes/properties), core entry point for parsing hardware description | |
off_dt_strings | Offset of the strings block (stores property names), used with indices in the structure block to retrieve property names | |
off_mem_rsvmap | Offset of the memory reservation block (marks non-allocatable memory regions), avoids kernel memory conflicts | |
size_dt_strings | Length of the strings block, used to read the complete set of property names | |
size_dt_struct | Length of the structure block, used to read the complete hardware description data | |
| Version Compatibility | version | Format version of the current DTB, determines parsing logic |
last_comp_version | Minimum compatible version for backward compatibility, ensures compatibility across kernel versions | |
| Hardware Association | boot_cpuid_phys | The physical ID of the boot CPU, corresponding to the CPU node in the device treeregattribute, used for core identification in multi-core systems |
Memory Reserved Block
A Memory Reserved Block is a list of protected and reserved physical memory regions for client programs.
These reserved regionsshould not be used for general memory allocation, but rather to protect important data structures from being overwritten by client programs 。
- Reserved Region List: The memory reserved block is a list consisting of a set of 64-bit big-endian integer pairs. Each pair of integers corresponds to a reserved memory region, containing the physical address and the size of the region (in bytes). These reserved regions should not overlap with each other.
Each reserved region in the memory reserved block is represented by a 64-bit big-endian integer pair. Each pair is represented by the following C structure
1 | struct fdt_reserve_entry { |
The first integer represents the physical address of the reserved region, and the second integer represents the size of the reserved region (in bytes). Each integer is represented in 64-bit form, even on 32-bit architectures. On 32-bit CPUs, the high 32 bits of the integer are ignored.
- Purpose of Reserved Regions: Client programs should not access the reserved regions in the memory reserved block unless other information provided by the bootloader explicitly indicates that access is allowed. The bootloader may use specific methods to indicate that client programs can access parts of the reserved memory. The bootloader may describe the specific purposes of the reserved memory in documentation, optional extensions, or platform-specific documentation.
The memory reserved block provides the device tree with the ability to protect and reserve physical memory regions. It ensures that specific memory regions are not modified or used during the execution of client programs. This ensures that the bootloader and other critical components can access specific parts of the reserved memory when needed, and protects critical data structures from accidental modification.
Structure Block
The structure block is the part of the device tree that describes the structure and content of the device tree itself. Itconsists of a sequence of tokens with data, organized in a linear tree structure。
Token Types
The tokens in the structure block are divided into five types, each used for a different purpose.
FDT_BEGIN_NODE (0x00000001)
FDT_BEGIN_NODETokenIndicates the start of a node. It is followed by the node’s unit name as additional data. The node name is stored as a null-terminated string and may include a unit address. After the node name, zero bytes may be needed for alignment, followed by the next token, which can be any token exceptFDT_END.
FDT_END_NODE (0x00000002)
FDT_END_NODETokenIndicates the end of a node. This token has no additional data, and is immediately followed by the next token, which can be any token exceptFDT_PROP.
FDT_PROP (0x00000003)
FDT_PROPTokenIndicates the start of a property in the device tree. It is followed by additional data describing the property, which first consists of the length and name of the property, represented as the following C structure
1 | struct { |
The length indicates the byte length of the property value, and the name offset points to the location in the strings block where the property name is stored.
After this structure, the property value is given as a byte string. The property value may need to be padded with zero bytes for alignment, followed by the next token, which can be any token exceptFDT_ENDany token other than
FDT_NOP (0x00000004)
FDT_NOPThe token can be ignored by programs parsing the device tree. This token has no additional data, and is immediately followed by the next token, which can be any valid token. UsingFDT_NOPtoken can override a property or node definition in the tree, thereby removing it from the tree without needing to move other parts of the device tree blob.
Tree Structure
The structure of the device tree is represented as a linear tree. Each node begins with aFDT_BEGIN_NODEtoken and ends with aFDT_END_NODEtoken.
The properties and child nodes of a node are represented beforeFDT_END_NODE, so the child node’sFDT_BEGIN_NODEandFDT_END_NODEtokens are nested within the parent node’s tokens.
End of Structure Block
The structure block ends with a singleFDT_ENDmarker. This marker has no additional data; it is located at the end of the structure block and is the last marker in the structure block.FDT_ENDThe bytes after the marker should be at the starting offset of the structure block, which equals the value of thesize_dt_structfield in the device tree blob header.
Strings Block
The strings block is used to store all property names used in the device tree. It consists of a series of null-terminated strings that are simply concatenated together within the strings block.
- String Concatenation
Strings in the strings blockare concatenated with a null character (\0) as the terminator. This means each string ends with a null character, and the next string immediately follows the end of the previous one. This concatenation method forms all strings in the strings block into a continuous sequence of characters.
- Offset Reference
In the structure block,property names reference the corresponding strings in the strings block via offsets. An offset is an unsigned integer value that indicates the position of a string within the strings block. By using offset references, the device tree saves space and becomes more flexible when property names change, as only the offsets need to be updated without modifying the property references in the structure block.
- Alignment Constraints
**The strings block has no alignment constraints, meaning it can appear at any offset within the device tree blob.**This makes the position of the strings block flexible within the device tree blob, allowing adjustments as needed without affecting the parsing and processing of the device tree.
The strings block is the part of the device tree used to store property names. It is composed of concatenated strings and is referenced by offsets in the structure block. The flexible position of the strings block makes the device tree representation more compact and extensible.
dtb expanded into device_node

- U-Boot loading
U-Boot (Universal Bootloader) is a commonly used open-source boot loader for booting embedded systems. During system startup, U-Boot loads theboot.imgbinary files of the kernel and device tree into specific addresses of system memory.
- Kernel initialization
After U-Boot loads the binary files of the kernel and device tree into specific addresses of system memory, control is transferred to the kernel. During kernel initialization, the device tree binary file is parsed and expanded into data structures recognizable by the kernel, enabling the kernel to correctly initialize and manage hardware resources.
- Device tree expansion
Device tree expansion refers to the process of parsing the device tree binary file into device nodes (struct device_node) within the kernel. The kernel reads the content of the device tree binary file and, based on the device tree’s description, constructs device tree data structures such as device nodes, interrupt controllers, registers, clocks, etc. These device tree data structures are used at runtime to manage and configure hardware resources.
struct device_nodeDefined as follows
1 | // include/linux/of.h |
Source code analysis of the DTB parsing process
init/main.c
In the kernelstart_kernel()Called insetup_arch(&command_line);Perform architecture-specific initialization
1 | // init/main.c |
arch/arm64/kernel/setup.c
While insetup_arch()the call at line 21 below insetup_machine_fdt(__fdt_pointer);Set up the machine’s FDT (Flattened Device Tree)
1 | // arch/arm64/kernel/setup.c |
setup_machine_fdt(__fdt_pointer)
__fdt_pointeris the address where the dtb binary file is loaded into memory, which is passed by the bootloader to the kernel via the x0 register during startup
arch/arm64/kernel/head.S
1 | // arch/arm64/kernel/head.S |
arch/arm64/kernel/setup.c
1 | // arch/arm64/kernel/setup.c |
Line 13 aboveearly_init_dt_scanThe function validates the compatibility and integrity of the device tree. It may check for consistency markers, version information, and the presence of required nodes and attributes in the device tree. If validation fails, the function returns false. The function is as follows:
1 | // drivers/of/fdt.c |
early_init_dt_scanFirst calledearly_init_dt_verifyValidate the device tree, verifying its compatibility and integrity
1 | // drivers/of/fdt.c |
Finallyearly_init_dt_scanCalledearly_init_dt_scan_nodesScan the device tree nodes
1 | // drivers/of/fdt.c |
functionearly_init_dt_scan_nodesis declared as__init, whichindicates that it is called during the kernel initialization phase,and is no longer needed after initialization is complete. The purpose of this function is to scan device tree nodes in the early stage and perform some initialization operations.
The function mainly callsof_scan_flat_dtfunction, which is used to scan the flat device tree. The flat device tree is a
unflatten_device_tree()
This function is used to parse the device tree, converting the compact device tree data structure into a tree-structured device tree
1 | // drivers/of/fdt.c |
This function is mainly used to parse the device tree and store the parsed device tree in a global variableof_root.
The function first calls the__unflatten_device_treefunction to perform the device tree parsing operation. The parsed device tree will be stored using theof_rootpointer.
Next, the function calls theof_alias_scanfunction. This function is used to scan the/chosenand/aliasesnodes in the device tree and allocate memory for them. This way, other parts of the code can access these nodes through global variables.
Finally, the function calls theunittest_unflatten_overlay_basefunction to run unit tests for the device tree.
__unflatten_device_tree
1 | //drivers/of/fdt.c |
The focus of this function is on two scans of the device tree. The purpose of the first scan is to calculate the memory size required for unflattening the device tree.
Line 46:size = unflatten_dt_nodes(blob, NULL, dad, NULL);The function recursively traverses the device tree data block and calculates the memory size required for unflattening the device tree. It takes four parameters:
- blob (pointer to the device tree data block)
- start (starting address of the current node, initially NULL)
- dad (parent node pointer, and
unflatten_device_treeNULL is passed in the function) - mynodes (pointer to an array for storing node pointers, initially NULL).
After the first scan is completed,unflatten_dt_nodesthe function will
Line 65: called againunflatten_dt_nodes(blob, mem, dad, mynodes);The function performs a second scan. Through this process,
unflatten_dt_nodes()
1 | // drivers/of/fdt.c |
fdt_next_node()The function is used to traverse the nodes of the device tree.
Start with offset 0, and execute the loop as long as the offset is greater than or equal to 0 and the depth is greater than or equal to the initial depth.
Each iteration of the loop processes a device tree node. In each iteration, first check if the depth exceeds the maximum depthFDT_MAX_DEPTHIf it does, skip the node.
If not enabledCONFIG_OF_KOBJand the node is unavailable (determined by theof_fdt_device_is_available()function), skip the node.
Then call thepopulate_node()function to populate node information and add child nodes to the device node arraynps.populate_node()The function definition is as follows
populate_node
This function parses the attributes of a device node and allocates memory as needed to store the attribute values.
1 | // drivers/of/fdt.c |
Inpopulate_nodefunction, first callunflatten_dt_allocfunction to allocate memory for the device node.
The allocated memory size issizeof(struct device_node) + alloclbytes, and use__alignof__(struct device_node)alignment, then callpopulate_propertiesfunction to fill in the attribute information of the device node.
Here
np->name = of_get_property(np, "name", NULL);get the name attribute of the device node and store it instruct device_node’snameattribute
device_node to platform_device
In the platform bus model, the device part is described usingplatform_devicestructure to describe hardware resources, so the kernel will ultimately convert the kernel-recognizeddevice_nodetree conversionplatform_device, but
not all device_node will be converted to platform_device
only those that meet the requirements will be converted toplatform_device, converted toplatform_devicenodes can be viewed under/sys/bus/platform/devices.
Conversion rules
The rules are as follows:
- Traverse the child nodes under the root node that contain the compatible attribute, and for each child node, create a corresponding
platform_device。 - Traverse the nodes under the root node whose compatible attribute is
simple-bus、simple-mfdorisanodes and their child nodes. If their child nodes contain a compatible attribute value, a correspondingplatform_device。 - Check whether the node’s compatible attribute contains
armorprimecell. If so, do not convert this node toplatform_device, but recognize it as an AMBA device.
For Rule 2:
Some nodes, although themselves “buses” or “containers”, do not directly correspond to hardware devices but are used to organize child devices. The compatible values of these nodes are typically:
simple-bus: Generic simple bus (e.g., ordinary memory-mapped buses other than AMBA APB/AHB)simple-mfd: Multi-Function Device containerisa: ISA bus (legacy PC)
For such nodes, they themselves usually do not createplatform_device, but will recursively traverse all their child nodes, and as long as a child node has a compatible property, createplatform_device
Purpose: To support the “bus nesting” structure in the device tree. For example, peripherals inside an SoC are attached under a simple-bus node.
For Rule 3:
If a node’s compatible string containsarm,xxxorprimecell(e.g.,arm,pl011、arm,pl081), indicating it is an ARM PrimeCell peripheral, belonging to AMBA bus devices (APB/AHB). Such devices:
- are not registered as
platform_device, but are handled by the AMBA bus subsystem (amba_bus_type) specifically - using the
amba_devicestructure, rather than theplatform_device
Reason: ARM PrimeCell devices have standard register layouts (e.g., CID/PID), and the AMBA subsystem automatically detects and verifies them, which is more secure and efficient than genericplatform_device.
Example
Example 1:
1 | /dts-v1/; |
In the device tree above, there are a total ofchosen、cpu1: cpu@1、aliases、node1、node2、gpio@22020101these six nodes. Among them, the first five nodes do not have a compatible property, so they will not be converted toplatform_device, while the lastgpio@22020101node meets rule one, being under the root node and having a compatible property, so it will ultimately be converted toplatform_device。
Example 2:
1 | /dts-v1/; |
Here, in thenode1node, a compatible attribute is added, but the value of this compatible attribute issimple-bus, we need to continue looking at its child nodes. The child nodegpio@22020102does not have a compatible attribute value, so thenode1node will not be converted.
Example 3:
1 | /dts-v1/; |
Here, in the child nodegpio@22020102of node1, a compatible attribute is added. The compatible attribute value of node1 issimple-bus, and then we need to continue looking at its child nodes. The child nodegpio@22020102has a compatible attribute value of gpio, so thegpio@22020102node will be converted toplatform_device
Example 4:
1 | /dts-v1/; |
The compatible value of the amba node issimple-bus, and it will not be converted toplatform_device, but instead serves as a parent node for organizing other devices, so we need to examine its child nodes.
dmac_peri: dma-controller@ff250000Node: The compatible property of this node containsarm,p1330andarm,primecell, according to rule 3, this node will not be converted toplatform_device, but is recognized as an AMBA device.
dmac_bus: dma-controller@ff600000Node: The compatible property of this node containsarm,p1330andarm,primecell, according to rule 3, this node will not be converted toplatform_device, but is recognized as an AMBA device.
Source Code Analysis
of_platform_default_populate_init()
First, let’s look atof_platform_default_populate_init, which usesarch_initcall_sync(of_platform_default_populate_init);to register this function for invocation during the boot phase
1 | // drivers/of/platform.c |
arch_initcall_syncis a function in the Linux kernel used to execute architecture-specific initialization functions during kernel initialization. It belongs to the kernel’s initialization call mechanism, ensuring that architecture-specific initialization functions are called at the appropriate time during system startup.
During the initialization of the Linux kernel, various subsystems and architectures register their own initialization functions. These initialization functions are responsible for completing initialization tasks specific to a subsystem or architecture, such as initializing hardware devices, registering interrupt handlers, setting up memory mappings, etc. And arch_initcall_syncfunction is used to call initialization functions related to the current architecture.
When the kernel starts, it callsrest_init()function to start the initialization process. During initialization,arch_initcall_syncfunction is called to ensure that all initialization functions related to the current architecture are executed in the correct order. This ensures that architecture-specific initialization tasks are completed correctly during startup.
And of_platform_default_populate_initfunction is to automatically parse the device tree during kernel initialization,**and create corresponding devices based on the device nodes in the device treeplatform_devicestructure. It will traverse the device nodes in the device tree and create a correspondingplatform_devicestructure, then register it into the kernel, enabling device drivers to recognize and operate these devices.
of_platform_default_populate_initThe function ultimately callsof_platform_default_populatepopulate other devices
1 | // drivers/of/platform.c |
The purpose of this function is to callof_platform_populatefunction to populate the platform devices in the device tree, and uses the default device match tableof_default_bus_match_table, the content of the device match table is as follows:
1 | // drivers/of/platform.c |
The above device match table corresponds to rule 2:
Traverse nodes under the root node that contain compatible attribute as simple-bus、simple-mfd or isa nodes and their child nodes。If their child nodes contain compatible attribute value
The function will automatically match the corresponding device driver based on the attributes of the device tree node and populate the kernel’s platform device list.
of_platform_populate()
of_platform_populateThe function is defined as follows:
1 | // drivers/of/platform.c |
of_platform_populateBy callingrc = of_platform_bus_create(child, matches, lookup, parent, true);Createplatform_device。
of_platform_bus_create()
of_platform_bus_createDefined as follows:
1 | // drivers/of/platform.c |
of_platform_bus_createBy callingof_match_node(of_skipped_node_table, bus)match the nodes to be skipped,of_skipped_node_tabledefined as follows
1 | static const struct of_device_id of_skipped_node_table[] = { |
operating-points-v2
This is a Device Tree Binding used to describe the Operating Performance Points (OPP) of a CPU or device, such as different frequency/voltage combinations. It is not a hardware device, but a data table (describing performance states) that typically appears as a child node under CPU, GPU, or SoC device nodes.
1 | cpu@0 { |
Subsequentlyof_platform_bus_createcall theof_platform_device_create_pdatafunction to create a platform device and assign it to the variable dev. Then, check whether the device node bus matches the given match tablematchesmatches. If the platform device creation fails or the device node does not match, return 0.
Finallyof_platform_bus_createusefor_each_child_of_node(bus, child), traverse each child node child of the device node bus, and recursively call theof_platform_bus_createfunction to create platform devices for the child nodes.
of_platform_device_create_pdata()
of_platform_device_create_pdatadefined as follows
1 | // drivers/of/platform.c |
of_platform_device_create_pdataFunction callof_device_allocAllocate a platform device structure, and pass the device node pointer, device identifier, and parent device pointer to it. If allocation fails, jump toerr_clear_flaglabel for error handling.
Lines 29 to 34of_platform_device_create_pdataSet some properties of the platform device:
- It sets the
coherent_dma_maskproperty to a 32-bit DMA bitmask - Check if the
dma_maskproperty is NULL. Ifdma_maskis NULL, point it tocoherent_dma_mask。 - Set the bus type of the platform device to
platform_bus_type, and store the platform data pointer inplatform_datain the attribute. - Call
of_msi_configureandof_reserved_mem_device_init_by_idxto configure the device’s MSI and reserved memory information
of_device_allocThe function is defined as follows
of_device_alloc()
1 | /** |
It can be seen that it callsplatform_device_alloc("", PLATFORM_DEVID_NONE)createdplatform_device’snameis empty
platform under the device tree_device and platform_driver matching
of_match_table
In the platform bus model, only when one of the following three conditions is met will it correctly match and load the probe initialization function:
platform_driver.driver.of_match_tablematches the value in the compatible property of the device treeplatform_driver.id_tablematches the value in the compatible property of the device treeplatform_driver.driver.nameandplatform_device.namematch
For the priority of matching, from the followingplatform_matchfunction, it can be seen:
- Device tree matching is used first, i.e.,
platform_driver.driver.of_match_table - then ACPI matching
- then
platform_driverinid_tablematching. - Finally, fall back to
return (strcmp(pdev->name, drv->name) == 0);, i.e., matchingplatform_device.nameandplatform_driver.driver.name, and this name is a legacy field, which after device tree parsing is converted fromstruct device_nodetostruct platform_devicewhen converting thisnameis set to empty (of_device_alloc), meaning the one generated through the device treestruct platform_devicewill not use this field.
However,platform_driver.driver.namemust be set, otherwise a kernel panic will occur, becausestrcmp(pdev->name, drv->name)this line of code usesstrcmp, anddrv->namean error will be reported when it is NULL.
1 | // drivers/base/platform.c |
Andof_driver_match_devicethe function definition is as follows:
1 | // include/linux/of_device.h |
of_driver_match_devicecalls theof_match_devicefunction, with the first parameter passed asdrv->of_match_table, and this function is defined as follows
1 | // drivers/of/device.c |
of_match_devicewhich in turn calls theof_match_nodefunction,of_match_nodeUsing a spin lock to__of_match_nodelock,__of_match_nodeis the actual matching function
1 | // drivers/of/base.c |
__of_match_nodecall__of_device_is_compatiblematch, with the parameters passed in order:
struct device_node *nodestruct of_device_id *matches’scompatible,type,name
1 | /** |
It can be seen thatof_device_id’scompatibleattribute has the highest priority, followed bytypeattribute, and finallynameattribute. The score is calculated based on these three attributesscore, the higher the score, the higher the match degree.
Andplatform_driverstruct nested within thedriverstruct’sof_match_tableThe attribute is a pointer toconst struct of_device_ida structure, used to describe the matching rules between device tree nodes and drivers.
1 | // include/linux/mod_devicetable.h |
struct of_device_id
The last element of the array must be an empty structure.,To mark the end of the array.
Example:
1 | static const struct of_device_id my_driver_match[] = { |
Example
| Device Tree Level | Device Tree Name | Device Tree Name |
|---|---|---|
| Top-Level Device Tree | rk3568-evb1-ddr4-v10-linux.dts | rk3568-evb1-ddr4-v10-linux.dts |
| Second-Level Device Tree | rk3568-evb1-ddr4-v10.dtsi | rk3568-linux.dtsi |
| Third-Level Device Tree | rk3568.dtsi rk3568-evb.dtsi topeet_screen_choose.dtsi topeet_rk3568_lcds.dtsi |
rk3568-evb1-ddr4-v10-linux.dtsis the top-level device tree
Add:
rk3568-evb1-ddr4-v10-linux.dts
1 | // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
driver
1 |
|
of operations
get device tree node
of_find_by_name()
of_find_node_by_nameis a function in the Linux kernel used to find a device tree node by its name
| Item | Description |
|---|---|
| Function Definition | struct device_node *of_find_node_by_name(struct device_node *from, const char *name); |
| Header File | #include <linux/of.h> |
| Parameter from | Starting search node: NULL: Start searching from the device tree root node Non-NULL: Continue searching for nodes with the same name after this node |
| Parameter name | The name of the device tree node to search for (node name, not compatible) |
| Function | Search for a node with a matching name in the device tree and return the correspondingdevice_nodestructure pointer |
| Return Value | Found: Returns the matching node’sstruct device_node *; Not found: returnNULL |
of_find_node_by_path()
| Item | Description |
|---|---|
| Function Definition | struct device_node *of_find_node_by_path(const char *path); |
| Header File | #include <linux/of.h> |
| Parameter path | Absolute path string of the device tree node, for example:/soc/gpio@ff720000、/topeet/myLed |
| Function | According to the device tree node’sabsolute pathfind the corresponding node, return the matchingstruct device_nodestructure pointer |
| Return Value | Success: returns a pointer tostruct device_nodepointer;Failure: returns NULL |
of_get_parent()
| item | Description |
|---|---|
| Function Definition | struct device_node *of_get_parent(const struct device_node *node); |
| Header File | #include <linux/of.h> |
| Parameter node | Device tree node pointer of the node whose parent is to be obtained |
| Function | Get the parent node of the specified node and return its correspondingdevice_nodestructure pointer |
| Return Value | Success: returns the parent node’sstruct device_nodepointer;Failure or no parent node: returns NULL |
of_get_next_child()
| item | Description |
|---|---|
| Function Definition | struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev); |
| Header File | #include <linux/of.h> |
| Parameter node | Pointer to the current device tree node, used to specify the parent node whose children are to be traversed |
| Parameter prev | Pointer to the previous child node; if it isNULL, returns the first child node |
| Function | Traverse the child nodes of a device tree node; return all child nodes of the specified node one by one |
| Return Value | Success: returns a pointer to the next child nodestruct device_nodepointer;No more child nodes: returns NULL |
for_each_child_of_node()
| NULL | Description |
|---|---|
| Macro Definition | for_each_child_of_node(parent, child) |
| Header File | #include <linux/of.h> |
| Parameter parent | Parent device tree node pointer (struct device_node *) |
| Parameter child | Child node pointer variable (struct device_node *), used for iteration |
| Function | Iterate through all child nodes of the specified parent node |
| Return Value | None (macro, used in for loop) |
Example:
1 | struct device_node *np = pdev->dev.of_node; |
of_find_compatible_node()
| Project | Description |
|---|---|
| Function Definition | struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible); |
| Header File | #include <linux/of.h> |
| Parameter from | Specifies the node to start searching from; if it isNULL, the search starts from the root node of the device tree |
| Parameter type | The device type string to match, can be used to match the node’sdevice_typeproperty; usually can be set toNULL |
| Parameter compatible | Thecompatibleproperty string to match in the device tree |
| Function | Finds the first node in the device tree that matches the specified compatible string; the return value can be used to continue searching for the next matching node |
| Return Value | Success: returns the matching nodestruct device_node*;Failure or non-existence: returns NULL |
of_find_matching_node_and_match()
| Item | Description |
|---|---|
| Function Definition | struct device_node *of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match); |
| Header File | #include <linux/of.h> |
| Parameter from | Specifies the node from which to start searching: Pass NULLindicates searching from the root node of the device tree;Passing the previously returned node allows continuing to search for the next matching node |
| Parameter matches | Points to a of_device_id[]match table, which contains entries for matching device tree nodescompatibleortypecondition |
| parameter match | output parameter, used to return the of_device_identry pointer; can be NULL |
| function | in the device tree, according tomatchesmatch table to find nodes that meet the conditions, and can return the corresponding match |
| return value | success: returns the matchedstruct device_node *;failure: returns NULL |
example:
1 |
|
first, define aof_device_idmatch tablemy_match_table, which contains a compatibility string ofvendor,devicematching items. Then, we useof_find_matching_node_and_matchfunction to find the matching node starting from the root node.
Example
Device Tree:
1 | /{ |
Driver:
1 |
|
Test:
1 | root@topeet:/root# insmod of_api_test.ko |
Get device tree property
of_find_property()
| Project | Description |
|---|---|
| Function Definition | struct property *of_find_property(const struct device_node *np, const char *name, int *lenp); |
| Header File | #include <linux/of.h> |
| Parameter np | Device tree node to search for the property (struct device_nodepointer) |
| Parameter name | The property name string to search for, e.g.,compatible、reg、status |
| Parameter lenp | Pointer tointpointer of type, used to return the byte length of the property value;If the length is not needed, NULL can be passed |
| Function | Under the specified nodenpsearch for the property namednameattributes, and can return the length of the attribute value |
| Return value | Success: returns a pointer to the attribute structurestruct property *;Failure (attribute not found or invalid parameter): returns NULL |
of_property_count_elems_of_size()
| Item | Description |
|---|---|
| Function definition | int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size); |
| Header file | #include <linux/of.h> |
| Parameter np | Device tree node pointer (struct device_node *), indicating the node from which to read the attribute |
| Parameter propname | Attribute name string, e.g.,reg、gpiosetc. |
| Parameter elem_size | Size of a single element (in bytes), for example:sizeof(u32)→ Number of 32-bit integers in the computed attribute |
| Function | Count the number of elements in the specified attribute (divided by the given element size) |
| Return Value | Success: returns the number of elements in the attribute Attribute does not exist or has no content: returns0 Other errors: returnsnegative error code(for example, -EINVAL) |
of_property_read_u32_index()
| Item | Description |
|---|---|
| Function Definition | int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value); |
| Header File | #include <linux/of.h> |
| Parameter np | Device tree node pointer (struct device_node *), indicating the node whose property is to be read |
| Parameter propname | Property name string, e.g.,reg、gpiosetc. |
| Parameter index | Index of the property element (starting from 0), specifying which element to read |
| Parameter out_value | Pointer tou32a variable of type, used to store the read value |
| Function | Retrieve a 32-bit unsigned integer (u32) at the specified index from the given property |
| Return value | On success: returns0,out_valuestores the read valueFailure: returns a negative error code (attribute does not exist or read failure) |
of_property_read_u64_index()
| Item | Description |
|---|---|
| Function Definition | static inline int of_property_read_u64_index(const struct device_node *np, const char *propname, u32 index, u64 *out_value); |
| Header File | #include <linux/of.h> |
| Parameter np | Device tree node pointer (struct device_node *), indicating the node whose attribute is to be read |
| Parameter propname | Attribute name string, e.g.,reg、gpios, etc. |
| Parameter index | Index of the attribute element (starting from 0), specifying which element to read |
| Parameter out_value | Pointer tou64Pointer to a type variable, used to store the read value |
| Function | Get a 64-bit unsigned integer (u64) at the specified index from the given property |
| Return value | Success: returns0,out_valueSave the read valueFailure: returns a negative error code, e.g., property does not exist or read failed |
of_property_read_variable_u32_array()
| Item | Description |
|---|---|
| Function definition | int of_property_read_variable_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t SZ_min, size_t SZ_max); |
| Header file | #include <linux/of.h> |
| Parameter np | Device tree node pointer (struct device_node *), indicating the node whose property is to be read |
| Parameter propname | Attribute name string, e.g.,reg、gpiosetc. |
| Parameter out_values | Pointer tou32a pointer to an array of type, used to store the read values |
| Parameter SZ_min | Specifies the minimum number of elements in the array |
| Parameter SZ_max | Specifies the maximum number of elements in the array |
| Function | Reads a variable-lengthu32array from the specified attribute and stores it intoout_values |
| Return value | Success: Returns the actual number of array elements read Failure: Returns a negative error code, e.g., attribute does not exist or read failed |
- Read a variable-length u8 array from the specified attribute
1 | int of_property_read_variable_u8_array(const struct device_node *np, const char *propname, u8 *out_values, |
- Read a variable-length u16 array from the specified attribute
1 | int of_property_read_variable_u16_array(const struct device_node *np, const char *propname, u16 |
- Read a variable-length u64 array from the specified attribute
1 | int of_property_read_variable_u64_array(const struct device_node *np, const char *propname, u64 |
of_property_read_string()
| Item | Description |
|---|---|
| Function Definition | static inline int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string); |
| Header File | #include <linux/of.h> |
| Parameter np | Device tree node pointer (struct device_node *), indicating the node from which to read the attribute |
| Parameter propname | Attribute name string, e.g.,compatible、statusetc. |
| Parameter out_string | Pointer to a string pointer, used to store the read string |
| Function | Read a string value from the specified attribute |
| Return Value | Success: returns 0 Failure: returns a negative error code, e.g., attribute does not exist or read failed |
of_property_read_bool()
| Item | Description |
|---|---|
| Function Definition | static inline bool of_property_read_bool(const struct device_node *np, const char *propname); |
| Header File | #include <linux/of.h> |
| Parameter np | Device tree node pointer (struct device_node *), indicating the node from which to read the attribute |
| Parameter propname | Attribute name string, e.g.,gpio-active-low、enableetc. |
| Function | Determine whether a specified node has a boolean type attribute |
| Return value | If the attribute exists: returntrue If it does not exist: returnfalse |
Example
Device tree:
1 | /{ |
Driver:
1 |
|
Test:
1 | root@topeet:/root# insmod of_api_property_test.ko |
ranges attribute
platform_get_Prerequisite for resource to obtain device tree resources
Since the device tree is converted into platform devices during system startup, we can directly use in the driver theplatform_get_resourcefunction to directly obtainplatform_deviceresources
Example:
Device tree:
1 | /{ |
Driver:
1 |
|
This method will fail to load. The reason isplatform_get_resourceReturn NULL
1 | // drivers/base/platform.c |
There are two possibilities for returning NULL: one is that the above for loop is not entered and NULL is returned directly; the other is that the for loop is entered, but the type matching is incorrect, and NULL is returned after breaking out of the for loop.
The type here must match, so we need to find out why the for loop was not entered. There is only one possibility, which isdev->num_resources 为 0。
Let’s look atof_platform_device_create_pdatathis function
1 | // drivers/of/platform.c |
function callof_device_allocIt is this function that allocates a platform device structure and passes the device node pointer, device identifier, and parent device pointer to it.resource.num
of_device_allocThe function is as follows:
1 | // drivers/of/platform.c |
At line 32dev->num_resources = num_reg + num_irq;That is, the number of reg and the number of irq. Since no interrupt-related attributes were added in the device tree,num_irqis 0, andnum_regis obtained by line 20 through this functionof_address_to_resource(np, num_reg, &temp_res) == 0obtained by loop countingnum_rg
1 | /* count the io and irq resources */ |
of_address_to_resourceThe function definition is as follows:
1 | // drivers/of/address.c |
Line 18 retrieves the address, size, and type of the reg attribute. Since the reg attribute already exists in the device tree, it will return correctly here.
Line 23 reads thereg-namesattribute. Since this attribute is not defined in the device tree, the function will have no effect.
The ultimately decisive function is the returned__of_address_to_resourcefunction. Jumping to its definition is as follows:
1 | // drivers/of/address.c |
The flags of the reg attribute areIORESOURCE_MEM, so it will execute line 9’sof_translate_addressfunction, jump to this function, whose definition is as follows
1 | // drivers/of/address.c |
The key point of this function is in line 7. The above function is actually__of_translate_addressa wrapper of the function, where the third parameter passed inranges is the focus we need to pay attention to. Continue to jump to the definition of this function, the specific content is as follows:
1 | // drivers/of/address.c |
Lines 34 to 37, get the parent node and the matching bus type
Line 40, getaddress-cellandsize-cellsand store them in int variables na and ns respectively
Line 52 is a for loop, in which line 92 uses theof_translate_onefunction for conversion, whererpropparameter indicates the resource attribute to be converted, and its value is the passed-in string"ranges", then we continue to jump to this function, the specific content of which is as follows:
1 | // drivers/of/address.c |
In line 30 of this function, use theof_get_propertyfunction to get"ranges"attribute, but since this attribute is not present in the device tree node we added, the ranges value here is NULL, the condition on line 34 holds true, and thus it returns 1.
Next, based on this return value, continue analyzing the parent function:
of_translate_oneAfter the function returns 1, the parent level’s_of_translate_addressreturn value isOF_BAD_ADDR;
The next higher level’sof_translate_addressreturn value is alsoOF_BAD_ADDR;
Continue searching upward__of_address_to_resourcefunction will return-EINVAL;
of_address_to_resourcereturns-EINVAL, sonum_regis 0;
At this point, regarding why theplatform_get_resourcefunction fails to obtain resources, the issue is found, simply because the parent node in its device tree does not have this attribute namedranges, so just add therangesattribute.
Introduction to the ranges property
rangesThe property is aproperty used to describe address mapping relationships between devices.It is used in the Device Tree todescribe how the child device’s address space maps to the parent device’s address space. The Device Tree is a hardware description language used to describe hardware components and their interconnections in embedded systems.
Each device node in the Device Tree can have arangesproperty, which contains address mapping information.
The common format is as follows:
1 | ranges = <child-bus-address parent-bus-address length>; |
or
1 | ranges; |
- child-bus-address
The starting address of the child device’s address space.
It specifies the location of the child device within the parent device’s address space. The specific word length is determined by therangesnode’s#address-cellsproperty.
- parent-bus-address
The starting address of the parent device’s address space.
It specifies the address range in the parent device used for mapping the child device. The specific word length is determined by therangesparent node’s#address-cellsattribute.
- length
The size of the mapping. It specifies the length of the child device’s address space within the parent device’s address space. The specific word length is determined by therangesparent node’s#size-cellsattribute.
When therangesattribute’s value is empty, it indicates that the child device’s address space and the parent device’s address space have an identical mapping, i.e., a 1:1 mapping. This is typically used to describe memory regions where the child device and parent device share the same address range.
When therangesattribute’s value is not empty, the child device’s address space is mapped to the parent device’s address space according to the specified mapping rules. The specific mapping rules depend on the device tree structure and the device’s specific requirements.
Example
1 | /dts-v1/; |
In theexternal-busnode, the#address-cellsattribute value of 2 indicates thatchild-bus-addressis represented by two values, namely 0 and 0, and the parent node’s#address-cellsattribute value and#size-cellsattribute value is 1, indicatingparent-bus-addressandlengthare both represented by 1, that is0x10100000and0x10000, thisrangesvalue indicates mapping the child address space (0x0-0xFFFF) to the parent address space0x10100000 - 0x1010FFFF, the example here is with parameterrangesattribute mapping, without parameter therangesattribute is a 1:1 mapping, which is simpler, so no example is given here.
In embedded systems, different devices may be connected to the same bus or bus controller, and they need to be correctly mapped in the physical address space for data exchange and communication.
For example, a device may be connected to the main processor or other devices via a bus, and the physical address ranges of these devices may differ. The ranges attribute is used to describe this address mapping relationship.
Device Classification
Memory-Mapped Devices
Memory-mapped devices refer todevices that can be directly accessed via memory addresses. A portion of the physical address space of such devices is mapped into the system’s memory address space, allowing the CPU to communicate with and control the device by reading and writing memory addresses.
- Characteristics:
- Direct Access: Memory-mapped devices can be directly accessed by the CPU, similar to accessing data in memory. This direct access method provides high-speed data transfer and low-latency device operations.
- Memory Mapping: The device’s registers, buffers, and other resources are mapped into the system’s memory address space, using memory read and write operations to communicate with the device.
- Read and Write Operations: The CPU can exchange data and perform control operations with the device by reading and writing to the mapped memory addresses.
An example of a device tree for a memory-mapped device is shown below:
1 | /dts-v1/; |
Non-Memory-Mapped Devices
Non-memory-mapped devices refer to devices that cannot be directly accessed via memory addresses. Such devices may communicate with the CPU through other means, such as I/O ports, dedicated buses, or specific communication protocols.
- Characteristics:
- Non-Memory Access: Non-memory-mapped devices cannot be accessed directly through memory addresses like memory-mapped devices. They may use separate I/O ports or dedicated buses for communication.
- Specific Interface: Devices typically use specific interfaces and protocols to communicate with and be controlled by the CPU, such as SPI, I2C, UART, etc.
- Driver: Non-memory-mapped devices usually require specific device drivers to achieve communication and control with the CPU.
In the device tree, an example of a device tree for a non-memory-mapped device is shown below:
1 | /dts-v1/; |
Mapping Address Calculation
Next, using theethernet@0node in the device tree of the non-memory-mapped device listed above as an example, calculate the mapping address of the network card device.
First, find theethernet@0node where it is located, and check its reg property. In the given device tree fragment,ethernet@0the reg property of<0 0 0x1000>is . In the root node,#address-cellsthe value of is 1, indicating that the address consists of one cell.
Next, according torangesattribute for address mapping calculation. In theexternal-busnode’srangesattribute, there are three mapping entries:
- The first mapping entry is
0 0 0x10100000 0x10000, indicating the external bus address range is0x10100000to0x1010FFFF. The first value of this mapping entry is 0, indicating it is associated with theexternal-busnode’s first child node (ethernet@0,0). - The second mapping entry:
1 0 0x10160000 0x10000, indicating the external bus address range is0x10160000to0x1016FFFF. The first value of this mapping entry is 1, indicating it is associated with theexternal-busnode’s second child node (i2c@1,0). - The third mapping entry:
2 0 0x30000000 0x30000000, indicating that the address range of the external bus is0x30000000to0x5FFFFFFF. The first value of this mapping entry is 2, indicating that it is associated withexternal-busthe third child node of the node.
Sinceethernet@0is associated withexternal-busthe first child node of , and its reg property is<0 0 0x1000>, we can perform the following calculation:
ethernet@0The physical start address of = the start value of the external bus address = 0x10100000
ethernet@0The physical end address of = the start value of the external bus address + (ethernet@0the second value of the reg property of - 1) =$$ 0x10100000 + 0xFFF = 0x10100FFF $$
Therefore,ethernet@0the physical address range of is0x10100000 - 0x10100FFF,
The concept of named resources
When a driver expects a resource list of a certain type, since the person writing the device tree for the development board is usually not the same person writing the driver, there is no guarantee that the list is ordered in the way the driver expects. For example, a driver might expect its device node to have 2 IRQ lines, one for Tx events at index 0 and another for Rx at index 1.
If this order is not satisfied, the driver may exhibit abnormal behavior. To avoid such mismatches, the concept of named resources (clock, irq, dma, reg, etc.) was introduced. It consists of defining resource lists and naming them, so that regardless of the index, a given name will always match the resource.
The corresponding properties for named resources are as follows.
reg-names: List of memory regions in the reg property.clock-names: Named clocks in the clocks property.interrupt-names: Specifies a name for each interrupt in the interrupts property.dma-names: Used for the dma property.
For example
1 | fake_device { |
The code for extracting each named resource in the driver is as follows:
1 | struct resource *res1, *res2; |
Accessing the reg property
Here, the driver will take the memory region and map it into the virtual address space.
1 | struct resource *res; |
Obtaining interrupt resources
irq_of_parse_and_map()
| Item | Description |
|---|---|
| Function Definition | unsigned int irq_of_parse_and_map(struct device_node *dev, int index); |
| Header File | #include <linux/of_irq.h> |
| Parameter dev | Device tree node pointer (struct device_node *), indicating the device node whose interrupt number is to be parsed |
| Parameter index | Index number, indicating which interrupt number to retrieve from the device node’sinterruptsproperty |
| Function | Parses and maps the corresponding interrupt number from the device node’sinterruptsproperty |
| Return Value | Success: Returns an unsigned integer representing the parsed and mapped interrupt number Failure: Typically returns 0 or an invalid interrupt number (specific error handling depends on platform implementation) |
irqd_get_trigger_type()
| Item | Description |
|---|---|
| Function Definition | u32 irqd_get_trigger_type(struct irq_data *d); |
| Header File | #include <linux/irq.h> |
| Parameter d | Pointer to interrupt data structure (struct irq_data *), indicating the interrupt for which to obtain the trigger type |
| Function | Retrieve the trigger type of the corresponding interrupt from the interrupt data structure |
| Return Value | Success: Returns an unsigned 32-bit integer representing the interrupt trigger type (e.g., level-triggered or edge-triggered). It will not fail because it always returns a valid trigger type definition macro. |
irq_get_irq_data()
| Item | Description |
|---|---|
| Function Definition | struct irq_data *irq_get_irq_data(unsigned int irq); |
| Header file | #include <linux/irq.h> |
| Parameter irq | Interrupt number, indicating the interrupt number for which to obtain the interrupt data structure |
| Function | Obtain the corresponding interrupt data structure based on the interrupt number |
| Return value | Success: returns a pointer tostruct irq_datapointer;Failure: returns NULL |
gpio_to_irq()
| Item | Description |
|---|---|
| Function definition | int gpio_to_irq(unsigned int gpio); |
| Header file | #include <linux/gpio.h> |
| Parameter gpio | GPIO number, indicating the GPIO for which to obtain the interrupt number |
| Function | Get the corresponding interrupt number based on the GPIO number |
| Return Value | Success: Returns the corresponding interrupt number (integer); Failure: Returns a negative error code |
of_irq_get()
| Item | Description |
|---|---|
| Function Definition | int of_irq_get(struct device_node *dev, int index); |
| Header File | #include <linux/of_irq.h> |
| Parameter dev | Device node, indicating the device node from which to obtain the interrupt number |
| Parameter index | Index number, indicating from theinterruptsattribute to obtain which interrupt number |
| Function | from the device nodeinterruptsobtain the corresponding interrupt number from the attribute |
| Return value | Success: returns the corresponding interrupt number (integer); Failure: returns a negative error code |
platform_get_irq()
| Item | Description |
|---|---|
| Function definition | int platform_get_irq(struct platform_device *dev, unsigned int num); |
| Header file | #include <linux/platform_device.h> |
| Parameter dev | Platform device, indicating the platform device from which to obtain the interrupt number |
| Parameter num | Index number, indicating which interrupt number to obtain from the device |
| Function | Obtain the corresponding interrupt number based on the platform device and index number |
| Return Value | Success: Returns the corresponding interrupt number (integer); Failure: Returns a negative error code |
Example
1 | // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
Driver Code
1 |
|
Test:
1 | root@topeet:/root# insmod of_api_irq_test.ko |
Reference Document: dts-bindings
Documentation/devicetree/bindingsThe directory is an important directory in the Linux kernel source code, used to store the bindings documentation of the Device Tree. The Device Tree is a data structure that describes hardware platforms and device configurations, specifying device attributes, register configurations, interrupt information, etc., in a portable and hardware-independent manner.
The documents in the bindings directory provide detailed descriptions and usage examples of various devices and drivers in the device tree. These documents are very important for developers because they provide the properties and conventions needed to describe hardware and configure drivers in the device tree.
Documentation/devicetree/bindingsAn overview of some common subdirectories of the directory and their contents:
armContains bindings documents for devices and drivers related to the ARM architecture.clockContains bindings documents for clock devices and clock controllers.dmaContains bindings documents for Direct Memory Access (DMA) controllers and devices.gpioContains bindings documents for General Purpose Input/Output (GPIO) controllers and devices.i2cContains bindings documents for I2C buses and devices.interrupt-controllerContains bindings documents for interrupt controllers.mediaContains bindings documents for multimedia devices and drivers.mfdContains bindings documents for Multi-Function Device (MFD) subsystems and devices.networkingContains bindings documents for network devices and drivers.powerContains bindings documents for power management subsystems and devices.spiContains bindings documents for SPI buses and devices.usbContains bindings documents for USB controllers and devices.videoContains bindings documents for video devices and drivers.
Documents in each subdirectory are usually saved with the extension.txtor.yamland written in text or YAML format. These documents provide detailed explanations of properties in the device tree, including property syntax, optional values, and usage examples. They also describe device tree conventions and best practices to help developers correctly configure and describe hardware devices and drivers.
The YAML format follows thejson-schemaDevicetree bindings written in, reference:
Device Tree Overlay
After Linux 4.4, Dynamic DeviceTree was introduced. Device Tree Overlay is an extension mechanism for the Device Tree. The Device Tree is a data structure used to describe hardware devices, widely applied in embedded systems, especially those based on the Linux kernel.
Device Tree Overlayallows dynamic modification of the device tree content at runtime to add, modify, or delete device nodes and properties. It provides a flexible way to configure and manage hardware devices without recompiling the entire device tree. By using device tree overlays, developers can make configuration changes to hardware without rebooting the system.
Device Tree Overlay (Dynamic DeviceTree) is typically defined in a text format called Device Tree Source (DTS). DTS files describe the structure and properties of the device tree, including device nodes, register addresses, interrupt information, etc. Device tree overlays can achieve dynamic modification of the device tree by loading and parsing device tree files and merging them into the existing device tree.
Application Scenarios
Using device tree overlays, common configuration changes can be achieved, such as adding external devices, disabling unnecessary devices, modifying device properties, etc. This is very useful for embedded system development and debugging, especially when dealing with multiple hardware configurations or frequent hardware configuration changes.
Device Tree Overlay Syntax
overlay.dts
- Header Declaration
1 | /dts-v1/; |
- The overlay node name is used to define the device nodes and their properties to be added, modified, or deleted. It uses the same syntax as the device tree source file, but with specific modifiers before the node name to indicate the overlay operation.
For example, the following node:
1 | //arch/arm64/boot/dts/rockchip/topeet-rk3568-linux.dts |
If in the device tree overlay, to add aoverlay_nodenode:
There are several ways to express it:
1 | /dts-v1/; |
The compilation of device tree overlays is the same as that of device trees.
1 | dtc -I dts -O dtb overlay.dts -o overlay.dtbo |
Porting Device Tree Driver Overlays
We will explore how to port device tree overlays to the iTOP-RK3568 development board. Porting device tree overlays mainly includes the following steps.
- Configure the kernel to support mounting the configfs virtual file system.
- Configure the kernel to support device tree overlays
- Port device tree overlay drivers
Configure the kernel to support mounting the configfs virtual file system
1 | $ make ARCH=arm64 menuconfig |
After boot, check if the kernel has mounted the virtual file system; if not, run the following command to mount it
1 | $ mount -t configfs none /sys/kernel/config |
Configure the kernel to support device tree overlays
1 | $ make ARCH=arm64 menuconfig |
Save configuration:
1 | cp .config arch/arm64/configs/rockchip_linux_defconfig |
Port the device tree overlay driver
There is already a pre-written one on GitHubdevice tree overlay driver, we just need to compile this driver as a module or into the kernel
Compile:
1 | $ git clone https://github.com/ikwzm/dtbocfg.git |
Compile and generatedtbocfg.koCopy it to the development board.
Load the device tree overlay
Load the device tree overlay (first, you need the device tree overlay driver, check if configfs is mounted viacat proc/filesystemscheck if configfs is mounted successfully)
Enter the system directory/sys/kernel/config/device-tree/overlays/
1 | $ insmod dtbocfg.ko |
Multiple can be created; if multiple modify the same property, the last overlay’s value is used
1 | $ cd /sys/kernel/config/device-tree/overlays/ |
Changeset
Nodes in the device tree overlay (dtbo) must also be converted todevice_node, somedevice_nodemust also be converted toplatform_device. However, before performing the conversion,of_overlay_fdt_applythe function first creates achangeset. Then modifications are made based on this changeset.
The purpose of creating a changeset is to facilitate modification and restoration of the device tree.
A changeset is a data structure that describes changes to the device tree, recording modification operations such as adding, deleting, or modifying nodes. By creating a changeset, we can dynamically modify the device tree at runtime without altering the original device tree source files.。
- By creating a changeset, we can conveniently define the modification operations needed without directly manipulating the underlying structure of the device tree. This provides a high-level abstraction, allowing us to describe device tree changes in a more concise and readable manner.
- Additionally, changesets can be saved, transferred, and applied to other device trees, facilitating device tree configuration and customization across different systems or environments.
- Furthermore, changesets can also be used for device tree restoration. In some cases, we may need to undo modifications to the device tree at runtime and revert to its original state. By applying a reverse changeset, we can restore the device tree to its state before modification, achieving restoration of changes.
Therefore, creating a changeset provides a convenient, controllable, and reversible way to modify the device tree.
Introduction to the Virtual File System ConfigFS
In the Linux kernel, there are several commonly used virtual file systems. The virtual file system provides a kernel abstraction layer, allowing applications to operate on different types of files and devices through a unified file access interface. It simplifies application development and maintenance, offers higher portability and flexibility, and provides functionality for managing file systems and accessing underlying hardware.
procfs
The virtual file system,**provides an interface to access the runtime state of the system kernel,**representing processes, devices, drivers, and other system information in the kernel as files and directories. By reading and writing files in procfs, information about the system state can be obtained and modified.
sysfs
A virtual file system,**used to represent devices, drivers, and other kernel objects in the system.**It provides a unified interface to access and configure the properties and states of these objects through files and directories. Sysfs is commonly used by device drivers and system management tools to view and control the system’s hardware and kernel objects.
configfs
A virtual file system,**used for dynamically configuring and managing kernel objects.**It provides an interface to access kernel objects in the form of files and directories, allowing users to add, modify, and delete kernel objects at runtime without recompiling the kernel or rebooting the system. ConfigFS is often used to configure and manage devices, drivers, and subsystems.
These virtual file systems have some functional differences:
procfsis mainly used to access and manage process information, providing information about processes, kernel parameters, and system status.sysfsis mainly used to represent and configure devices, drivers, and other kernel objects in the system, providing a unified interface to access and control the properties and states of these objects.configfsis mainly used for dynamically configuring and managing kernel objects, providing an interface to access kernel objects in the form of files and directories, allowing addition, modification, and deletion of kernel objects at runtime.

To achieve the above functions, user space needs to interact with the kernel, that is, to loaddtbointo memory.sysfsThe role of the virtual file system is to export kernel data and attributes to user space in the form of files. Once exported to user space, reading these files means reading device files, and writing to these files means controlling the device.
configfsThe English explanation of the role isUserspace-driven kernel object configurationwhich translates to user-space configuration of kernel objects.
Soconfigfsandsysfsare exactly opposite,sysfsexports kernel objects to user space,configfsconfigures kernel objects from user space without recompiling the kernel or modifying kernel code. Soconfigfsis more suitable for the device tree overlay technology.
ConfigFS Core Data Structures
The core data structures of ConfigFS mainly include the following parts:
configfs_subsystem
configfs_subsystemis a top-level data structure representing the entire ConfigFS subsystem. It contains a pointer to the root config item group, as well as other attributes and status information of ConfigFS.
config_group
config_groupis a special type of config item representing a config item group. It can contain a set of related config items, forming a hierarchical structure.config_groupstructure contains a pointer to the parent config item and a linked list pointing to child config items.
config_item
This is the most basic data structure in ConfigFS, used to represent a config item. Each config item is a kernel object, which can be a device, driver, subsystem, etc.config_itemstructure contains information such as the type, name, attributes, and status of the config item, as well as pointers to the parent and child config items.
The relationships among these data structures can form a tree structure, whereconfigfs_subsystemis the root node,config_grouprepresents a configuration item group,config_itemrepresents a single configuration item. Child configuration items are linked together via a linked list, forming a parent-child relationship.
configfs_subsystem
1 | struct configfs_subsystem { |
struct configfs_subsystemThe structure containsstruct config_groupstructure,struct config_groupThe structure is as follows:
config_group
1 | struct config_group { |
struct config_groupThe structure containsstruct config_itemstructure,struct config_itemThe structure is as follows:
config_item
1 | struct config_item { |
struct config_itemThe structure containsstruct config_item_typestructure,struct config_item_typeThe structure is as follows
config_item_type
1 | struct config_item_type { |
struct config_item_typeContains many important data structures:
struct configfs_item_operations *ct_item_opsOperation methods of item (directory)struct configfs_group_operations *ct_goup_opsOperation methods of group (container)struct configfs_attribute **ct_attrsOperation methods of attribute filesstruct configfs_bin_attribute **ct_bin_attrsOperation methods of bin attribute files
Each
config_item(orconfig_group) must be bound to aconfig_item_type。
This structure determines what kind of “operable object” the item appears as in sysfs.
configfs_item_operations
1 | struct configfs_item_operations { |
release()
| Project | Description |
|---|---|
| Prototype | void (*release)(struct config_item *); |
| Trigger | Reference count is 0 |
| Responsibility | kfree / Resource release |
| Required | ✅ Required |
configfs_group_operations
1 | struct configfs_group_operations { |
make_item()
| Item | Description |
|---|---|
| Prototype | struct config_item *(*make_item)(struct config_group *, const char *name); |
| Trigger | User mkdir |
| Function | Dynamically create item |
| Required | If item creation is allowed |
make_group()
| Item | Description |
|---|---|
| Prototype | struct config_group *(*make_group)(struct config_group *, const char *name); |
| Trigger | mkdir |
| Function | Create sub-group (multi-level directory) |
drop_item()
| Project | Description |
|---|---|
| Prototype | void (*drop_item)(struct config_group *, struct config_item *); |
| Trigger | rmdir |
| Responsibility | config_item_put() |
| Must | ✅ Yes |
struct configfs_attribute
1 | struct configfs_attribute { |
Key data structure relationship diagram

ConfigFS API
Core API
config_group_init()
| Item | Description |
|---|---|
| Function Definition | void config_group_init(struct config_group *group); |
| Header File | #include <linux/configfs.h> |
| Parameter group | config_group to be initialized |
| Function | Initialize the basic structure of a group (without name/type) |
| Typical Scenario | Initialize the su_group of a subsystem |
| Return Value | None |
config_group_init_type_name()
| Item | Description |
|---|---|
| Function Definition | void config_group_init_type_name(struct config_group *group, const char *name, const struct config_item_type *type); |
| Header File | #include <linux/configfs.h> |
| Parameter group | The group to be initialized |
| Parameter name | The directory name displayed by the group in configfs |
| Parameter type | Corresponding config_item_type |
| Function | Initialize group + set name + bind type |
| Typical Scenario | Create a visible directory node |
| Return Value | None |
config_item_init_type_name()
| Project | Description |
|---|---|
| Function Definition | void config_item_init_type_name(struct config_item *item, const char *name, const struct config_item_type *type); |
| Header File | #include <linux/configfs.h> |
| Parameter item | config_item object |
| Parameter name | item name |
| Parameter type | item type |
| Function | Initialize an item and bind its type |
| Typical Scenario | Used in make_item |
| Return Value | None |
configfs_register_subsystem()
| Item | Description |
|---|---|
| Function Definition | int configfs_register_subsystem(struct configfs_subsystem *subsys); |
| Header File | #include <linux/configfs.h> |
| Parameter subsys | The configfs subsystem to register |
| Function | Under/sys/kernel/config/Register the subsystem directory |
| Effect | Create/sys/kernel/config/<name> |
| Return Value | Success: 0 Failure: Negative error code |
configfs_unregister_subsystem()
| Item | Description |
|---|---|
| Function Definition | void configfs_unregister_subsystem(struct configfs_subsystem *subsys); |
| Function | Unregister the entire configfs subsystem |
| Effect | Delete the subsystem directory and all sub-items |
| Return Value | None |
configfs_register_group()
| Item | Description |
|---|---|
| Function Definition | int configfs_register_group(struct config_group *parent, struct config_group *group); |
| Parameter parent | Parent group |
| Parameter group | Subgroup to register |
| Function | Register a group under the parent directory |
| Features | ✅ Static group (users cannot rmdir) |
| Return value | Success: 0 |
configfs_unregister_group()
| Item | Description |
|---|---|
| Function definition | void configfs_unregister_group(struct config_group *group); |
| Function | Unregister group |
| Note | Only applies to groups registered via register_group |
config_item_put()
| Item | Description |
|---|---|
| Function definition | void config_item_put(struct config_item *item); |
| Function | Decrease reference count |
| Trigger | Reference count is 0 → call release |
| Common location | In drop_item callback |
| Return value | None |
Attribute-related
Can utilizeCONFIGFS_ATTRRelated macro
CONFIGFS_ATTR_RO()
| Item | Description |
|---|---|
| Macro definition | CONFIGFS_ATTR_RO(prefix, name) |
| Generate | Read-only property |
| Required function | prefix_name_show() |
| Generated variable | prefixattr_name |
CONFIGFS_ATTR_WO()
| Item | Description |
|---|---|
| Macro definition | CONFIGFS_ATTR_WO(prefix, name) |
| Generate | Write-only property |
| Required function | prefix_name_store() |
CONFIGFS_ATTR()
| Item | Description |
|---|---|
| Macro definition | CONFIGFS_ATTR(prefix, name) |
| Generate | Read-write property |
Example
Register configFS subsystem
1 |
|
After loading, you can see/sys/kernel/configthe registered subsystem myconfigfs in the directory
1 | $ ls /sys/kernel/config |
Register group container
1 |
|
Test
1 | $ ls /sys/kernel/config/ |
Create item in user space
We have successfully created/sys/kernel/config/under the directorymyconfigfssubsystem, and createdmygroupcontainer under this subsystem, butmygroupcannot use mkdir to create item under the container
1 |
|
Test:
1 | ~ $ insmod configfs_make_item_test.ko |
Improve drop and release
releaseanddrop_itemare two different member fields used for different purposes:
releaseThe member field is defined in thestruct config_item_typestructure as a callback function pointer. It points to a function that the kernel calls when a configfs config item is released or deleted, to perform corresponding resource cleanup operations. It is typically used to release resources associated with the config item, such as freeing dynamically allocated memory, closing open file descriptors, etc.drop_itemis defined in thestruct configfs_group_operationsstructure as a callback function pointer. It points to a function that the kernel calls when a configfs config group is deleted, to handle operations related to the config group. This function is typically used to clean up the state of the config group, release associated resources, and perform other necessary cleanup operations.drop_itemThe function is called when a config group is deleted, not when a single config item is deleted.
releaseThe member field is used for releasing config items, while thedrop_itemmember field is used for deleting config groups. They perform different tasks in different contexts, but both are related to resource release and cleanup.
1 |
|
Test:
1 | $ insmod configfs_release_and_drop_test.ko |
child_group1is a subgroup statically registered viaconfigfs_register_groupand is “fixed” by default, meaning it cannot be deleted from user space.
| The function | Call timing | Responsibility | Is it mandatory |
|---|---|---|---|
root_drop_item | Called by the parent group when rmdir starts | Disassociate item from group, decrease reference count | ✅ Mandatory (otherwise release cannot be triggered) |
child_release | Automatically called when reference count reaches zero | Release item’s own memory and resources | ✅ Mandatory (otherwise memory leak) |
Register attribute
We successfully created the item, but no attributes or operation items were created under the item.
1 |
|
Test:
1 | $ insmod configfs_attribute_test.ko |
Implement multi-level directories
1 |
|
It should be noted that:
When the user executesmkdir /sys/kernel/config/myconfigfs/xxx, ConfigFS first calls.make_item(if defined), and on failure attempts to call.make_group(if defined)
dtbocfg driver analysis
Next, let’s analyze the device tree overlay driverdtbocfg.koCode:
1 | static struct configfs_subsystem dtbocfg_root_subsys = { |
This code defines a structure instance nameddtbocfg_root_subsysofconfigfs_subsystemstructure instance,
First,dtbocfg_root_subsys.su_groupis aconfig_groupstruct, which represents the root configuration item group of the subsystem. Here, the struct’scg_itemfield represents the basic configuration item of the root configuration item group.
.ci_namebuf = "device-tree": The name of the configuration item is set todevice-tree, indicating that the name of the configuration item isdevice-tree。
.ci_type = &dtbocfg_root_type: The type of the configuration item is set todtbocfg_root_type, which is a custom configuration item type.
Next,.su_mutexfield is a mutex lock used to protect the subsystem’s operations. Here, the__MUTEX_INITIALIZERmacro is used to initialize the mutex lock.
Summary: The above code creates a subsystem nameddevice-tree, whose root configuration item group is empty. More configuration items and configuration item groups can be added under this subsystem for dynamically configuring and managing device tree-related kernel objects.
1 | $ ls /sys/kernel/config/ |
Driver code entry part:
1 | static int __init dtbocfg_module_init(void) |
This code is an initialization functiondtbocfg_module_init(), used to initialize and register the ConfigFS subsystem and configuration item groups.
- First, through the
config_group_init()function, initializedtbocfg_root_subsys.su_group, which is the root configuration item group of the subsystem. - Using the
config_group_init_type_name()function, initializedtbocfg_overlay_group, representing a configuration item group namedoverlays, and specify the type of the configuration item group asdtbocfg_overlays_type, which is a custom configuration item type. - Call the
configfs_register_subsystem()function to registerdtbocfg_root_subsyssubsystem. If registration fails, an error message will be printed, and it will jump toregister_subsystem_failedError handling at the tag. - Call
configfs_register_group()function registereddtbocfg_overlay_groupconfiguration item group and added it todtbocfg_root_subsys.su_groupunder.
The purpose of this code is to initialize and register a ConfigFS subsystem nameddevice-treeand create a configuration item group namedoverlaysunder it.
That is, under the Linux system, indevice-treesubsystem, createdoverlayscontainer
1 | $ ls /sys/kernel/config/device-tree/ |
References
kernel/Documentation/filesystems/configfsdirectory underconfigfs.txt。
kernel/samples/configfsdirectory underconfigfs_sample.c

