1. Device Tree Introduction
  2. Device Tree Compilation
  3. Basic Device Tree Syntax
    1. root node
    2. Child node
    3. reg property
    4. #address-cells and #size-cells property
      1. #address-cells
      2. #size-cells
    5. model property
    6. status property
    7. compatible property
    8. aliases node
    9. chosen node
    10. device_type node
    11. Custom properties
  4. Case Analysis
    1. Case Study: Interrupts
      1. interrupts property
      2. interrupt-controller property
      3. interrupt-parent attribute
      4. #interrupt-cellsproperty
      5. Comparison of other SoC device trees
    2. Case Study: Clock
      1. Clock provider
        1. Clock provider properties
          1. clock-cells
          2. clock-frequency
      2. Clock consumer
        1. Clock consumer properties
          1. clock
          2. clock-names
          3. assigned-clocks and assigned-clock-rates
          4. clock-indices
          5. assigned-clock-parents
    3. Example analysis: CPU
      1. Processor topology relationships
        1. cpu-map, socket, and cluster nodes
        2. core and thread nodes
    4. Case Study: GPIO
      1. gpio-controller property
      2. #gpio-cellsproperty
      3. The gpio-ranges property
      4. GPIO description property and gpio-cells
      5. Other properties
      6. example
      7. Comparison with other SoCs
    5. Case analysis: pinctrl
      1. Introduction to pinmux
      2. Using pinctrl to set multiplexing relationships
        1. Client
        2. Server
      3. Writing a pinctrl example
  5. DTB file format parsing
    1. Header
    2. Memory Reserved Block
    3. Structure Block
      1. Token types
      2. tree structure
      3. End of structure block
    4. String block
  6. dtb expansion into device_node
    1. Source code analysis of the DTB parsing process
      1. init/main.c
      2. arch/arm64/kernel/setup.c
      3. setup_machine_fdt(__fdt_pointer)
      4. unflatten_device_tree()
        1. __unflatten_device_tree
        2. unflatten_dt_nodes()
        3. populate_node
  7. device_node to platform_device
    1. Conversion rules
    2. example
    3. Source code analysis
      1. of_platform_default_populate_init()
      2. of_platform_populate()
      3. of_platform_bus_create()
      4. of_platform_device_create_pdata()
      5. of_device_alloc()
  8. platform under the device tree_device and platform_driver matching
    1. of_match_table
    2. example
  9. of operations
    1. Get the device tree node
      1. of_find_by_name()
      2. of_find_node_by_path()
      3. of_get_parent()
      4. of_get_next_child()
      5. for_each_child_of_node()
      6. of_find_compatible_node()
      7. of_find_matching_node_and_match()
      8. example
    2. Get device tree property
      1. of_find_property()
      2. of_property_count_elems_of_size()
      3. of_property_read_u32_index()
      4. of_property_read_u64_index()
      5. of_property_read_variable_u32_array()
      6. of_property_read_string()
      7. of_property_read_bool()
      8. example
    3. ranges property
      1. platform_get_resource: the prerequisite for obtaining device tree resources
      2. Introduction to the ranges attribute
      3. Device Classification
        1. Memory-Mapped Devices
        2. Non-Memory-Mapped Devices
    4. The concept of named resources
    5. Accessing the reg attribute
    6. Obtaining interrupt resources
      1. irq_of_parse_and_map()
      2. irqd_get_trigger_type()
      3. irq_get_irq_data()
      4. gpio_to_irq()
      5. of_irq_get()
      6. platform_get_irq()
      7. Example
  10. Reference Documentation: dts-bindings
  11. Device Tree Overlay
    1. Device Tree Overlay Syntax
    2. Porting Device Tree Driver Overlay
    3. Load the device tree overlay.
    4. changeset
    5. Introduction to the ConfigFS Virtual File System
    6. ConfigFS core data structures
      1. configfs_subsystem
      2. config_group
      3. config_item
      4. config_item_type
        1. configfs_item_operations
          1. release()
        2. configfs_group_operations
          1. make_item()
          2. make_group()
          3. drop_item()
        3. struct configfs_attribute
      5. Key data structure relationship diagram
    7. ConfigFS API
      1. Core API
        1. config_group_init()
        2. config_group_init_type_name()
        3. config_item_init_type_name()
        4. configfs_register_subsystem()
        5. configfs_unregister_subsystem()
        6. configfs_register_group()
        7. configfs_unregister_group()
        8. config_item_put()
      2. Attribute-related
        1. CONFIGFS_ATTR_RO()
        2. CONFIGFS_ATTR_WO()
        3. CONFIGFS_ATTR()
    8. example
      1. register configFS subsystem
      2. register group container
      3. create item in user space
      4. Improve drop and release.
      5. Register attribute
      6. Implement multi-level directories
    9. dtbocfg driver analysis
    10. References
Cover image for Linux device tree

Linux device tree

Words 33.2k
Views
Visitors

Timeline

Timeline

2025-11-30

init

2025-12-01

add devicetree

2025-12-03

add devicetree plugin

This article introduces the basic concepts and core mechanisms of the Linux device tree. It first explains the background of the device tree as a hardware description mechanism, pointing out that it is used to describe the characteristics, connection relationships, and configuration information of hardware devices, which can reduce the coupling between the kernel and hardware and improve system portability and maintainability. Subsequently, the article details four key components related to the device tree: DTS (Device Tree Source), DTSI (Device Tree Source Include), DTB (Device Tree Blob), and DTC (Device Tree Compiler), and explains their respective roles and relationships. In addition, the article also introduces the storage location of device tree source files under the ARM64 architecture, as well as the methods for compiling and decompiling the device tree, and mentions the technique of exposing the device tree to user space through configuration variables for debugging. Finally, the article outlines the basic syntax of the device tree, including elements such as the root node, child nodes, node labels, node names, unit addresses, property definitions, and child nodes, providing a foundation for readers to understand the writing and parsing of the device tree.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Race Conditions
5. Advanced Character Device Topics
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hotplug
11. pinctrl Subsystem
12. GPIO subsystem
13. Input subsystem
14. 1-Wire
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network devices
23. ADC
24. IIO
25. USB
26. LCD

Device Tree Introduction

Background

The 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_devicea structure to describe hardware devices, which is a traditional platform bus device description method.

eachplatform_deviceThe structure represents a specific hardware device and enables the kernel to communicate and interact with the device by registering it on the platform bus. The structure contains information such as the device name, resources (e.g., memory addresses, interrupt numbers), device driver, and so on.

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 hardware description method, making support for different chips and board levels simpler and more flexible. In addition, the device tree also provides visualization and readability of hardware configuration, making it easier for developers to understand and debug hardware.

DTS(Device Tree Source)DTS is the source file of the device treeIt uses a text-like syntax to describe the structure, properties, and connection relationships of hardware devices. DTS files have the .dts extension and are usually written by developers. They are human-readable and used to describe the hierarchical structure and property information of the device tree.

DTSI(Device Tree Source Include)DTSI files are include files for device tree source filesThey 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. By using DTSI, the reusability and maintainability of the device tree can be improved (similar to the role of header files in C language).

DTB(Device Tree Blob)DTB is the binary representation of the device treeDTB 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 compiler for the device treeIt is a command-line tool used to compile DTS and DTSI files into DTB files. DTC converts the device tree source code in text format 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.

Device Tree Source Code Location

Device tree source files under the ARM64 architecture are usually stored inarch/arm64/boot/dts/the 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.

In the ARM64 subdirectories, they are also organized and classified 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/rockchipEach subdirectory may contain multiple device tree files to describe different hardware configurations and device types.

Device Tree Compilation

In the Linux kernel source code, the source code and related tools of DTC (Device Tree Compiler) are usually stored inscripts/dtc/the 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 output file name after compilation

Device tree decompilation

1
dtc -I dtb -O dts -o output.dts input.dtb

Compiling in Linux

1234
# Compile all arm64 DTBsARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu- make dtbs# Compile a single DTBARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu- make imx6dl-sabrelite.dtb

For debugging, it may be useful to expose the DT to user space. UsingCONFIG_PROC_DEVICETREEthe configuration variable can achieve this purpose. Then, you can browse/proc/devicetreethe DT in.

Basic Device Tree Syntax

root node

12345678
/dts-v1/; // Device tree version information/ {	// Root node start	/*		Comments can be added here,Describe the properties and configuration of the root node	*/}

Child node

1234
[label:] node-name@[unit-address] {	[properties definitions]	[child nodes]};
  • Node label (Label)(Optional): A node label is an optional identifier used to reference the node in the device tree. The label allows other nodes to directly reference this node to establish reference relationships in the device tree.

  • Node name (Node Name): The node name is a string used to uniquely identify the node’s position in the device tree. The node name is usually the name of the hardware device, but it must be unique in the device tree.

  • Unit address (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 requirements. The purpose of the unit address is to distinguish different instances of the same type of device.

  • Property definitions (Properties Definitions): Property definitions are a set of key-value pairs used to describe the configuration and characteristics of a device. Properties can be defined according to the device requirements, such as register addresses, interrupt numbers, clock frequencies, etc.

  • Child Nodes: Child nodes are children of the current node, used to further describe subcomponents or configurations of a 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 have in a device nodesingle-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 a hexadecimal value (u32). size indicates the size of the register, i.e., the number of bytes occupied.

Example:

12345
my_device {	compatible = "vendor,device";	reg = <0x1000 0x4>;	// Other properties and child node definitions}
  • 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:

12345
my_device {	compatible = "vendor,device";	reg = <0x1000 0x8 0x2000 0x4>;	// Other properties and child node definitions};

Each device has at least one node in the DT. Some properties are common to many device types, especially devices on buses known to the kernel (SPI, I2C, platform, MDIO, etc.). These properties are reg,#address-cellsand#size-cells, and their purpose is to perform device addressing on the bus where they are located.

Every addressable device has a reg property.

That is, the primary addressing property is reg, which is a generic property whose meaning depends on the bus where the device is located.

#address-cellsand#size-cellsproperty

#address-cellsand#size-cellsThe property is used to specify in the device tree to be set in the previous subsectionaddress cellsandaddress sizethe number of bits. They provide the metadata required for device tree parsing to correctly interpret the device’s address and size information.

#address-cellsand#size-cellsprefix in#It is a naming convention in the device tree that represents “cell count”. It cannot be translated as ‘length’; it indicates how many 32-bit cells are used to represent the value of the property.

Addressable devices inherit from their parent node’s#size-celland#address-cell, the parent node represents the bus controller. In a given device, there exists#size-celland#address-celldoes not affect the device itself, but affects its child devices. In other words, when interpreting a given node’sregproperty, you must know the parent node’s#address-cellsand#size-cellsvalue. The parent node is free to define the addressing scheme applicable to the device’s child nodes (children).

#address-cells

The property is a special property located at the root node of the device tree. It specifies the number of bits of an address cell in the device tree. An address cell is a single unit used to represent a device address in the device tree. It is usually an integer, which can be a decimal or hexadecimal value. Its value tells the software parsing the device tree how many bits should be used to represent one address cell when interpreting device addresses.

By default,#address-cellsthe value is 2, meaning two cells are used to represent a device address. This means the device’s address will consist of two integers (each integer using a specified number of bits).

For example, for a device usingtwo 32 -bit integerto represent the address (64-bit), you can set the #address-cells property to <2> in the root node of the device tree.

#size-cells

#size-cells The property is also a special property located at the root node of the device tree. It specifies the number of bits of a size cell in the device tree. A size cell is a single unit used to represent a device size in the device tree. It is usually an integer, which can be a decimal or hexadecimal value.

Example

123456789
node1 {	#address-cells = <1>;	#size-cells = <1>;	node1-child {		reg = <0x02200000 0x4000>;		// Other properties and child node definitions	};};
  • Address part: 0x02200000 is interpreted as one address cell, with the address 0x02200000.
  • Size part: 0x4000 is interpreted as one size cell, with the size 0x4000.

model property

In the device tree,the model property is used to describe the model or name of the device. It is usually an attribute of a device node, used to provide identification information about the device. The model property is optional, but it is often used in practice.

12345
my_device {	compatible = "vendor,device";	model = "My Device XYZ";	// Other properties and child node definitions}

The model property is usually used to identify and distinguish different devices, especially when the compatible property of the device nodes is the same or similar. By using different model property values, the type of device being used can be determined more accurately.

status property

In the device tree, the status propertyis used to describe the status 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 value of the status property can be one of the following:

  • okay”: indicates that the device or node is working normally and available.

  • disabled”: indicates that the device or node is disabled and unavailable.

  • reserved”: indicates that the device or node is reserved and temporarily unavailable.

  • fail”: indicates that the device or node failed to initialize or operate, and is unavailable.

Example:

12345
my_device {	compatible = "vendor,device";	status = "okay";	// Other properties and child node definitions}

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 device nodes and drivers.

The value of the compatible property is a string or a list of strings, used to specify the rules for a device node to be compatible with the corresponding driver or device descriptor. Usually, 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 the device node is compatible with a specific device from a specific manufacturer.
  • String list: for example,["vendor,device1", "vendor,device2"], used to specify that the device node is compatible with multiple devices, usually when the device node has multiple variants or configurations.
  • Wildcard matching: for example,"vendor,*", used to specify that the 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, it selects the appropriate driver 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

123456
aliases {	mmc0 = &sdmmc0;	mmc1 = &sdmmc1;	mmc2 = &sdhci;	serial0 = "/simple@fe000000/seria1@11c500";};

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, the associations between device nodes can be simplified, and the need to repeatedly enter device node paths is 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 the internal organization and referencing of 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 subnodes and properties:

  • bootargs: used forStores the command-line parameters passed when booting the kernel. It can contain information such as kernel parameters and device tree parameters. During the boot process, the operating system or boot loader can read this property to obtain boot parameters.
  • stdout-path: used forSpecifies the device path used for standard output. During 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 boot loader or firmware in use.
  • 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 boot loader during the boot process to load the initrd into memory for the kernel to use.
  • 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 use and context of the device tree.

example

123
chosen {	bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";};

By using the chosen node, information related to the system boot process can be conveniently passed to the operating system or boot loader. In this way, various components of system boot and configuration can share and access this information, thereby achieving a more flexible and configurable system boot process.

device_type node

In the device tree,The device_type node is a node used to describe the type of a device.. ItIt usually 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 presence 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: Indicates the central processing unit.
  • memory: Indicates a memory device.
  • display: Indicates a display device, such as an LCD screen.
  • serial: Indicates a serial communication device, such as a serial port.
  • ethernet: Indicates an Ethernet device.
  • usb: Indicates a Universal Serial Bus (USB) device.
  • i2c: Indicates a device that communicates using the I2C (Inter-Integrated Circuit) bus.
  • spi: Indicates a device that communicates using the SPI (Serial Peripheral Interface) bus.
  • gpio: Indicates a general-purpose input/output (GPIO) device.
  • pwm: Indicates a pulse-width modulation (PWM) device.

These are just some examples of common device types. In practice, device types can be customized and extended based on the specific hardware and the usage of the device tree.

Custom properties

Custom properties in the device tree are properties added by users according to specific requirements. 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 property named pinnum for a pin label in the device tree.

1234
my_device {	compatible = "my_device";	pinnum = <0 1 2 3 4>;};

Definitions of some data types used in the device tree:

  • Text strings are represented by double quotes. Commas can be used to create a list of strings.
  • Cells are 32-bit unsigned integers delimited 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 Study: Interrupts

arch/arm64/boot/dts/rk3568.dtsi

123456789101112131415161718192021
pinctrl: pinctrl {	compatible = "rockchip,rk3568-pinctrl";	rockchip,grf = <&grf>;	rockchip,pmu = <&pmugrf>;	#address-cells = <2>;	#size-cells = <2>;	ranges;	gpio0: gpio0@fdd60000 {		compatible = "rockchip,gpio-bank";		reg = <0x0 0xfdd60000 0x0 0x100>;		interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;		clocks = <&pmucru PCLK_GPIO0>, <&pmucru DBCLK_GPIO0>;		gpio-controller;		#gpio-cells = <2>;		interrupt-controller;		#interrupt-cells = <2>;	};};

arch/arm64/boot/dts/rockchip/topeet-screen-lcds.dts

12345678910111213141516
&i2c1 {    status = "okay";	ft5x061:ft5x06@38 {		status = "disabled";		compatible = "edt,edt-ft5306";		reg = <0x38>;		touch-gpio = <&gpio3 RK_PA5 IRQ_TYPE_EDGE_RISING>;		interrupt-parent = <&gpio3>;		interrupts = <RK_PA5 IRQ_TYPE_LEVEL_LOW>;		reset-gpio = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;		touchscreen-size-x = <800>;		touchscreen-size-y = <1280>;    };};

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.

12345678910111213
gpio0: gpio0@fdd60000 {	...	interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;	interrupt-controller;	...};ft5x061:ft5x06@38 {	...	interrupt-parent = <&gpio3>;	interrupts = <RK_PA5 IRQ_TYPE_LEVEL_LOW>;	...  	};
  1. 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 inside the processor.

  1. 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, or a macro definition or symbolic reference.**In the given code snippet, 33 indicates that the device uses interrupt number 33.

  1. interrupt trigger type

The third parameter of the interrupts property specifies the interrupt trigger type, i.e., the trigger condition of the interrupt signal. Common trigger types include edge-triggered and level-triggered.
Level-triggered means that the interrupt signal is triggered when it maintains 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 code.include/dt-bindings/interrupt-controller/irq.hthe directory

123456
#define IRQ_TYPE_NONE 0 // No interrupt trigger type#define IRQ_TYPE_EDGE_RISING 1 // Rising edge trigger#define IRQ_TYPE_EDGE_FALLING 2 // Falling edge trigger#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)// Dual-edge triggered#define IRQ_TYPE_LEVEL_HIGH 4 // High-level triggered#define IRQ_TYPE_LEVEL_LOW 8 // Low-level triggered

interrupt-controller property

interrupt-controllerThe property is used toidentify that the device described by the current node is an interrupt controller

An interrupt controller isa hardware or software moduleresponsible for managing and distributing interrupt signals. It receives interrupt requests from various devices and distributes interrupts to the corresponding processors or devices according to priority and configuration rules.

interrupt-controllerThe attribute itself has no specific attribute value; it only needs to appear in the node’s attribute list.

interrupt-parent attribute

interrupt-parentThe attribute is used in the device tree to establish the association between interrupt signal sources and interrupt controllers. Itspecifies the interrupt controller node to which the interrupt signal source belongs, to ensure correct interrupt handling and distribution.

interrupt-parentThe attribute 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

123456
ft5x061:ft5x06@38 {	...	interrupt-parent = <&gpio3>;	interrupts = <RK_PA5 IRQ_TYPE_LEVEL_LOW>;	...};

In gpio0, there is nointerrupt-parent, but because the root DTS has a defaultinterrupt-parent = GIC, therefore GPIO0 does not need to explicitly write it.

#interrupt-cellsproperty

#interrupt-cellsThe property is used todescribe the number of interrupt number cells for each interrupt signal source in the interrupt controller.

An interrupt number cell is a fixed-size unit used to represent the interrupt number and other related information.. By specifying the number of interrupt number cells, 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, indicating the number of interrupt number cells. Usually, this value is a positive integer, such as 1, 2, or 3, depending on the requirements of the interrupt controller and the device.

123456789101112131415
gpio0: gpio0@fdd60000 {	...	interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;	...	interrupt-controller;	#interrupt-cells = <2>;	...};ft5x061:ft5x06@38 {	...	interrupt-parent = <&gpio3>;	interrupts = <RK_PA5 IRQ_TYPE_LEVEL_LOW>;	...};

In gpio0, the interrupt controller is gic; in the gic node,#interrupt-cellsthe attribute is set to 2, which is why the interrupts attribute in the gpio0 node has two values;

For ft5x061, the interrupt controller is gpio3; 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

123456789101112131415161718192021
gpio1: gpio@0209c000 {	compatible = "fsl,inx6ul-gpio", "fsl,imx35-gpio";	reg = <0x0209c000 0x4000>;	interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;	gpio-controller;	#gpio-cells = <2>;	interrupt-controller;	#interrupt-cells = <2>;	edt-ft5x06@38 {		compatible = "edt,edt-ft5306", "edt,edt-ft5x06", "edt,edt-ft5406";		pinctrl-names = "default";		pinctrl-0 = <&ts_int_pin &ts_reset_pin>;		reg = <0x38>;		interrupt-parent = <&gpio1>;		interrupts = <9 0>;		reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;		irq-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;		status = "disabled";	};}

Samsung

1234567891011121314151617181920212223
gpio_c: gpioc {	compatible = "gpio-controller";	#gpio-cells = <2>;	interrupt-controller;	#interrupt-cells = <2>;};ft5x06: ft5x06038 {	compatible = "edt,edt-ft5406";	reg = <0x38>;	pinctrl-names = "default";#if defined(RGB_1024x600) || defined(RGB_800x480)	pinctrl-0 = <&tsc2007_irq>;	interrupt-parent = <&gpio_c>;	interrupts = <26 IRQ_TYPE_EDGE_FALLING>;#endif#if defined(LvDs_800×1280) || defined(LvDS_1024x768)	pinctrl-0 = <&gt911_irq>;	interrupt-parent = <&gpio_b>;	interrupts = <29 IRQ_TYPE_EDGE_FALLING>;#endif	reset-gpios = <&gpio_e 30 0>;}

Case Study: Clock

Clock is used todescribe the clock sources in hardware devices and systems, as well as clock-related configurations and connection relationships.

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: In the device tree, a clock provideris represented as a clock node

Clock provider properties

clock-cells

This property is used to specify the bit width of the clock number. It is an integer value that represents the bit width of the clock number.

Usually, whenclock-cellsit is 0, it represents one clock; when it is 1, it represents multiple clocks.

123456789101112
osc24m: osc24m {	compatible = "clock";	clock-frequency = <24000000>;	clock-output-names = "osc24m";	#clock-cells = <0>;};// Multiple clocksclock: clock {	#clock-cells = <1>;	clock-output-names = "clock1", "clock2";};
clock-frequency

It is a property in the device tree used to specify the clock frequency. It is used to describe the frequency of the clock signal provided by a clock node, using Hertz (Hz) as the unit.

For a clock provider node,clock-frequencythe property represents the frequency of the clock signal generated by the 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.

123456
osc24m: osc24m {	compatible = "clock";	clock-frequency = <24000000>;	clock-output-names = "osc24m";	#clock-cells = <0>;};

Clock consumer

A clock consumer isa hardware device or module that depends on clock signals. They obtain clock signals by referencing the clock sources provided by clock provider nodes.

Clock consumer properties

clock

This property is used to specify the clock sources required by a clock consumer node. It is an integer array, each element being a clock number, representing a clock source required 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:

1234
clock: clock {	clocks = <&cru CLK_VOP>;	clock-names = "clk_vop";};

The clocks property specifies the clock sources used by this node, referencingcruin the nodeCLK_VOPclock source.clock-namesThe property specifies the name of the clock source, here it isclk_vop

assigned-clocks and assigned-clock-rates

are properties in the device tree used to describe multiple clocks, and are usually used together.

assigned-clocksThe property is used toidentify the clock source used by the clock consumer node

It is an integer array, each element corresponds to a clock number. The clock number refers to the number of the clock source provided by the clock producer node (such as a clock controller).

By using in the clock consumer nodeassigned-clocksproperty, the clock source required by the node can be specified.

assigned-clock-ratesThe property is used tospecify the clock frequency of each clock source

It is an integer array, 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 property should be consistent withassigned-clocksthe clock numbers in the property.

12345
cru: clock-controller@fdd20000 {	#clock-cells = <1>;	assigned-clocks = <&pmucru CLK_RTC_32K>, <&cru ACLK_RKVDEC_PRE>;	assigned-clock-rates = <32768>, <300000000>;};
clock-indices

clock-indicesThe property is used tospecify the index value of the clock source used by the clock consumer node

It is an integer array, each element corresponds to an 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 in the clock consumer nodeclock-indicesproperty, the clock source required by the node can be explicitly specified and matched in a specific order.

1234567891011
scpi_dvfs: clocks-0 {	#clock-cells = <1>;	clock-indices = <0>, <1>, <2>;	clock-output-names = "atlclk", "aplclk", "gpuclk";};scpi_clk: clocks-1 {	#clock-cells = <1>;	clock-indices = <3>;	clock-output-names = "pxlclk";};

In the first node,atlclk,aplclk,gpuclkthe indexes of the three clock sources are set to 0, 1, 2 respectively, and in the second node,pxlclkthe index value of the clock source is set to 3.

assigned-clock-parents

used forspecify the parent clock source of the clock source used by the clock consumer node

It is an array of clock source references, 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, that is, they provide clock signals to other clock sources as inputs.

By using in the clock consumer nodeassigned-clock-parentsproperty, the parent clock source required by the node can be explicitly specified and matched in a specific order

12345
clock: clock {	assigned-clocks = <&clkcon 0>, <&pll 2>;	assigned-clock-parents = <&pll 2>;	assigned-clock-rates = <115200>, <9600>;};

assigned-clocksproperty specifies the clock sources used by this node, referencing two clock source nodes:clkcon 0andpll 2

assigned-clock-parentsproperty specifies the parent clock sources of these clock sources, referencingpll 2clock source nodes.

assigned-clock-ratesproperty specifies the clock frequency of each clock source, which are 115200 and 9600 respectively.

Example analysis: CPU

The device tree’s cpus nodeis an important node used to describe the processors in the system. It isthe top-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 usuallycpu@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 attributes

cpu@XThe attributes in a child node can include the following information:

  • device_type: indicates that the device type is a processor (e.g., “cpu”).
  • reg: specifies the address range of the processor, usually 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 properties 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 between processors.

  • cpu-map: Describes the mapping relationship of processors, usually used in multi-core processor systems.
  • socket: Describes physical sockets or chipsets in a multiprocessor system.
  • cluster: Describes a processor cluster, that is, a logical group formed by organizing multiple processors together.
  • core: Describes a processor core, that is, an independent execution unit within a physical processor.
  • thread: Describes a processor thread, that is, a thread within a physical processor core. (Multiple threads exist only if hyper-threading is enabled; otherwise, a core has only one thread.)

The nesting relationship of these nodes can form a hierarchy under the cpus node, reflecting the processor’s topology.

Single-core CPU:

123456789
cpus {	#address-cells = <1>;	#size-cells = <0>;	cpu0: cpu@0 {		compatible = "arm,cortex-a7";		device_type = "cpu";		// Other attributes...	};}

Multi-core CPU:

1234567891011121314151617181920212223
cpus {	#address-cells = <1>;	#size-cells = <0>;	cpu0: cpu@0 {		device_type = "cpu";		compatible = "arm,cortex-a9";	};	cpu1: cpu@1 {		device_type = "cpu";		compatible = "arm,cortex-a9";	};	cpu2: cpu@2 {		device_type = "cpu";		compatible = "arm,cortex-a9";	};	cpu3: cpu@3 {		device_type = "cpu";		compatible = "arm,cortex-a9";	};}

The cpus node is a container node that contains the cpu0 child node. This node uses#address-cellsand#size-cellsattribute to specify the number of address and size cells.

cpu-map, socket, and 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 becpusNodes, andThe child node can be one or moreclusterandsocketnode.
    • Through thecpu-mapnode, which can define the connection and organizational structure between different cores and clusters.
  • socketThe node is used to describe processor sockets (socket) mapping relationship.

    • Eachsocketchild node represents a processor socket, canusecpu-map-maskattribute to specify the cores used by that socket.
    • By specifying the appropriatesocketfor each child nodecpu-map-mask, the cores used in different sockets can be defined. In this way, the operating system and software can understand the core allocation across different sockets.
  • clusternodeis used to describe cores (cluster) mapping relationship.

    • Eachclusterchild node represents a core cluster, canusecpu-map-maskattribute to specify the cores used by that cluster.
    • By specifying the appropriateclusterfor each child nodecpu-map-mask, the cores used in each cluster can be defined. In this way, the operating system and software can understand the core allocation across different clusters

A concrete example of a big.LITTLE architecture

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
cpus {	#address-cells = <2>;	#size-cells = <0>;	cpu-map {		cluster0 {			core0 {				cpu = <&cpu_l0>;			};			core1 {				cpu = <&cpu_l1>;			};			core2 {				cpu = <&cpu_l2>;			};			core3 {				cpu = <&cpu_l3>;			};		};		cluster1 {			core0 {				cpu = <&cpu_b0>;			};			core1 {				cpu = <&cpu_b1>;			};		};	};	cpu_l0: cpu@0 {		device_type = "cpu";		compatible = "arm,cortex-a53", "arm,armv8";	};	cpu_l1: cpu@1 {		device_type = "cpu";		compatible = "arm,cortex-a53", "arm,armv8";	};	cpu_l2: cpu@2 {		device_type = "cpu";		compatible = "arm,cortex-a53", "arm,armv8";	};	cpu_l3: cpu@3 {		device_type = "cpu";		compatible = "arm,cortex-a53", "arm,armv8";	};	cpu_b0: cpu@100 {		device_type = "cpu";		compatible = "arm,cortex-a72", "arm,armv8";	};	cpu_b1: cpu@101 {		device_type = "cpu";		compatible = "arm,cortex-a72", "arm,armv8";	};};

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 usually used to describe the configuration of processor cores and threads.

coreThe node is used to describe the cores of a processor. A processor usually consists of multiple cores, and each core can independently execute instructions and tasks.

threadThe node is used to describe the threads of a processor. A thread is the basic execution unit executed on a processor core, and each core can support multiple threads. (Unless hyper-threading is enabled, a core has only one thread.)

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
cpus {	#address-cells = <2>;	cpu-map {		socket0 {			cluster0 {				core0 {					thread0 {						cpu = <&CPU0>;					};					thread1 {						cpu = <&CPU1>;					};				};				core1 {					thread0 {						cpu = <&CPU2>;					};					thread1 {						cpu = <&CPU3>;					};				};			};			cluster1 {				core0 {					thread0 {						cpu = <&CPU4>;					};					thread1 {						cpu = <&CPU5>;					};				};				core1 {					thread0 {						cpu = <&CPU6>;					};					thread1 {						cpu = <&CPU7>;					};				};			};		};		socket1 {			cluster0 {				core0 {					thread0 {						cpu = <&CPU8>;					};					thread1 {						cpu = <&CPU9>;					};				};				core1 {					thread0 {						cpu = <&CPU10>;					};					thread1 {						cpu = <&CPU11>;					};				};			};			cluster1 {				core0 {					thread0 {						cpu = <&CPU12>;					};					thread1 {						cpu = <&CPU13>;					};				};				core1 {					thread0 {						cpu = <&CPU14>;					};					thread1 {						cpu = <&CPU15>;					};				};			};		};	};};

Case Study: GPIO

123456789101112131415161718192021222324
gpio0: gpio@fdd60000 {	compatible = "rockchip,gpio-bank";	reg = <0x0 0xfdd60000 0x0 0x100>;	interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;	clocks = <&pmucru PCLK_GPI00>, <&pmucru DBCLK_GPI00>;	gpio-controller;	#gpio-cells = <2>;	gpio-ranges = <&pinctrl 0 0 32>;	interrupt-controller;	#interrupt-cells = <2>;};ft5x06: ft5x06@38 {	status = "disabled";	compatible = "edt,edt-ft5306";	reg = <0x38>;	touch-gpio = <&gpio0 RK_PB5 IRQ_TYPE_EDGE_RISING>;	interrupt-parent = <&gpio0>;	interrupts = <RK_PB5 IRQ_TYPE_LEVEL_LOW>;	reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;	touchscreen-size-x = <800>;	touchscreen-size-y = <1280>;	touch_type = <1>;};

gpio-controller property

gpio-controllerThe 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-controllerProperties typically appear as properties of a device node, located in the device node’s property list.

When a device node is identified as a GPIO controller, it typically 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-cellsThe property is used tospecify the encoding method for 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’s value is an integer that represents the number of cells used to encodeGPIO pin descriptors. Usually this value is 2.

12345
ft5x06: ft5x06@38 {	.....	reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;	.....};

The gpio-ranges property

Background

each GPIO Inside the controllereach pin is given a local number, for example, GPIO controller internal numbers: 0, 1, 2, 3, … 31

However,In the entire system or in other hardware modules, these GPIOs may have their own global number external numbers: 100, 101, 102, 103, … 131

In other words,The controller’s internal numbers and external numbers may not be consistent.

To facilitate the use of GPIOs by other devices, a mapping table is needed to map the controller’s internal numbers to the system’s external numbers. This is thegpio-rangesfunction.

gpio-rangesThe property is a device tree property used todescribe GPIO range mapping.. It is typically used to describe GPIO controllers with a large number of GPIO pins, to simplify the encoding and access of GPIO pins.
gpio-rangesThe property is a list containing a series of integer values, each integer value corresponding to a GPIO controller in the device tree. Each integer value in the list provides the following information in a specific order:

  • Starting value of external pin numbering
  • Starting value of the GPIO controller’s internal local numbering
  • Size of the pin range (number of pins)

Example:

12345
ft5x06: ft5x06@38 {	.....	reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;	.....};
  • <&pinctrl>Indicates a reference to a pin controller node named pinctrl
  • 0 0 32represents
    • External pins start from 0
    • Controller local numbering starts from 0
    • A total of 32 pins are mapped.

GPIO description property and gpio-cells

12345
ft5x06: ft5x06@38 {	.....	reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;	.....};

The number of GPIO pin description properties is#gpio-cellsdetermined by, because in the gpio0 node#gpio-cellsThe property is set to 2, so the number of GPIO pin description properties in the device tree above is also 2.

whereRK_PB6Defined in the kernel source directoryinclude/dt-bindings/pinctrl/rockchip.hIn the header file, macro definitions for RK pin names and GPIO numbers are defined.

123456789101112131415161718
#ifndef __DT_BINDINGS_ROCKCHIP_PINCTRL_H__#define __DT_BINDINGS_ROCKCHIP_PINCTRL_H__#define RK_PA0		0#define RK_PA1		1#define RK_PA2		2#define RK_PA3		3#define RK_PA4		4#define RK_PA5		5#define RK_PA6		6#define RK_PA7		7#define RK_PB0		8#define RK_PB1		9#define RK_PB2		10#define RK_PB3		11#define RK_PB4		12#define RK_PB5		13#define RK_PB6		14

GPIO_ACTIVE_LOWDefined in the source directoryinclude/dt-bindings/gpio/gpio.hIn it, it means set to low level; similarlyGPIO_ACTIVE_HIGHIt means setting this GPIO to high level, but this is only a description of the device; the actual setting still needs to match the driver.

Other properties

123456789101112131415
gpio-controller@00000000 {	compatible = "foo";	reg = <0x00000000 0x1000>;	gpio-controller;	#gpio-cells = <2>;	ngpios = <18>;	gpio-reserved-ranges = <0 4>, <12 2>;	gpio-line-names = "MMC-CD", "MMC-WP",			"voD eth", "RST eth", "LED R",			"LED G", "LED B", "col A",			"col B", "col C", "col D",			"NMI button", "Row A", "Row B",			"Row C", "Row D", "poweroff",			"reset";}
  • ngpios property

Specifies The number of GPIO pins supported by the GPIO controller. It represents 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

  • gpio-reserved-ranges property

Defines reserved GPIO ranges. Each range is represented by two integer values enclosed in angle brackets.

The reserved GPIO range means that 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> means that 4 consecutive pins starting from pin 0 are reserved, while <12 2> means that 2 consecutive pins starting from pin 12 are reserved.

  • gpio-line-names property

Defines the names of GPIO pins, separated by commas. Each name corresponds to a GPIO pin. These names are used to identify and recognize the function of each GPIO pin or the device connected to it.

In this example,gpio-line-namesThe property lists the names of multiple GPIO pins, such as “MMC-CD”, “MMC-WP”, “voD eth”, etc. Through these names, the function or purpose of each GPIO pin can be clearly understood.

example

topeet-rk3568 LED schematic
topeet-rk3568 LED schematic

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 look at the file in the kernel source directorydrivers/leds/leds-gpio.cfile, which is the LED driver file, and then find the part related to the compatible match value, as shown below:

1234
static const struct of_device_id of_gpio_leds_match[] = {	{ .compatible = "gpio-leds", },	{},};

It can be seen that the compatible match value is gpio-leds.

Finally, in the kernel source directoryinclude/dt-bindings/pinctrl/rockchip.hIn the header file, macro definitions for RK pin names and GPIO numbers are defined, as shown below:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
/* SPDX-License-Identifier: GPL-2.0-or-later *//* * Header providing constants for Rockchip pinctrl bindings. * * Copyright (c) 2013 MundoReader S.L. * Author: Heiko Stuebner <heiko@sntech.de> */#ifndef __DT_BINDINGS_ROCKCHIP_PINCTRL_H__#define __DT_BINDINGS_ROCKCHIP_PINCTRL_H__#define RK_PA0		0#define RK_PA1		1#define RK_PA2		2#define RK_PA3		3#define RK_PA4		4#define RK_PA5		5#define RK_PA6		6#define RK_PA7		7#define RK_PB0		8#define RK_PB1		9#define RK_PB2		10#define RK_PB3		11#define RK_PB4		12#define RK_PB5		13#define RK_PB6		14#define RK_PB7		15#define RK_PC0		16#define RK_PC1		17#define RK_PC2		18#define RK_PC3		19#define RK_PC4		20#define RK_PC5		21#define RK_PC6		22#define RK_PC7		23#define RK_PD0		24#define RK_PD1		25#define RK_PD2		26#define RK_PD3		27#define RK_PD4		28#define RK_PD5		29#define RK_PD6		30#define RK_PD7		31#define RK_FUNC_GPIO	0#endif

include/dt-bindings/gpio/gpio.hThe file defines the pin polarity setting macro definition

123456789101112131415161718192021222324252627282930313233343536373839404142
/* SPDX-License-Identifier: GPL-2.0 *//* * This header provides constants for most GPIO bindings. * * Most GPIO bindings include a flags cell as part of the GPIO specifier. * In most cases, the format of the flags cell uses the standard values * defined in this header. */#ifndef _DT_BINDINGS_GPIO_GPIO_H#define _DT_BINDINGS_GPIO_GPIO_H/* Bit 0 express polarity */#define GPIO_ACTIVE_HIGH 0#define GPIO_ACTIVE_LOW 1/* Bit 1 express single-endedness */#define GPIO_PUSH_PULL 0#define GPIO_SINGLE_ENDED 2/* Bit 2 express Open drain or open source */#define GPIO_LINE_OPEN_SOURCE 0#define GPIO_LINE_OPEN_DRAIN 4/* * Open Drain/Collector is the combination of single-ended open drain interface. * Open Source/Emitter is the combination of single-ended open source interface. */#define GPIO_OPEN_DRAIN (GPIO_SINGLE_ENDED | GPIO_LINE_OPEN_DRAIN)#define GPIO_OPEN_SOURCE (GPIO_SINGLE_ENDED | GPIO_LINE_OPEN_SOURCE)/* Bit 3 express GPIO suspend/resume and reset persistence */#define GPIO_PERSISTENT 0#define GPIO_TRANSITORY 8/* Bit 4 express pull up */#define GPIO_PULL_UP 16/* Bit 5 express pull down */#define GPIO_PULL_DOWN 32#endif

Therefore, the device tree is as follows:

12345678910111213
/dts-v1/;#include "dt-bindings/pinctrl/rockchip.h"#include "dt-bindings/gpio/gpio.h"/{	model = "This is my devicetree!";	led: led@1 {		compatible = "gpio-leds";		gpios = <&gpio0 RK_PB7 GPIO_ACTIVE_HIGH>;	};};

&gpio0is a reference to the pin controller,RK_PB7is the number or identifier of the pin,GPIO_ACTIVE_HIGHindicates that the active level of this GPIO pin is high level

Comparison with other SoCs

NXP

123456789101112131415161718192021
gpio1: gpio@0209c000 {	compatible = "fsl,inx6ul-gpio", "fsl,imx35-gpio";	reg = <0x0209c000 0x4000>;	interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;	gpio-controller;	#gpio-cells = <2>;	interrupt-controller;	#interrupt-cells = <2>;	edt-ft5x06@38 {		compatible = "edt,edt-ft5306", "edt,edt-ft5x06", "edt,edt-ft5406";		pinctrl-names = "default";		pinctrl-0 = <&ts_int_pin &ts_reset_pin>;		reg = <0x38>;		interrupt-parent = <&gpio1>;		interrupts = <9 0>;		reset-gpios = <&gpio5 9 GPIO_ACTIVE_LOW>;		irq-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;		status = "disabled";	};};

samsung

1234567891011121314151617181920212223
gpio_c: gpioc {	compatible = "gpio-controller";	#gpio-cells = <2>;	interrupt-controller;	#interrupt-cells = <2>;};ft5x06: ft5x06038 {	compatible = "edt,edt-ft5406";	reg = <0x38>;	pinctrl-names = "default";#if defined(RGB_1024x600) || defined(RGB_800x480)	pinctrl-0 = <&tsc2007_irq>;	interrupt-parent = <&gpio_c>;	interrupts = <26 IRQ_TYPE_EDGE_FALLING>;#endif#if defined(LvDs_800×1280) || defined(LvDS_1024x768)	pinctrl-0 = <&gt911_irq>;	interrupt-parent = <&gpio_b>;	interrupts = <29 IRQ_TYPE_EDGE_FALLING>;#endif	reset-gpios = <&gpio_e 30 0>;};

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 the pin multiplexing function, you can switch between these different functions.

Pin multiplexing is implemented through hardware and software methods.

  • Hardware level

The chip design provides multiple function choices for each pin. These functions are usually defined by the chip manufacturer in the chip specification document. By programming registers or switches, a function can be selected to connect to the pin. This hardware-level configuration is usually managed by Pin Controller or Pin Mux Controller It is responsible for management.

Pin definitions in the chip manual
Pin definitions in the chip manual

  • Software level

The operating system or device driver needs to understand and configure the functions of the pins. They use the device tree (DeviceTree) or device tree bindings (Device Tree Bindings) to describe and configure the pin functions. In the device tree, the multiplexing function of a pin can be specified to connect it to a specific hardware interface or function. The operating system or device driver parses the device tree during startup and initializes and configures the pins according to the configuration.

As can be seen from the figure aboveUART4_RX_M1The corresponding pin can be multiplexed into the following 6 functionsLCDC_D16VOP_BT1120_D7GMAC1_RXD0_M0UART4_RX_M1PWM8_M0GPIO3_B1_d, and the corresponding BGA pin label is AG1

Before BGA (Ball Grid Array) packageIn this, pin labels are identifiers used to uniquely identify each pin. These labels are usually defined by the chip manufacturer and provided in the chip’s specification document or datasheet.

The pin labels of a BGA chip are usually composed 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:

RK3568 pin label diagram
RK3568 pin label diagram

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

Multiplexing function diagram created based on BGA positions
Multiplexing function diagram created based on BGA positions

Among them, black boxes represent reserved pins, other colored boxes generally represent power and ground, and 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 devicesIt is part of the device tree, used to pass pin configuration information to the operating system and device drivers during startup, so that the pins can be correctly initialized and controlled.

In the device tree, pinctrl (pin control) uses the concepts of client and server to describe the relationships and configurations of pin control.

Client

1234
node {	pinctrl-names = "default";	pinctrl-0 = <&pinctrl_hog_1>;}

In the above example,pinctrl-namesThe property defines a state name: default.

pinctrl-0The property specifies the pin configuration corresponding to the first state, default.

<&pinctrl_hog_1>is a pin descriptor, which references a namedpinctrl_hog_1pin controller node. This indicates that in the default state, the device’s pin configuration will usepinctrl_hog_1the configuration defined in the node.

12345
node {	pinctrl-names = "default", "wake up";	pinctrl-0 = <&pinctrl_hog_1>;	pinctrl-1 = <&pinctrl_hog_2>;}

In the example,pinctrl-namesThe property defines two state names: default and wake up.

pinctrl-0The property specifies the pin configuration for the first state, default, referencingpinctrl_hog_1the node.

pinctrl-1The property specifies the pin configuration for the second state, wake up, referencingpinctrl_hog_2the node.

This means the device can be in one of two different states, each using a different pin configuration.

1234
node {	pinctrl-names = "default";	pinctrl-0 = <&pinctrl_hog_1 &pinctrl_hog_2>;}

In this example,pinctrl-namesThe property still defines a state name: default.

pinctrl-0The property specifies the pin configuration for the first state, default, but unlike the previous example, it references two pin descriptors:pinctrl_hog_1andpinctrl_hog_2

This indicates that in the default state, the device’s pin configuration will usepinctrl_hog_1andpinctrl_hog_2the configuration defined in the two nodes.

This approach can combine the configurations of multiple pin controllers to meet the pin requirements of a specific state.

Server

The server side 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 side defines pinctrl nodes in the device tree, which contain definitions of pin groups and pin descriptors.

Here, taking Rockchip’s RK3568 as an example to explain the pinctrl server side, the Rockchip BSP engineers, in order to facilitate users to set pin multiplexing relationships through pinctrl, wrote the configurations containing all multiplexing relationships in the kernel directory’sarch/arm64/boot/dts/rockchip/rk3568-pinctrl.dtsidevice tree:

123456789101112131415161718192021222324252627282930313233
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)/* * Copyright (c) 2020 Rockchip Electronics Co., Ltd. */#include <dt-bindings/pinctrl/rockchip.h>#include "rockchip-pinconf.dtsi"/* * This file is auto generated by pin2dts tool, please keep these code * by adding changes at end of this file. */&pinctrl {	acodec {		/omit-if-no-ref/		acodec_pins: acodec-pins {			rockchip,pins =				/* acodec_adc_sync */				<1 RK_PB1 5 &pcfg_pull_none>,				/* acodec_adcclk */				<1 RK_PA1 5 &pcfg_pull_none>,				/* acodec_adcdata */				<1 RK_PA0 5 &pcfg_pull_none>,				/* acodec_dac_datal */				<1 RK_PA7 5 &pcfg_pull_none>,				/* acodec_dac_datar */				<1 RK_PB0 5 &pcfg_pull_none>,				/* acodec_dacclk */				<1 RK_PA3 5 &pcfg_pull_none>,				/* acodec_dacsync */				<1 RK_PA5 5 &pcfg_pull_none>;		};	};

In the pinctrl node are the multiplexing functions of each node. Then we take the pin multiplexing of uart4 as an example

12345678910111213141516171819202122232425262728293031323334
uart4 {		/omit-if-no-ref/		uart4m0_xfer: uart4m0-xfer {			rockchip,pins =				/* uart4_rxm0 */				<1 RK_PA4 2 &pcfg_pull_up>,				/* uart4_txm0 */				<1 RK_PA6 2 &pcfg_pull_up>;		};		/omit-if-no-ref/		uart4m0_ctsn: uart4m0-ctsn {			rockchip,pins =				/* uart4m0_ctsn */				<1 RK_PA7 2 &pcfg_pull_none>;		};		/omit-if-no-ref/		uart4m0_rtsn: uart4m0-rtsn {			rockchip,pins =				/* uart4m0_rtsn */				<1 RK_PA5 2 &pcfg_pull_none>;		};		/omit-if-no-ref/		uart4m1_xfer: uart4m1-xfer {			rockchip,pins =				/* uart4_rxm1 */				<3 RK_PB1 4 &pcfg_pull_up>,				/* uart4_txm1 */				<3 RK_PB2 4 &pcfg_pull_up>;		};	};

where<3 RK_PB1 4 &pcfg_pull_up>and<3 RK_PB2 4 &pcfg_pull_up>They respectively indicate setting the PB1 pin of GPIO3 to function 4, setting PB2 of GPIO3 to function 4 as well, and the electrical properties will be set to pull-up. By looking up the schematic, we can find that the two pins are at positions AG1 and AF2 in the BGA package.

Function 4
Function 4

It can be seen that function 4 corresponds to the transmit and receive pins of UART4. The pinctrl server-side configuration corresponds one-to-one with the pin multiplexing functions in the datasheet.

Then if you want to setRK_PB1andRK_PB2How to set it to GPIO function? From the figure above, GPIO corresponds to function 0, so you can set it via the following pinctrl contentRK_PB1andRK_PB2to GPIO function (in fact, if the pin is not multiplexed, 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

12345
&uart4{	status = "okay";	pinctrl-names = "default";	pinctrl-0 = <&uart4m1_xfer>;}

By referencing the server’s pin descriptor in the client, the device tree can associate the pin configurations of the client and server.

Writing a pinctrl example

In the SDK source directorydevice/rockchip/rk356x/BoardConfig-rk3568-evb1-ddr4-v10.mkThe default configuration file shows that the compiled device tree isrk3568-evb1-ddr4-v10-linux.dts, and the list of inclusion relationships between device trees is as follows:

Device tree inclusion relationships
Device tree inclusion relationships

The above device tree is for a 4.x version kernel

The LED has been properly configured in the device tree:

arch/arm64/boot/dts/rockchip/topeet-rk3568-linux.dts

12345678910111213141516171819
//LED#if LED_PWM        leds {                                                                                              		   		compatible = "pwm-leds";                work {                        pwms = <&pwm0 0 500000 0>;                        linux,default-trigger = "heartbeat";                        default-state = "on";                };        };#else        leds {                compatible = "gpio-leds";                work {                        gpios = <&gpio0 RK_PB7 GPIO_ACTIVE_HIGH>;                        linux,default-trigger = "heartbeat";                        default-state = "on";                };        };#endif

pinctrl is not configured here, so why can the LED still work normally? As we mentioned above, in the RK3568, when the GPIO0_B7 pin is not multiplexed to any function, it defaults to the GPIO function, so even without pinctrl here, the LED function can work normally.

We can write our own LED node:

123456
my_led: led {	compatible = "topeet,led";	gpios = <&gpio0 RK_PB7 GPIO_ACTIVE_HIGH>;	pinctrl-names = "default";	pinctrl-0 = <&rk_led_gpio>;}

Server

12345
rk_led{	rk_led_gpio:rk-led-gpio {		rockchip,pins = <0 RK_PB7 RK_FUNC_GPIO &pcfg_pull_none>;	};};

DTB file format parsing

Device Tree Blob (DTB) format is a flat binary encoding of device tree dataIt is used to exchange device tree data between software programs. For example, when booting an operating system, firmware passes the DTB to the operating system 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 Reserved BlockStructure BlockandString blockThese should appear in this order in the flattened device tree. Therefore, the device tree structure as a whole, when loaded into a memory address, will resemble the following figure.

DTB
DTB

Take the following device tree file as an example.

1234567891011121314151617181920212223242526272829303132333435363738394041
/dts-v1/;/ {	model = "This is my devicetree!";	#address-cells = <1>;	#size-cells = <1>;	chosen {		bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";	};	cpu1: cpu@1 {		device_type = "cpu";		compatible = "arm,cortex-a35", "arm,armv8";		reg = <0x0 0x1>;	};	aliases {		led1 = "/gpio@22020101";	};	node1 {		#address-cells = <1>;		#size-cells = <1>;		gpio@22020102 {			reg = <0x20220102 0x40>;		};	};	node2 {		node1-child {			pinnum = <01234>;		};	};	gpio@22020101 {		compatible = "led";		reg = <0x20220101 0x40>;		status = "okay";	};};

After compiling to DTB, open it with Binary Viewer:

Binary Viewer
Binary Viewer

123456789101112131415161718192021
struct fdt_header {    uint32_t magic;  // Magic number of the device tree header    uint32_t totalsize; // Total size of the device tree file    uint32_t off_dt_struct; // Offset of the device tree structure (node data) relative to the beginning of the file    uint32_t off_dt_strings; // Offset of the device tree strings table relative to the beginning of the file    uint32_t off_mem_rsvmap; // Offset of the memory reservation map relative to the beginning of the file    uint32_t version; // Device tree version number    uint32_t last_comp_version; // Last compatible version number    uint32_t boot_cpuid_phys; // Physical ID of the boot CPU    uint32_t size_dt_strings; // Size of the device tree strings table    uint32_t size_dt_struct; // Size of the device tree structure (node data)};
Core Function ClassificationfieldKey Notes
File IdentificationmagicFixed magic number of DTB (0xd00dfeed, big-endian), used to verify file validity.
Size and Offset PositioningtotalsizeTotal size of DTB (including all blocks and gaps), determines the file read range.
off_dt_structOffset of the structure block (stores hardware nodes/attributes), the core entry point for parsing hardware descriptions.
off_dt_stringsOffset of the strings block (stores property names), used with indexes in the structure block to obtain property names.
off_mem_rsvmapOffset of the memory reservation block (marks non-allocatable memory regions), to avoid kernel memory conflicts.
size_dt_stringsString block length, used to read the complete set of property names
size_dt_structStructure block length, used to read the complete hardware description data
Version compatibilityversionThe format version that the current DTB follows, which determines the parsing logic
last_comp_versionThe minimum backward-compatible version, ensuring compatibility across different kernel versions
Hardware associationboot_cpuid_physPhysical 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

The Memory Reserved Block is a list of protected and reserved physical memory regions for client programs.

These reserved regions should not be used for general memory allocation, but rather to protect important data structures from being overwritten by client programs.

The purpose of the Memory Reserved Block is to ensure that specific memory regions are not modified or used while client programs are running. . Since no Memory Reserved Block is set in the example device tree, the corresponding regions are all 0.
  • 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 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

1234
struct fdt_reserve_entry {	uint64_t address;	uint64_t size;};

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 upper 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 boot program explicitly indicates that access is allowed. The boot program may use specific methods to indicate that client programs can access parts of the reserved memory. The boot program may describe the specific uses of 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 while client programs are running. This ensures that the boot program 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 series of token sequences 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_NODEMark Indicates the start of a node. . It is followed by the unit name of the node as additional data. The node name is stored as a null-terminated string, and may include the unit address. After the node name, it may be necessary to pad with zero bytes for alignment, followed by the next token, which can be except…FDT_ENDAny marks other than.

  • FDT_END_NODE (0x00000002)

FDT_END_NODEMark Indicates the end of a node . This marker has no additional data, immediately followed by the next marker, which can be anything exceptFDT_PROPAny marks other than.

  • FDT_PROP (0x00000003)

FDT_PROPMark Indicates the start of a property in the device tree. It is followed by additional data describing the attribute, which first consists of the attribute’s length and name, represented as the following C structure.

1234
struct {	uint32_t len;	uint32_t nameoff;}

Length indicates the byte length of the attribute value, and the name offset points to the location in the string block where the attribute name is stored.
After this structure, the value of the attribute is given as a byte string. After the attribute value, it may be necessary to pad with zero bytes for alignment, then the next token, which can be anything exceptFDT_ENDAny marks other than.

  • FDT_NOP (0x00000004)

FDT_NOPThe token can be ignored by programs that parse the device tree. This token has no additional data; it is immediately followed by the next token, which can be any valid token. UseFDT_NOPTokens can override attribute or node definitions in the tree, thereby removing them from the tree without moving other parts of the device tree blob.

tree structure

The structure of the device tree is represented in the form of a linear tree. Each node isFDT_BEGIN_NODEMark start, byFDT_END_NODEMark end.

The attributes and child nodes of the node are inFDT_END_NODEPreviously stated, therefore the child node’sFDT_BEGIN_NODEandFDT_END_NODEThe token is nested within the parent node’s token.

End of structure block

Structure block as a singleFDT_ENDEnd marker. This marker has no additional data; it is at the end of the structure block and is the last marker in the structure block.
FDT_ENDThe bytes after the marker should be located at the offset of the start of the structure block, which is equal to the offset in the device tree blob header.size_dt_structThe value of the field.

String block

The string 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 in the string block.

  • String concatenation

Strings in the string blockConcatenated 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 makes all strings in the string block form a continuous character sequence.

  • Offset reference

In the structure block,the name of a property references the corresponding string in the string block by an offset. The offset is an unsigned integer value that represents the position of the string in the string block. By using offset references, the device tree can save space and become more flexible when property names change, because only the offset needs to be updated, without modifying the property references in the structure block.

  • Alignment constraint

The string block has no alignment constraint, which means it can appear at any offset in the device tree blob. This makes the position of the string block flexible in the device tree blob and can be adjusted as needed without affecting the parsing and processing of the device tree.

The string block is the part of the device tree used to store property names. It is formed by concatenated strings and is referenced in the structure block by offsets. The flexible position of the string block makes the device tree representation more compact and extensible.

dtb expansion into device_node

dtb expansion flowchart
dtb expansion flowchart

  • U-Boot loading

U-Boot (Universal Bootloader) is a commonly used open-source boot loader for booting embedded systems. During system startup, U-Boot willboot.imgload the kernel and device tree binary files into a specific address in system memory.

  • Kernel initialization

After U-Boot loads the kernel and device tree binary files to a specific address in system memory, control is transferred to the kernel. During kernel initialization, the device tree binary file is parsed and expanded into a data structure that the kernel can recognize, so that the kernel can 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 in the kernel (device_nodestruct device_node). The kernel reads the contents of the device tree binary file and, based on the description information in the device tree, constructs device tree data structures, such as device nodes, interrupt controllers, registers, clocks, etc. These device tree data structures will be used to manage and configure hardware resources during kernel runtime.

struct device_nodedefined as follows

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
// include/linux/of.htypedef u32 phandle;typedef u32 ihandle;struct property {	char	*name; // Attribute name	int	length;// Attribute value length (bytes)	void	*value;// Attribute value pointer	struct property *next;// Next attribute node pointer#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)	unsigned long _flags;// Attribute flags#endif#if defined(CONFIG_OF_PROMTREE)	unsigned int unique_id;// Attribute unique identifier#endif#if defined(CONFIG_OF_KOBJ)	struct bin_attribute attr;// Kernel object binary attribute#endif};#if defined(CONFIG_SPARC)struct of_irq_controller;#endifstruct device_node {	const char *name; //Device node name	phandle phandle; // Device node handle	const char *full_name;// Device node full name	struct fwnode_handle fwnode;// Device node firmware node handle	struct	property *properties;// Device node attribute list	struct	property *deadprops;	/* removed properties */ // Deleted attribute list	struct	device_node *parent; // Parent device node pointer	struct	device_node *child; // Child device node pointer	struct	device_node *sibling; // Sibling device node pointer#if defined(CONFIG_OF_KOBJ)	struct	kobject kobj; // Kernel object (for sysfs)#endif	unsigned long _flags; // Device node flags	void	*data; // Device node related data pointer#if defined(CONFIG_SPARC)	unsigned int unique_id;// Device node unique identifier	struct of_irq_controller *irq_trans;// Device node interrupt controller#endif};

Source code analysis of the DTB parsing process

init/main.c

In the kernelstart_kernel()call insetup_arch(&command_line);Perform architecture-specific initialization

12345678910111213141516171819202122232425262728293031323334
// init/main.casmlinkage __visible void __init __no_sanitize_address start_kernel(void){	char *command_line;	char *after_dashes;	set_task_stack_end_magic(&init_task);// Set the task stack magic number	smp_setup_processor_id();// Set processor ID	debug_objects_early_init();// Initialize debug objects	cgroup_init_early();// Initialize cgroup (control group)	local_irq_disable();// Disable local interrupts	early_boot_irqs_disabled = true;// Mark interrupts as disabled during early boot	/*	* Interrupts are still disabled。Perform necessary setup,Then enable them。	*/	boot_cpu_init();// Initialize boot CPU	page_address_init();// Set up page address	pr_notice("%s", linux_banner);// Print Linux kernel version information	early_security_init();	setup_arch(&command_line);// Architecture-specific initialization	setup_boot_config(command_line);	setup_command_line(command_line);// Set up command line parameters	setup_nr_cpu_ids();// Set number of CPUs	setup_per_cpu_areas();// Set up per-CPU areas	smp_prepare_boot_cpu();	/* arch-specific boot-cpu hooks */	boot_cpu_hotplug_init();// Initialize the hotplug boot CPU	build_all_zonelists(NULL);// Build all memory zone lists	page_alloc_init();// Initialize the page allocator    ...}

arch/arm64/kernel/setup.c

And in thesetup_arch()Called at line 21 belowsetup_machine_fdt(__fdt_pointer);Set up the machine’s FDT (platform device tree)

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
// arch/arm64/kernel/setup.cvoid __init __no_sanitize_address setup_arch(char **cmdline_p){	init_mm.start_code = (unsigned long) _text;	init_mm.end_code   = (unsigned long) _etext;	init_mm.end_data   = (unsigned long) _edata;	init_mm.brk	   = (unsigned long) _end;	*cmdline_p = boot_command_line;	/*	 * If know now we are going to need KPTI then use non-global	 * mappings from the start, avoiding the cost of rewriting	 * everything later.	 */	arm64_use_ng_mappings = kaslr_requires_kpti();	early_fixmap_init();// Initialize early fixmap	early_ioremap_init();// Initialize early ioremap	setup_machine_fdt(__fdt_pointer);// Set up the machine's FDT (platform device tree)	/*	 * Initialise the static keys early as they may be enabled by the	 * cpufeature code and early parameters.	 */	jump_label_init();// Initialize static keys, which may be enabled early by cpufeature code and early parameters	parse_early_param();	/*	 * Unmask asynchronous aborts and fiq after bringing up possible	 * earlycon. (Report possible System Errors once we can report this	 * occurred).	 */	 // After starting the possible early console, unmask asynchronous interrupts and FIQ (we can immediately report system errors that occur)	local_daif_restore(DAIF_PROCCTX_NOIRQ);	/*	 * TTBR0 is only used for the identity mapping at this stage. Make it	 * point to zero page to avoid speculatively fetching new entries.	 */	cpu_uninstall_idmap();// At this stage, TTBR0 is only used for identity mapping. Point it to the zero page to avoid speculative new entry fetches.	xen_early_init();// Early initialization of the Xen platform	efi_init();// EFI platform initialization	if (!efi_enabled(EFI_BOOT) && ((u64)_text % MIN_KIMG_ALIGN) != 0)	     pr_warn(FW_BUG "Kernel image misaligned at boot, please fix your bootloader!");	arm64_memblock_init();// ARM64 memory block initialization	paging_init();// Paging initialization	acpi_table_upgrade();// ACPI table upgrade	/* Parse the ACPI tables for possible boot-time configuration */	acpi_boot_table_init();// Parse ACPI tables for possible boot-time configuration	if (acpi_disabled)		unflatten_device_tree();// Unflatten device tree	bootmem_init();// Boot memory initialization	...}

setup_machine_fdt(__fdt_pointer)

__fdt_pointerIt is the address where the dtb binary is loaded into memory, passed by the bootloader to the kernel via the x0 register

arch/arm64/kernel/head.S

123456789
// arch/arm64/kernel/head.SSYM_CODE_START_LOCAL(preserve_boot_args)	mov	x21, x0				// x21=FDT	...SYM_FUNC_START_LOCAL(__primary_switched)	...	str_l	x21, __fdt_pointer, x5		// Save FDT pointer	...

arch/arm64/kernel/setup.c

12345678910111213141516171819202122232425262728293031323334
// arch/arm64/kernel/setup.c// Initialize the device tree that sets up the machinestatic void __init setup_machine_fdt(phys_addr_t dt_phys){	int size;	// Map the device tree physical address to the kernel virtual address space	void *dt_virt = fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL);	const char *name;	if (dt_virt)// If the mapping succeeds		memblock_reserve(dt_phys, size);// Reserve the memory region occupied by the device tree	if (!dt_virt || !early_init_dt_scan(dt_virt)) {// If the device tree mapping fails or the device tree parsing fails		pr_crit("\n"			"Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n"			"The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"			"\nPlease check your bootloader.",			&dt_phys, dt_virt);		while (true)// Infinite loop, waiting for the system to crash			cpu_relax();	}	/* Early fixups are done, map the FDT as read-only now */	fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);// Map the device tree as read-only	name = of_flat_dt_get_machine_name();// Get the machine name from the device tree	if (!name)		return;	pr_info("Machine model: %s\n", name);// Output the machine model information	dump_stack_set_arch_desc("%s (DT)", name);// Set the architecture description for stack dump to the machine model}

Line 13 aboveearly_init_dt_scanThe function performs compatibility and integrity validation on the device tree. It may check the consistency markers, version information, and the presence of required nodes and properties in the device tree. If validation fails, the function returns false. The function is as follows:

12345678910111213
// drivers/of/fdt.cbool __init early_init_dt_scan(void *params){	bool status;	status = early_init_dt_verify(params);// Validate the compatibility and integrity of the device tree	if (!status)		return false;	early_init_dt_scan_nodes();// scan device tree nodes	return true;}

early_init_dt_scanFirst, it callsearly_init_dt_verifyValidate the device tree to verify its compatibility and integrity

1234567891011121314151617181920
// drivers/of/fdt.cbool __init early_init_dt_verify(void *params){	if (!params)// Verify whether the passed-in parameter is empty.		return false;	/* check device tree validity */    // Check the validity of the device tree header.	// If the device tree header is invalid, return false.	if (fdt_check_header(params))		return false;	/* Setup flat device-tree pointer */	initial_boot_params = params;// Set the pointer to the device tree to the passed-in parameter.    // Calculate the CRC32 checksum of the device tree.	// and store the result in the global variable of_fdt_crc32	of_fdt_crc32 = crc32_be(~0, initial_boot_params,				fdt_totalsize(initial_boot_params));	return true;}

Finally,early_init_dt_scancallsearly_init_dt_scan_nodesscan device tree nodes

12345678910111213141516
// drivers/of/fdt.cvoid __init early_init_dt_scan_nodes(void){	int rc = 0;	/* Retrieve various information from the /chosen node */	rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);/* retrieve various information from the /chosen node. */	if (!rc)		pr_warn("No chosen node found, continuing without\n");	/* Initialize {size,address}-cells info */	of_scan_flat_dt(early_init_dt_scan_root, NULL);/* initialize {size,address}-cells information. */	/* Setup memory, calling early_init_dt_add_memory_arch */	of_scan_flat_dt(early_init_dt_scan_memory, NULL);/* set memory information, call early_init_dt_add_memory_arch function */}

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 adata structure that represents the device tree in a compact form.,It does not use a tree structure.,but uses a linear structure.,to save memory space.

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.

1234567891011121314
// drivers/of/fdt.cvoid __init unflatten_device_tree(void){	/* Parse the device tree */	__unflatten_device_tree(initial_boot_params, NULL, &of_root,				early_init_dt_alloc_memory_arch, false);	/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */	/* Get pointers to the "/chosen" and "/aliases" nodes for global use. */	of_alias_scan(early_init_dt_alloc_memory_arch);	/* Run unit tests for the device tree */	unittest_unflatten_overlay_base();}

This function is mainly used to parse the device tree and store the parsed device tree in a global variable.of_rootMiddle.

The function first calls__unflatten_device_treefunction to perform the device tree parsing operation. The parsed device tree will be stored usingof_rootpointer.

Next, the function callsof_alias_scanfunction. This function is used to scan the/chosenand/aliasesnodes in the device tree, and allocate memory for them. In this way, other parts of the code can access these nodes through global variables.

Finally, the function callsunittest_unflatten_overlay_basefunction to run unit tests for the device tree.

__unflatten_device_tree

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
//drivers/of/fdt.c/** * __unflatten_device_tree - create tree of device_nodes from flat blob * * unflattens a device-tree, creating the * tree of struct device_node. It also fills the "name" and "type" * pointers of the nodes so the normal device-tree walking functions * can be used. * @blob: The blob to expand * @dad: Parent device node * @mynodes: The device_node tree created by the call * @dt_alloc: An allocator that provides a virtual address to memory * for the resulting tree * @detached: if true set OF_DETACHED on @mynodes * * Returns NULL on failure or the memory chunk containing the unflattened * device tree on success. */void *__unflatten_device_tree(const void *blob,			      struct device_node *dad,			      struct device_node **mynodes,			      void *(*dt_alloc)(u64 size, u64 align),			      bool detached){	int size;	void *mem;	pr_debug(" -> unflatten_device_tree()\n");	if (!blob) {		pr_debug("No device tree pointer\n");		return NULL;	}	pr_debug("Unflattening device tree:\n");	pr_debug("magic: %08x\n", fdt_magic(blob));	pr_debug("size: %08x\n", fdt_totalsize(blob));	pr_debug("version: %08x\n", fdt_version(blob));	if (fdt_check_header(blob)) {		pr_err("Invalid device tree blob header\n");		return NULL;	}	/* First pass, scan for size */	size = unflatten_dt_nodes(blob, NULL, dad, NULL);/* First pass scan, calculate size */	if (size < 0)		return NULL;	size = ALIGN(size, 4);	pr_debug("  size is %d, allocating...\n", size);	/* Allocate memory for the expanded device tree */	mem = dt_alloc(size + 4, __alignof__(struct device_node));/* Allocate memory for the expanded device tree */	if (!mem)		return NULL;	memset(mem, 0, size);	*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);	pr_debug("  unflattening %p...\n", mem);	/* Second pass, do actual unflattening */	unflatten_dt_nodes(blob, mem, dad, mynodes);/* Second pass scan, actually expand the device tree */	if (be32_to_cpup(mem + size) != 0xdeadbeef)		pr_warn("End of tree marker overwritten: %08x\n",			be32_to_cpup(mem + size));	if (detached && mynodes) {		of_node_set_flag(*mynodes, OF_DETACHED);		pr_debug("unflattened tree is detached\n");	}	pr_debug(" <- unflatten_device_tree()\n");	return mem;}

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 to expand 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 to expand the device tree. It accepts four parameters:

  • blob (device tree data block pointer)
  • start (starting address of the current node, initially NULL)
  • dad (parent node pointer, andunflatten_device_treeNULL is passed in the function)
  • mynodes (pointer to an array of node pointers, initially NULL).

After the first scan is completed,unflatten_dt_nodesThe function willreturn the memory size required to unflatten the device tree,then align the size,and allocate memory for the unflattened device tree

Line 65: called againunflatten_dt_nodes(blob, mem, dad, mynodes);The function performs a second pass. Through this process,The second pass expands the nodes in the device tree data block into real device nodes,and fills in the node names、and information such as type and propertiesThis completes the unflattening process of the device tree.

unflatten_dt_nodes()

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
// drivers/of/fdt.c/** * unflatten_dt_nodes - Alloc and populate a device_node from the flat tree * @blob: The parent device tree blob * @mem: Memory chunk to use for allocating device nodes and properties * @dad: Parent struct device_node * @nodepp: The device_node tree created by the call * * It returns the size of unflattened device tree or error code */static int unflatten_dt_nodes(const void *blob,			      void *mem,			      struct device_node *dad,			      struct device_node **nodepp){	struct device_node *root;// root node	int offset = 0, depth = 0, initial_depth = 0;// offset, depth, and initial depth#define FDT_MAX_DEPTH	64 // maximum depth	struct device_node *nps[FDT_MAX_DEPTH];// device node array	void *base = mem;// base address, used to calculate offsets	bool dryrun = !base;// whether it is only a simulated run without actual processing	if (nodepp)		*nodepp = NULL;// If the pointer is not null, set it to a null pointer	/*	 * We're unflattening device sub-tree if @dad is valid. There are	 * possibly multiple nodes in the first level of depth. We need	 * set @depth to 1 to make fdt_next_node() happy as it bails	 * immediately when negative @depth is found. Otherwise, the device	 * nodes except the first one won't be unflattened successfully.	 */	/*	* if @dad valid,it indicates that the device subtree is being unflattened。	* At the first depth level, there may be multiple nodes。	* will @depth Set to 1,so that fdt_next_node() works properly。	* When a negative value is found @depth when,the function will exit immediately。	* otherwise,Device nodes other than the first node will not be unflattened successfully。	*/	if (dad)		depth = initial_depth = 1;	root = dad;// The root node is @dad	nps[depth] = dad;// Add the root node to the device node array	for (offset = 0;	     offset >= 0 && depth >= initial_depth;	     offset = fdt_next_node(blob, offset, &depth)) {		if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH - 1))			continue;		// If CONFIG is not enabled_OF_KOBJ and the node is unavailable, then skip the node		if (!IS_ENABLED(CONFIG_OF_KOBJ) &&		    !of_fdt_device_is_available(blob, offset))			continue;		// Fill in node information and add child nodes to the device node array		if (!populate_node(blob, offset, &mem, nps[depth],				   &nps[depth+1], dryrun))			return mem - base;		if (!dryrun && nodepp && !*nodepp)			*nodepp = nps[depth+1];// Assign the child node pointer to @nodepp		if (!dryrun && !root)			root = nps[depth+1];// If the root node is NULL, set the child node as the root node	}	if (offset < 0 && offset != -FDT_ERR_NOTFOUND) {		pr_err("Error %d processing FDT\n", offset);		return -EINVAL;	}	/*	 * Reverse the child list. Some drivers assumes node order matches .dts	 * node order	 */	if (!dryrun)// Reverse the child node list. Some drivers assume the node order matches the order in the .dts file		reverse_nodes(root);	return mem - base;// Return the number of bytes processed}

fdt_next_node()The function is used to traverse the nodes of the device tree.

Starting from offset 0, as long as the offset is greater than or equal to 0 and the depth is greater than or equal to the initial depth, execute the loop.

Each iteration of the loop processes one device tree node. In each iteration, first check whether the depth exceeds the maximum depthFDT_MAX_DEPTHIf it exceeds, skip the node.

If not enabledCONFIG_OF_KOBJand the node is unavailable (viaof_fdt_device_is_available()function), then skip the node.

Then callpopulate_node()the function to fill in node information and add child nodes to the device node arraynpsMiddle.populate_node()The function definition is as follows

populate_node

This function parses the device node’s properties and allocates memory as needed to store the property values.

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
// drivers/of/fdt.cstatic bool populate_node(const void *blob,			  int offset,			  void **mem,			  struct device_node *dad,			  struct device_node **pnp,			  bool dryrun){	struct device_node *np;// Device node pointer	const char *pathp;// Node path string pointer	unsigned int l, allocl;// Path string length and allocated memory size	pathp = fdt_get_name(blob, offset, &l);// Get the node path and length	if (!pathp) {		*pnp = NULL;		return false;	}	allocl = ++l;// Allocate memory of size path length plus one to store the node path string	np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,				__alignof__(struct device_node));// Allocate device node memory	if (!dryrun) {		char *fn;		of_node_init(np);// Initialize device node		np->full_name = fn = ((char *)np) + sizeof(*np);// Set the full path name of the device node		memcpy(fn, pathp, l);// Copy the node path string into the full path name of the device node		if (dad != NULL) {			np->parent = dad;// Set the parent node of the device node			np->sibling = dad->child;// Set the sibling node of the device node			dad->child = np;// Add the device node as a child of the parent node		}	}	populate_properties(blob, offset, mem, np, pathp, dryrun);// Fill in the attribute information of the device node	if (!dryrun) {		np->name = of_get_property(np, "name", NULL);// Get the name attribute of the device node		if (!np->name)			np->name = "<NULL>";	}	*pnp = np;// Assign the device node pointer to *pnp	return true;}

Beforepopulate_nodeIn the function, it first callsunflatten_dt_allocThe function allocates memory for the device node.

The allocated memory size issizeof(struct device_node) + alloclbytes, and use__alignof__(struct device_node)alignment, then callpopulate_propertiesThe function fills in the attribute information of the device node.

Herenp->name = of_get_property(np, "name", NULL);Get the name attribute of the device node stored instruct device_nodeofnamein the attribute

device_node to platform_device

In the platform bus model, the device part is described usingplatform_devicea structure 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_deviceThe node can be/sys/bus/platform/devicesviewed below.

Conversion rules

The rules are as follows:

  • Traverse the child nodes under the root node that contain the compatible property, and for each child node, create a correspondingplatform_device
  • Traverse the nodes under the root node whose compatible property issimple-bussimple-mfdorisanodes and their child nodes. If their child nodes contain a compatible property value, a correspondingplatform_device
  • Check whether the node’s compatible property containsarmorprimecell. If so, do not convert the node toplatform_device, but instead identify it as an AMBA device.

For rule 2:

Some nodes are themselves “bus” or “container” but do not directly correspond to hardware devices; instead, they are used to organize child devices. The compatible values of these nodes are usually:

  • simple-bus: generic simple bus (e.g., an ordinary memory-mapped bus outside AMBA APB/AHB)
  • simple-mfd: multi-function device (Multi-Function Device) container
  • isa: ISA bus (legacy PC)

For such nodes, they themselves usually do not createplatform_device, but will recursively traverse all their child nodes; as long as a child node has a compatible property, create one for that child nodeplatform_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(such asarm,pl011arm,pl081), it indicates that it is an ARM PrimeCell peripheral, belonging to an AMBA bus device (APB/AHB). Such devices:

  • will not be registered asplatform_device, but instead by the AMBA bus subsystem (amba_bus_type) specially handled
  • useamba_devicestructure, rather thanplatform_device

Reason: ARM PrimeCell devices have a standard register layout (such as CID/PID), and the AMBA subsystem automatically detects and verifies them, compared to genericplatform_devicemore secure and efficient.

example

Example 1:

123456789101112131415161718192021222324252627282930313233343536373839
/dts-v1/;/ {	model = "This is my devicetree!";	#address-cells = <1>;	#size-cells = <1>;	chosen {		bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";	};	cpu1: cpu@1 {		device_type = "cpu";		compatible = "arm,cortex-a35", "arm,armv8";		reg = <0x0 0x1>;	};	aliases {		led1 = "/gpio@22020101";	};	node1 {		#address-cells = <1>;		#size-cells = <1>;		gpio@22020102 {			reg = <0x20220102 0x40>;		};	};	node2 {		node1-child {			pinnum = <01234>;		};	};	gpio@22020101 {		compatible = "led";		reg = <0x20220101 0x40>;		status = "okay";	};}

In the device tree above, there arechosencpu1: cpu@1aliasesnode1node2gpio@22020101these six nodes. Among them, the first five nodes do not have a compatible property, so they will not be converted toplatform_device, and the last onegpio@22020101node conforms to Rule 1, is under the root node, and has a compatible property, so it will eventually be converted toplatform_device

Example 2:

1234567891011121314151617181920212223242526272829303132333435363738394041
/dts-v1/;/ {	model = "This is my devicetree!";	#address-cells = <1>;	#size-cells = <1>;	chosen {		bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";	};	cpu1: cpu@1 {		device_type = "cpu";		compatible = "arm,cortex-a35", "arm,armv8";		reg = <0x0 0x1>;	};	aliases {		led1 = "/gpio@22020101";	};	node1 {		#address-cells = <1>;		#size-cells = <1>;		compatible = "simple-bus";		gpio@22020102 {			reg = <0x20220102 0x40>;		};	};	node2 {		node1-child {			pinnum = <01234>;		};	};	gpio@22020101 {		compatible = "led";		reg = <0x20220101 0x40>;		status = "okay";	};};

Here, in thenode1node, a compatible property is added, but the value of this compatible property issimple-bus, we need to continue looking at its child nodes. The child nodegpio@22020102does not have a compatible property value, so thenode1node will not be converted.

Example 3:

12345678910111213141516171819202122232425262728293031323334353637383940414243
/dts-v1/;/ {	model = "This is my devicetree!";	#address-cells = <1>;	#size-cells = <1>;	chosen {		bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";	};	cpu1: cpu@1 {		device_type = "cpu";		compatible = "arm,cortex-a35", "arm,armv8";		reg = <0x0 0x1>;	};	aliases {		led1 = "/gpio@22020101";	};	node1 {		#address-cells = <1>;		#size-cells = <1>;		compatible = "simple-bus";		gpio@22020102 {			compatible = "gpio";			reg = <0x20220102 0x40>;		};	};	node2 {		node1-child {			pinnum = <01234>;		};	};	gpio@22020101 {		compatible = "led";		reg = <0x20220101 0x40>;		status = "okay";	};};

Here, in the child node of node1gpio@22020102a compatible property is added. The compatible property value of node1 issimple-bus, then we need to continue looking at its child nodes. The child nodegpio@22020102has a compatible property value of gpio, so thegpio@22020102node will be converted toplatform_device

Example 4:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
/dts-v1/;/ {	model = "This is my devicetree!";	#address-cells = <1>;	#size-cells = <1>;	chosen {		bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";	};	cpul: cpu@1 {		device_type = "cpu";		compatible = "arm,cortex-a35", "arm,armv8";		reg = <0x0 0x1>;		amba {			compatible = "simple-bus";			#address-cells = <2>;			#size-cells = <2>;			ranges;			dmac_peri: dma-controller@ff250000 {				compatible = "arm,p1330", "arm,primecell";				reg = <0x0 0xff250000 0x0 0x4000>;				interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>,				<GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>;				#dma-cells = <1>;				arm,pl330-broken-no-flushp;				arm,p1330-periph-burst;				clocks = <&cru ACLK DMAC_PERI>;				clock-names = "apb_pclk";			};			dmac_bus: dma-controller@ff600000 {				compatible = "arm,p1330", "arm,primecell";				reg = <0x0 0xff600000 0x0 0x4000>;				interrupts = <GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH>,				<GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>;				#dma-cells = <1>;				arm,pl330-broken-no-flushp;				arm,pl330-periph-burst;				clocks = <&cru ACLK_DMAC_BUS>;				clock-names = "apb_pclk";			};		};	};};

The compatible value of the amba node issimple-bus, so it will not be converted toplatform_device, but instead serves as a parent node to organize other devices, so we need to look at 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()

Let’s first look atof_platform_default_populate_init, it usesarch_initcall_sync(of_platform_default_populate_init);registers the function to be called during the startup phase

123456789101112131415161718192021222324252627282930313233343536
// drivers/of/platform.cstatic int __init of_platform_default_populate_init(void){	struct device_node *node;	// Suspend device link supplier synchronization state	device_links_supplier_sync_state_pause();	// If the device tree has not been populated, return an error code.	if (!of_have_populated_dt())		return -ENODEV;	/*	 * Handle certain compatibles explicitly, since we don't want to create	 * platform_devices for every node in /reserved-memory with a	 * "compatible",	 */	/*	* Explicitly handle certaincompatibles,Because we don't want to/reserved-memory each in ... that has“compatible”create a nodeplatform_device。	*/	for_each_matching_node(node, reserved_mem_matches)		of_platform_device_create(node, NULL, NULL);	// Find the node "/firmware"	node = of_find_node_by_path("/firmware");	if (node) {		// Use this node to populate device tree platform devices.		of_platform_populate(node, NULL, NULL, NULL);		of_node_put(node);	}	/* Populate everything else. */	of_platform_default_populate(NULL, NULL, NULL);// populate other devices	return 0;}arch_initcall_sync(of_platform_default_populate_init);

arch_initcall_syncis a function in the Linux kernel that is used to execute architecture-specific initialization functions during kernel initialization. It belongs to the kernel’s initialization call mechanism and is used to ensure that initialization functions specific to a particular architecture are called in a timely manner during system startup.

During the Linux kernel initialization process, various subsystems and architectures register their own initialization functions. These initialization functions are responsible for completing initialization work specific to a subsystem or architecture, such as initializing hardware devices, registering interrupt handlers, setting up memory mappings, etc. And arch_initcall_syncThe function is used to call initialization functions related to the current architecture.

When the kernel starts, callrest_init()function to start the initialization process. During the initialization process,arch_initcall_syncThe function will be called to ensure that all initialization functions related to the current architecture are executed in the correct order. This guarantees that architecture-specific initialization work is completed correctly during the startup process.

And of_platform_default_populate_initThe function’s role is to automatically parse the device tree during kernel initialization.and creates corresponding based on the device nodes in the device treeplatform_devicestructure. It will traverse the device nodes in the device tree, and for each device node create a correspondingplatform_devicestructure, and then register it with the kernel, so that device drivers can recognize and operate these devices.

of_platform_default_populate_initThe function ultimately callsof_platform_default_populatepopulate other devices

12345678
// drivers/of/platform.cint of_platform_default_populate(struct device_node *root,				 const struct of_dev_auxdata *lookup,				 struct device *parent){	return of_platform_populate(root, of_default_bus_match_table, lookup,				    parent);}

The function is used to callof_platform_populatefunction to populate the platform devices in the device tree, and use the default device matching tableof_default_bus_match_table, the device matching table is as follows:

12345678910
// drivers/of/platform.cconst struct of_device_id of_default_bus_match_table[] = {	{ .compatible = "simple-bus", },	{ .compatible = "simple-mfd", },	{ .compatible = "isa", },#ifdef CONFIG_ARM_AMBA	{ .compatible = "arm,amba-bus", },#endif /* CONFIG_ARM_AMBA */	{} /* Empty terminated list */};

The above device matching table is the second rule:

Traverse the nodes under the root node that contain compatible attribute is simple-bus、simple-mfd or isa the 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:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
// drivers/of/platform.c/** * of_platform_populate() - Populate platform_devices from device tree data * @root: parent of the first level to probe or NULL for the root of the tree * @matches: match table, NULL to use the default * @lookup: auxdata table for matching id and platform_data with device nodes * @parent: parent to hook devices from, NULL for toplevel * * Similar to of_platform_bus_probe(), this function walks the device tree * and creates devices from nodes.  It differs in that it follows the modern * convention of requiring all device nodes to have a 'compatible' property, * and it is suitable for creating devices which are children of the root * node (of_platform_bus_probe will only create children of the root which * are selected by the @matches argument). * * New board support should be using this function instead of * of_platform_bus_probe(). * * Returns 0 on success, < 0 on failure. */int of_platform_populate(struct device_node *root,			const struct of_device_id *matches,			const struct of_dev_auxdata *lookup,			struct device *parent){	struct device_node *child;	int rc = 0;	// If root is not NULL, increment the reference count of the root node; otherwise, look up the root node in the device tree by path.	root = root ? of_node_get(root) : of_find_node_by_path("/");	if (!root)		return -EINVAL;	pr_debug("%s()\n", __func__);	pr_debug(" starting at: %pOF\n", root);	// Suspend device link supplier synchronization state	device_links_supplier_sync_state_pause();	// Traverse all child nodes of the root node	for_each_child_of_node(root, child) {		// Create platform devices and add them to the device tree bus		rc = of_platform_bus_create(child, matches, lookup, parent, true);		if (rc) {			of_node_put(child);			break;		}	}	// Resume device link supplier synchronization state	device_links_supplier_sync_state_resume();	// Set the OF_POPULATED_BUS flag	of_node_set_flag(root, OF_POPULATED_BUS);	// Release the reference count of the root node	of_node_put(root);	return rc;}EXPORT_SYMBOL_GPL(of_platform_populate);

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:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
// drivers/of/platform.c/** * of_platform_bus_create() - Create a device for a node and its children. * @bus: device node of the bus to instantiate * @matches: match table for bus nodes * @lookup: auxdata table for matching id and platform_data with device nodes * @parent: parent for new device, or NULL for top level. * @strict: require compatible property * * Creates a platform_device for the provided device_node, and optionally * recursively create devices for all the child nodes. */static int of_platform_bus_create(struct device_node *bus,				  const struct of_device_id *matches,				  const struct of_dev_auxdata *lookup,				  struct device *parent, bool strict){	const struct of_dev_auxdata *auxdata;	struct device_node *child;	struct platform_device *dev;	const char *bus_id = NULL;	void *platform_data = NULL;	int rc = 0;	/* Make sure it has a compatible property */	/* Ensure the device node has a compatible property */	if (strict && (!of_get_property(bus, "compatible", NULL))) {		pr_debug("%s() - skipping %pOF, no compatible prop\n",			 __func__, bus);		return 0;	}	/* Skip nodes for which we don't want to create devices */	/* Skip nodes for which device creation is not desired */	if (unlikely(of_match_node(of_skipped_node_table, bus))) {		pr_debug("%s() - skipping %pOF node\n", __func__, bus);		return 0;	}	if (of_node_check_flag(bus, OF_POPULATED_BUS)) {		pr_debug("%s() - skipping %pOF, already populated\n",			__func__, bus);		return 0;	}	auxdata = of_dev_lookup(lookup, bus);	if (auxdata) {		bus_id = auxdata->name;		platform_data = auxdata->platform_data;	}	if (of_device_is_compatible(bus, "arm,primecell")) {		/*		 * Don't return an error here to keep compatibility with older		 * device tree files.		 */		/*		* Do not return an error here to maintain compatibility with old device tree files.。		*/		of_amba_device_create(bus, bus_id, platform_data, parent);		return 0;	}	dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);	if (!dev || !of_match_node(matches, bus))		return 0;	for_each_child_of_node(bus, child) {		pr_debug("   create child: %pOF\n", child);		rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);		if (rc) {			of_node_put(child);			break;		}	}	of_node_set_flag(bus, OF_POPULATED_BUS);	return rc;}

of_platform_bus_createby callingof_match_node(of_skipped_node_table, bus)Match the nodes that you want to skip,of_skipped_node_tabledefined as follows

1234
static const struct of_device_id of_skipped_node_table[] = {	{ .compatible = "operating-points-v2", },	{} /* Empty terminated list */};

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 usually appears as a child node under CPU, GPU, or SoC device nodes.

123456789101112131415
cpu@0 {    compatible = "arm,cortex-a53";    ...    operating-points-v2 {        compatible = "operating-points-v2";        opp00 {            opp-hz = /bits/ 64 <1000000000>;            opp-microvolt = <900000>;        };        opp01 {            opp-hz = /bits/ 64 <1500000000>;            opp-microvolt = <1000000>;        };    };};

Thenof_platform_bus_createCallof_platform_device_create_pdataThe function creates a platform device and assigns it to the variable dev. Then, it checks whether the device node bus matches the given match tablematchesIf the platform device creation fails or the device node does not match, return 0.

Finally,of_platform_bus_createusefor_each_child_of_node(bus, child), traverse each child node child of the device node bus, and recursively callof_platform_bus_createthe function to create platform devices for the child nodes.

of_platform_device_create_pdata()

of_platform_device_create_pdatadefined as follows

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
// drivers/of/platform.c/** * of_platform_device_create_pdata - Alloc, initialize and register an of_device * @np: pointer to node to create device for * @bus_id: name to assign device * @platform_data: pointer to populate platform_data pointer with * @parent: Linux device model parent device. * * Returns pointer to created platform device, or NULL if a device was not * registered.  Unavailable devices will not get registered. */static struct platform_device *of_platform_device_create_pdata(					struct device_node *np,					const char *bus_id,					void *platform_data,					struct device *parent){	struct platform_device *dev;	/* Check whether the device node is available or already populated. */	if (!of_device_is_available(np) ||	    of_node_test_and_set_flag(np, OF_POPULATED))		return NULL;	/* Allocate platform device structure */	dev = of_device_alloc(np, bus_id, parent);	if (!dev)		goto err_clear_flag;	/* Set some properties of the platform device. */	dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);	if (!dev->dev.dma_mask)		dev->dev.dma_mask = &dev->dev.coherent_dma_mask;	dev->dev.bus = &platform_bus_type;	dev->dev.platform_data = platform_data;	of_msi_configure(&dev->dev, dev->dev.of_node);	/* Add platform device to device model */	if (of_device_add(dev) != 0) {		platform_device_put(dev);		goto err_clear_flag;	}	return dev;err_clear_flag:	of_node_clear_flag(np, OF_POPULATED);/* Clear the filled flag of the device node */	return NULL;}

of_platform_device_create_pdataFunction callof_device_allocallocates a platform device structure, and passes the device node pointer, device identifier, and parent device pointer to it. If allocation fails, jump toerr_clear_flagPerform error handling at the label.

Lines 29 to 34of_platform_device_create_pdataSet some properties of the platform device:

  • It sets thecoherent_dma_maskproperty to a 32-bit DMA bitmask.
  • Checkdma_maskwhether the property is NULL. Ifdma_maskit is NULL, then point it tocoherent_dma_mask
  • Set the bus type of the platform device toplatform_bus_type, and store the platform data pointer inplatform_datathe property.
  • Callof_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()

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
/** * of_device_alloc - Allocate and initialize an of_device * @np: device node to assign to device * @bus_id: Name to assign to the device.  May be null to use default name. * @parent: Parent device. */struct platform_device *of_device_alloc(struct device_node *np,				  const char *bus_id,				  struct device *parent){	struct platform_device *dev;	int rc, i, num_reg = 0, num_irq;	struct resource *res, temp_res;	dev = platform_device_alloc("", PLATFORM_DEVID_NONE);	if (!dev)		return NULL;	/* count the io and irq resources */	while (of_address_to_resource(np, num_reg, &temp_res) == 0)		num_reg++;	num_irq = of_irq_count(np);	/* Populate the resource table */	if (num_irq || num_reg) {		res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL);		if (!res) {			platform_device_put(dev);			return NULL;		}		dev->num_resources = num_reg + num_irq;		dev->resource = res;		for (i = 0; i < num_reg; i++, res++) {			rc = of_address_to_resource(np, i, res);			WARN_ON(rc);		}		if (of_irq_to_resource_table(np, res, num_irq) != num_irq)			pr_debug("not all legacy IRQ resources mapped for %pOFn\n",				 np);	}	dev->dev.of_node = of_node_get(np);	dev->dev.fwnode = &np->fwnode;	dev->dev.parent = parent ? : &platform_bus;	if (bus_id)		dev_set_name(&dev->dev, "%s", bus_id);	else		of_device_make_bus_id(&dev->dev);	return dev;}EXPORT_SYMBOL(of_device_alloc);

you can see that it is calledplatform_device_alloc("", PLATFORM_DEVID_NONE)createdplatform_deviceofnameis 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 the probe initialization function be correctly matched and loaded:

  1. platform_driver.driver.of_match_tablematches the value in the device tree’s compatible
  2. platform_driver.id_tablematches the value in the device tree’s compatible
  3. platform_driver.driver.nameandplatform_device.nameMatch

For the matching priority, from the followingplatform_matchfunction, it can be seen:

  • device tree matching is preferred, i.e.,platform_driver.driver.of_match_table
  • then ACPI matching
  • thenplatform_driverinid_tablematching.
  • finally fall back toreturn (strcmp(pdev->name, drv->name) == 0);, i.e., matchingplatform_device.nameandplatform_driver.driver.name, and this name is a legacy field. After device tree parsing, fromstruct device_nodeconverted tostruct platform_devicewhen thisnameis set to empty (of_device_alloc), that is, the device tree generatedstruct 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->namewill report an error when it is NULL.

123456789101112131415161718192021222324252627282930313233343536373839
// drivers/base/platform.c/** * platform_match - bind platform device to platform driver. * @dev: device. * @drv: driver. * * Platform device IDs are assumed to be encoded like this: * "<name><instance>", where <name> is a short description of the type of * device, like "pci" or "floppy", and <instance> is the enumerated * instance of the device, like '0' or '42'.  Driver IDs are simply * "<name>".  So, extract the <name> from the platform_device structure, * and compare it against the name of the driver. Return whether they match * or not. */static int platform_match(struct device *dev, struct device_driver *drv){	struct platform_device *pdev = to_platform_device(dev);	struct platform_driver *pdrv = to_platform_driver(drv);	/* When driver_override is set, only bind to the matching driver */	if (pdev->driver_override)		return !strcmp(pdev->driver_override, drv->name);	/* Attempt an OF style match first */	if (of_driver_match_device(dev, drv))		return 1;	/* Then try ACPI style match */	if (acpi_driver_match_device(dev, drv))		return 1;	/* Then try to match against the id table */	if (pdrv->id_table)		return platform_match_id(pdrv->id_table, pdev) != NULL;	/* fall-back to driver name match */	return (strcmp(pdev->name, drv->name) == 0);}

Andof_driver_match_deviceThe function is defined as follows:

1234567891011
// include/linux/of_device.h/** * of_driver_match_device - Tell if a driver's of_match_table matches a device. * @drv: the device_driver structure to test * @dev: the device structure to match against */static inline int of_driver_match_device(struct device *dev,					 const struct device_driver *drv){	return of_match_device(drv->of_match_table, dev) != NULL;}

of_driver_match_devicecallsof_match_devicefunction, the first parameter is passed indrv->of_match_table, the function is defined as follows

123456789101112131415161718
// drivers/of/device.c/** * of_match_device - Tell if a struct device matches an of_device_id list * @matches: array of of device match structures to search in * @dev: the of device structure to match against * * Used by a driver to check whether an platform_device present in the * system is in its list of supported devices. */const struct of_device_id *of_match_device(const struct of_device_id *matches,					   const struct device *dev){	if ((!matches) || (!dev->of_node))		return NULL;	return of_match_node(matches, dev->of_node);}EXPORT_SYMBOL(of_match_device);

of_match_devicealso callsof_match_nodefunction,of_match_nodeuses a spinlock to__of_match_nodelock,__of_match_nodeis the actual matching function

123456789101112131415161718192021222324252627282930313233343536373839404142
// drivers/of/base.c** * of_match_node - Tell if a device_node has a matching of_match structure *	@matches:	array of of device match structures to search in *	@node:		the of device structure to match against * *	Low level utility function used by device matching. */const struct of_device_id *of_match_node(const struct of_device_id *matches,					 const struct device_node *node){	const struct of_device_id *match;	unsigned long flags;	raw_spin_lock_irqsave(&devtree_lock, flags);	match = __of_match_node(matches, node);	raw_spin_unlock_irqrestore(&devtree_lock, flags);	return match;}EXPORT_SYMBOL(of_match_node);staticconst struct of_device_id *__of_match_node(const struct of_device_id *matches,					   const struct device_node *node){	const struct of_device_id *best_match = NULL;	int score, best_score = 0;	if (!matches)		return NULL;	for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {		score = __of_device_is_compatible(node, matches->compatible,						  matches->type, matches->name);		if (score > best_score) {			best_match = matches;			best_score = score;		}	}	return best_match;}

__of_match_nodeCall__of_device_is_compatiblematching, the parameters passed in are, in order:

  • struct device_node *node
  • struct of_device_id *matchesofcompatibletypename
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
/** * __of_device_is_compatible() - Check if the node matches given constraints * @device: pointer to node * @compat: required compatible string, NULL or "" for any match * @type: required device_type value, NULL or "" for any match * @name: required node name, NULL or "" for any match * * Checks if the given @compat, @type and @name strings match the * properties of the given @device. A constraints can be skipped by * passing NULL or an empty string as the constraint. * * Returns 0 for no match, and a positive integer on match. The return * value is a relative score with larger values indicating better * matches. The score is weighted for the most specific compatible value * to get the highest score. Matching type is next, followed by matching * name. Practically speaking, this results in the following priority * order for matches: * * 1. specific compatible && type && name * 2. specific compatible && type * 3. specific compatible && name * 4. specific compatible * 5. general compatible && type && name * 6. general compatible && type * 7. general compatible && name * 8. general compatible * 9. type && name * 10. type * 11. name */static int __of_device_is_compatible(const struct device_node *device,				     const char *compat, const char *type, const char *name){	struct property *prop;	const char *cp;	int index = 0, score = 0;	/* Compatible match has highest priority */	if (compat && compat[0]) {		prop = __of_find_property(device, "compatible", NULL);		for (cp = of_prop_next_string(prop, NULL); cp;		     cp = of_prop_next_string(prop, cp), index++) {			if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {				score = INT_MAX/2 - (index << 2);				break;			}		}		if (!score)			return 0;	}	/* Matching type is better than matching name */	if (type && type[0]) {		if (!__of_node_is_type(device, type))			return 0;		score += 2;	}	/* Matching name is a bit better than not */	if (name && name[0]) {		if (!of_node_name_eq(device, name))			return 0;		score++;	}	return score;}

It can be seen thatof_device_idofcompatibleattribute has the highest priority, followed bytypeattribute, and finallynameattribute. The score is calculated based on these three attributesscore, the higher the score, the higher the matching degree.

Andplatform_drivernested in the structuredriverof the structureof_match_tableattribute is a pointer toconst struct of_device_ida structure, used to describe the matching rules between device tree nodes and drivers.

12345678910
// include/linux/mod_devicetable.h/* * Struct used for matching a device */struct of_device_id {	char	name[32];	char	type[32];	char	compatible[128];	const void *data;};

struct of_device_idusually defined as an array in the driver,used to describe the matching rules between device tree nodes and drivers. And:

the last element of the array must be an empty structure,to mark the end of the array

Example:

12345
static const struct of_device_id my_driver_match[] = {	{ .compatible = "vendor,device-1" },	{ .compatible = "vendor,device-2" },	{ },};

example

device tree leveldevice tree namedevice tree name
Top-level device treerk3568-evb1-ddr4-v10-linux.dtsrk3568-evb1-ddr4-v10-linux.dts
Second-level device treerk3568-evb1-ddr4-v10.dtsirk3568-linux.dtsi
Third-level device treerk3568.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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)/* * Copyright (c) 2020 Rockchip Electronics Co., Ltd. * */#include "rk3568-evb1-ddr4-v10.dtsi"#include "rk3568-linux.dtsi"#include <dt-bindings/display/rockchip_vop.h>/{    topeet{        #address-cells = <1>;        #size-cells = <1>;        compatible = "simple-bus";        myLed{            compatible = "my devicetree";            reg = <0xFDD60000 0x00000004>;        };    };};&vp0 {	cursor-win-id = <ROCKCHIP_VOP2_CLUSTER0>;};&vp1 {	cursor-win-id = <ROCKCHIP_VOP2_CLUSTER1>;};&uart7 {		status ="okay";		pinctrl-name = "default";		pinctrl-0 = <&uart7m1_xfer>;};&uart4 {	    status = "okay";		pinctrl-names = "default";		pinctrl-0 = <&uart4m1_xfer>;};&uart9 {	    status = "okay";		pinctrl-names = "default";		pinctrl-0 = <&uart9m1_xfer>;};&can1 {        status = "okay";		compatible = "rockchip,canfd-1.0";        assigned-clocks = <&cru CLK_CAN1>;        assigned-clock-rates = <150000000>;  //If can bitrate lower than 3M,the clock-rates should set 100M,else set 200M.        pinctrl-names = "default";        pinctrl-0 = <&can1m1_pins>;};

driver

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
#include <linux/module.h>#include <linux/platform_device.h>#include <linux/of.h>// Platform device initialization functionstatic int my_platform_probe(struct platform_device *pdev){    printk(KERN_INFO "my_platform_probe: Probing platform device\n");    // Add device-specific operations    // ...    return 0;}// Platform device removal functionstatic int my_platform_remove(struct platform_device *pdev){    printk(KERN_INFO "my_platform_remove: Removing platform device\n");    // Clean device-specific operations    // ...    return 0;}const struct of_device_id of_match_table_id[]  = {	{.compatible="my devicetree"},};// Define the platform driver structurestatic struct platform_driver my_platform_driver = {    .probe = my_platform_probe,    .remove = my_platform_remove,    .driver = {        .name = "my_platform_device",        .owner = THIS_MODULE,	.of_match_table =  of_match_table_id,    },};// Module initialization functionstatic int __init my_platform_driver_init(void){    int ret;    // Register platform driver    ret = platform_driver_register(&my_platform_driver);    if (ret) {        printk(KERN_ERR "Failed to register platform driver\n");        return ret;    }    printk(KERN_INFO "my_platform_driver: Platform driver initialized\n");    return 0;}// Module exit functionstatic void __exit my_platform_driver_exit(void){    // Unregister platform driver    platform_driver_unregister(&my_platform_driver);    printk(KERN_INFO "my_platform_driver: Platform driver exited\n");}module_init(my_platform_driver_init);module_exit(my_platform_driver_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629");

of operations

Get the 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 node name

ItemDescription
Function definitionstruct device_node *of_find_node_by_name(struct device_node *from, const char *name);
Header file#include <linux/of.h>
parameter fromStarting search node:
NULL: start searching from the device tree root node
Non-NULL: continue searching for a node with the same name after this node
Parameter nameThe device tree node name to search for (node name, not compatible)
FunctionFind a node with a matching name in the device tree and return the correspondingdevice_nodestructure pointer
Return valueFound: return the matching node’sstruct device_node *; not found: returnNULL

of_find_node_by_path()

ItemDescription
Function definitionstruct device_node *of_find_node_by_path(const char *path);
Header file#include <linux/of.h>
Parameter pathThe absolute path string of the device tree node, for example:/soc/gpio@ff720000/topeet/myLed
FunctionAccording to the device tree node’sabsolute pathFind the corresponding node and return the matchingstruct device_nodestructure pointer
Return valueSuccess: return pointerstruct device_nodepointer;
Failure: returnsNULL

of_get_parent()

ItemDescription
Function definitionstruct device_node *of_get_parent(const struct device_node *node);
Header file#include <linux/of.h>
Parameter nodeThe device tree node pointer whose parent is to be obtained
FunctionGet the parent node of the specified node and return its correspondingdevice_nodestructure pointer
Return valueSuccess: returns the parent node’sstruct device_nodepointer;
Failure or no parent node: returnsNULL

of_get_next_child()

ItemDescription
Function definitionstruct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev);
Header file#include <linux/of.h>
Parameter nodeCurrent device tree node pointer, used to specify the parent node whose children are to be traversed
Parameter prevPointer to the previous child node; if it isNULL, then return the first child node
FunctionTraverse the child nodes of a device tree node; return all child nodes of the specified node one by one
Return valueSuccess: returns a pointer to the next child nodestruct device_nodepointer;
No more child nodes: returnsNULL

for_each_child_of_node()

ItemDescription
Macro definitionfor_each_child_of_node(parent, child)
Header file#include <linux/of.h>
Parameter parentParent device tree node pointer (struct device_node *
Parameter childChild node pointer variable (struct device_node *), used for traversal
FunctionTraverse all child nodes of the specified parent node
Return valueNone (macro, used in for loops)

Example:

12345678910
struct device_node *np = pdev->dev.of_node;struct device_node *sub_np;for_each_child_of_node(np, sub_np) {	/* sub_np will point to each sub-node in turn */	[...]	int size;	of_property_read_u32(client->dev.of_node,"size", &size);	[...]}

of_find_compatible_node()

ItemDescription
Function definitionstruct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible);
Header file#include <linux/of.h>
parameter fromSpecifies the node to start searching from; if it isNULL, then the search starts from the device tree root node
Parameter typeThe device type string to match, which can be used to match the node’sdevice_typeAttribute; usually can be set toNULL
Parameter compatibleto be matched in the device treecompatibleattribute string
FunctionFind 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 valueSuccess: returns the matching node’sstruct device_node*
Failed or does not exist: ReturnNULL

of_find_matching_node_and_match()

ItemDescription
Function definitionstruct 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 fromSpecify the node from which to start searching:
biographyNULLIndicates to start searching from the root node of the device tree;
Pass the previously returned node to continue searching for the next matching node.
parameter matchespoint to a of_device_id[]Match table, the match table contains the device tree nodes used for matching.compatibleortypethe condition
Parameter matchOutput parameter, used to return the matched this time.of_device_idEntry pointer; can be NULL
FunctionIn the device tree, according tomatchesThe matching table finds nodes that meet the conditions and can also return the corresponding matches.
Return valueSuccess: Return the matched ones.struct device_node *
Failure: returnsNULL

Example:

1234567891011
#include <linux/of.h>static const struct of_device_id my_match_table[] = {	{ .compatible = "vendor,device" },	{ /* sentinel */ }};const struct of_device_id *match;struct device_node *np;// Start searching for matching nodes from the root node.np = of_find_matching_node_and_match(NULL, my_match_table, &match)

First, defined aof_device_idmatching tablemy_match_table, which contains a compatible string ofvendor,devicematching item. Then, we useof_find_matching_node_and_matchfunction to find matching nodes starting from the root node.

example

Device tree:

1234567891011121314
/{	test_device{                #address-cells =<1>;                #size-cells = <1>;                compatible = "simple-bus";                myLed{                        compatible = "my devicetree";                        reg = <0xFDD60000 0x00000004>;                };        };};

Driver:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
#include <linux/module.h>#include <linux/init.h>#include <linux/platform_device.h>#include <linux/of.h>static const struct of_device_id mynode_of_match[] = { { .compatible = "my devicetree" }, {} };static int my_platform_driver_probe(struct platform_device *pdev){        struct device_node *mydev_node;        pr_info("my_platform_driver_probe: Probing platform device\n");        // Find device tree node by node name        mydev_node = of_find_node_by_name(NULL, "myLed");        pr_info("[of_find_node_by_name]: device node is %s\n", mydev_node->name);        // Find device tree node by node path        mydev_node = of_find_node_by_path("/test_device/myLed");        pr_info("[of_find_node_by_path]: device node is %s\n", mydev_node->name);        // Get parent node        mydev_node = of_get_parent(mydev_node);        pr_info("[of_find_node_by_path]: device node is %s\n", mydev_node->name);        // Get child node        mydev_node = of_get_next_child(mydev_node, NULL);        pr_info("[of_get_next_child]: device node is %s\n", mydev_node->name);        // Find node using compatible        mydev_node = of_find_compatible_node(NULL, NULL, "my devicetree");        pr_info("[of_find_compatible_node]: device node is %s\n", mydev_node->name);        // Use of_device_id match table to find matching node        mydev_node = of_find_matching_node_and_match(NULL, mynode_of_match, NULL);        pr_info("[of_find_matching_node_and_match]: device node is %s\n", mydev_node->name);        return 0;}static int my_platform_driver_remove(struct platform_device *pdev){        return 0;}static const struct of_device_id match_table[] = { { .compatible = "my devicetree" }, {} };static struct platform_driver my_platform_driver ={        .driver = {                .owner = THIS_MODULE,                .name = "my_platform_driver",                .of_match_table = match_table,        },        .probe = my_platform_driver_probe,        .remove = my_platform_driver_remove,};module_platform_driver(my_platform_driver);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for of api");

Test:

12345678
root@topeet:/root# insmod of_api_test.ko[ 1230.073568] my_platform_driver_probe: Probing platform device[ 1230.074042] [of_find_node_by_name]: device node is myLed[ 1230.074156] [of_find_node_by_path]: device node is myLed[ 1230.074163] [of_find_node_by_path]: device node is test_device[ 1230.074170] [of_get_next_child]: device node is myLed[ 1230.074461] [of_find_compatible_node]: device node is myLed[ 1230.074717] [of_find_matching_node_and_match]: device node is myLed

Get device tree property

of_find_property()

ItemDescription
Function definitionstruct property *of_find_property(const struct device_node *np, const char *name, int *lenp);
Header file#include <linux/of.h>
Parameter npThe device tree node to look up the property (struct device_nodepointer)
Parameter nameThe property name string to look up, for examplecompatibleregstatus
Parameter lenppoint tointA pointer of type, used to return the byte length of the property value;
If you do not need to get the length, you can pass NULL
FunctionIn the specified nodenpfind the property namednameproperty, and can return the property value length
Return valueSuccess: returns the property structure pointerstruct property *
Failure (property not found or invalid parameter): returns NULL

of_property_count_elems_of_size()

ItemDescription
Function definitionint of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size);
Header file#include <linux/of.h>
Parameter npDevice tree node pointer (struct device_node *), indicating the node whose property is to be read
Parameter propnameProperty name string, e.g.reggpiosetc.
Parameter elem_sizeSize of a single element (in bytes), for example:sizeof(u32)→ Number of 32-bit integers in the property
FunctionCalculate the number of elements in the specified property (divided by the given element size)
Return valueSuccess: returns the number of elements contained in the property
Property does not exist or has no content: returns 0
Other errors: returns Negative error code(e.g.-EINVAL

of_property_read_u32_index()

ItemDescription
Function definitionint 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 npDevice tree node pointer (struct device_node *), indicating the node whose property is to be read
Parameter propnameProperty name string, e.g.reggpiosetc.
Parameter indexIndex of the property element (starting from 0), specifying which element to read.
Parameter out_valuepoint tou32Pointer to a type variable, used to store the read value.
FunctionGet the 32-bit unsigned integer (u32) at the specified index from the specified property
Return valueOn success: returns 0out_valueStore the read value.
Failure: returns a negative error code (property does not exist or read failed)

of_property_read_u64_index()

ItemDescription
Function definitionstatic 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 npDevice tree node pointer (struct device_node *), indicating the node whose property is to be read
Parameter propnameProperty name string, e.g.reggpiosetc.
Parameter indexIndex of the property element (starting from 0), specifying which element to read.
Parameter out_valuepoint tou64Pointer to a type variable, used to store the read value.
FunctionGet the 64-bit unsigned integer (u64) at the specified index from the specified property.
Return valueOn success: returns 0out_valueStore the read value.
Failure: returns a negative error code, e.g., property does not exist or read failure

of_property_read_variable_u32_array()

ItemDescription
Function definitionint 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 npDevice tree node pointer (struct device_node *), indicating the node whose property is to be read
Parameter propnameProperty name string, e.g.reggpiosetc.
Parameter out_valuespoint tou32Pointer to an array of the type, used to store the read values.
Parameter SZ_minSpecifies the minimum number of elements in the array.
Parameter SZ_maxSpecifies the maximum number of elements in the array.
FunctionRead a variable-lengthu32array, and store it intoout_values
Return valueSuccess: returns the actual number of array elements read.
Failure: returns a negative error code, e.g., property does not exist or read failure
  • Read a variable-length u8 array from the specified property
12
int of_property_read_variable_u8_array(const struct device_node *np, const char *propname, u8 *out_values,size_t SZ_min, size_t SZ_max)
  • Read a variable-length u16 array from the specified property
12
int of_property_read_variable_u16_array(const struct device_node *np, const char *propname, u16*out_values, size_t SZ_min, size_t SZ_max)
  • Read a variable-length u64 array from the specified property
12
int of_property_read_variable_u64_array(const struct device_node *np, const char *propname, u64*out_values, size_t SZ_min, size_t SZ_max)

of_property_read_string()

ItemDescription
Function definitionstatic 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 npDevice tree node pointer (struct device_node *), indicating the node whose property is to be read
Parameter propnameProperty name string, e.g.compatiblestatusetc.
Parameter out_stringPointer to a string pointer, used to store the retrieved string
FunctionRead a string value from the specified property
Return valueSuccess: returns 0
Failure: returns a negative error code, e.g., property does not exist or read failure

of_property_read_bool()

ItemDescription
Function definitionstatic inline bool of_property_read_bool(const struct device_node *np, const char *propname);
Header file#include <linux/of.h>
Parameter npDevice tree node pointer (struct device_node *), indicating the node whose property is to be read
Parameter propnameProperty name string, e.g.gpio-active-lowenableetc.
FunctionDetermine whether a boolean property exists in the specified node
Return valueIf the property exists: returns true
If it does not exist: returns false

example

Device tree:

1234567891011121314
/{	test_device{                #address-cells =<1>;                #size-cells = <1>;                compatible = "simple-bus";                myLed{                        compatible = "my devicetree";                        reg = <0xFDD60000 0x00000004>;                };        };};

Driver:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
#include <linux/module.h>#include <linux/init.h>#include <linux/platform_device.h>#include <linux/of.h>static int my_platform_driver_probe(struct platform_device *pdev){        struct device_node *mydev_node;        int i, num;        u32 out_value_u32;        u64 out_value_u64;        u32 out_value_u32_array[2];        const char *value_compatible;        struct property *my_prop;        pr_info("my_platform_driver_probe: Probing platform device\n");        // Find a device tree node by name        mydev_node = of_find_node_by_name(NULL, "myLed");        pr_info("[of_find_node_by_name]: device node is %s\n", mydev_node->name);        // Find the compatible property        my_prop = of_find_property(mydev_node, "compatible", NULL);        pr_info("[of_find_property]: property name is %s\n", my_prop->name);        // Get the number of elements in the reg property        num = of_property_count_elems_of_size(mydev_node, "reg", sizeof(u32));        pr_info("[of_property_count_elems_of_size]: reg elem size is %d\n", num);        // Read the u32 value of the reg property        for (i = 0; i < num; i++) {                of_property_read_u32_index(mydev_node, "reg", i, &out_value_u32);                pr_info("[of_property_read_u32_index]: reg u32 value: 0x%X\n", out_value_u32);        }        // Read the u64 value of the reg attribute        of_property_read_u64_index(mydev_node, "reg", 0, &out_value_u64);        pr_info("[of_property_read_u64_index]: reg u64 value: 0x%llX\n", out_value_u64);        // Read the reg attribute as an array        of_property_read_variable_u32_array(mydev_node, "reg", out_value_u32_array, 1, 2);        pr_info("[of_property_read_variable_u32_array]: array[0] is 0x%X\n",                out_value_u32_array[0]);        pr_info("[of_property_read_variable_u32_array]: array[1] is 0x%X\n",                out_value_u32_array[1]);        // Read the string value of the compatible attribute        of_property_read_string(mydev_node, "compatible", &value_compatible);        pr_info("[of_property_read_string]: compatible string is %s\n", value_compatible);        return 0;}static int my_platform_driver_remove(struct platform_device *pdev){        return 0;}static const struct of_device_id of_match_table[] = { { .compatible = "my devicetree" }, {} };static struct platform_driver my_platform_driver={        .driver={                .owner = THIS_MODULE,                .name = "my_platform_driver",                .of_match_table = of_match_table,        },        .probe = my_platform_driver_probe,        .remove = my_platform_driver_remove,};module_platform_driver(my_platform_driver);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("this is a test sample for devicetree api: of_property");

Test:

1234567891011
root@topeet:/root# insmod of_api_property_test.ko[ 1173.158496] my_platform_driver_probe: Probing platform device[ 1173.158912] [of_find_node_by_name]: device node is myLed[ 1173.158922] [of_find_property]: property name is compatible[ 1173.158929] [of_property_count_elems_of_size]: reg elem size is 2[ 1173.158935] [of_property_read_u32_index]: reg u32 value: 0xFDD60000[ 1173.158941] [of_property_read_u32_index]: reg u32 value: 0x4[ 1173.158947] [of_property_read_u64_index]: reg u64 value: 0xFDD6000000000004[ 1173.158954] [of_property_read_variable_u32_array]: array[0] is 0xFDD60000[ 1173.158958] [of_property_read_variable_u32_array]: array[1] is 0x4[ 1173.158964] [of_property_read_string]: compatible string is my devicetree

ranges property

platform_get_resource: the prerequisite for obtaining device tree resources

Since the device tree is converted into platform devices at system startup, we can use it in the driver on the platform bus.platform_get_resourceDirect function retrievalplatform_deviceResources

Example:

Device tree:

1234567891011121314
/{	test_device{                #address-cells =<1>;                #size-cells = <1>;                compatible = "simple-bus";                myLed{                        compatible = "my devicetree";                        reg = <0xFDD60000 0x00000004>;                };        };};

Driver:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
#include <linux/module.h>#include <linux/platform_device.h>#include <linux/mod_devicetable.h>// Platform device initialization functionstruct resource *myresources;static int my_platform_probe(struct platform_device *pdev){	printk(KERN_INFO "my_platform_probe: Probing platform device\n");	// Obtain platform device resources	myresources = platform_get_resource(pdev, IORESOURCE_MEM, 0);	if (myresources == NULL) {		// If fetching the resource fails, print the value of value_compatible.		printk("platform_get_resource is error\n");	}	printk("reg valus is %llx\n", myresources->start);	return 0;}// Platform device removal functionstatic int my_platform_remove(struct platform_device *pdev){	printk(KERN_INFO "my_platform_remove: Removing platform device\n");	// Clean device-specific operations	// ...	return 0;}const struct of_device_id of_match_table_id[] = {	{ .compatible = "my devicetree" },};// Define the platform driver structurestatic struct platform_driver my_platform_driver = {  .probe = my_platform_probe,  .remove = my_platform_remove,  .driver = {    .name = "my_platform_device",    .owner = THIS_MODULE,    .of_match_table = of_match_table_id,  },};// Module initialization functionstatic int __init my_platform_driver_init(void){	int ret;	// Register platform driver	ret = platform_driver_register(&my_platform_driver);	if (ret) {		printk(KERN_ERR "Failed to register platform driver\n");		return ret;	}	printk(KERN_INFO "my_platform_driver: Platform driver initialized\n");	return 0;}// Module exit functionstatic void __exit my_platform_driver_exit(void){	// Unregister platform driver	platform_driver_unregister(&my_platform_driver);	printk(KERN_INFO "my_platform_driver: Platform driver exited\n");}module_init(my_platform_driver_init);module_exit(my_platform_driver_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("topeet");

This method will fail to load. The reason isplatform_get_resourcereturn NULL

123456789101112131415161718192021222324
// drivers/base/platform.c/** * platform_get_resource - get a resource for a device * @dev: platform device * @type: resource type * @num: resource index * * Return: a pointer to the resource or NULL on failure. */struct resource *platform_get_resource(struct platform_device *dev,				       unsigned int type, unsigned int num){	u32 i;	for (i = 0; i < dev->num_resources; i++) {		struct resource *r = &dev->resource[i];		if (type == resource_type(r) && num-- == 0)			return r;	}	return NULL;}EXPORT_SYMBOL_GPL(platform_get_resource);

There are two possibilities for returning NULL: one is that it did not enter the above for loop and directly returned NULL; the other is that it entered the for loop, but the type matching was incorrect, and after breaking out of the for loop, it returned NULL.

The types here must be matching, so let’s find out why the for loop was not entered. There is only one possibility here, which isdev->num_resources is 0

Let’s take a lookof_platform_device_create_pdatathis function

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
// drivers/of/platform.c/** * of_platform_device_create_pdata - Alloc, initialize and register an of_device * @np: pointer to node to create device for * @bus_id: name to assign device * @platform_data: pointer to populate platform_data pointer with * @parent: Linux device model parent device. * * Returns pointer to created platform device, or NULL if a device was not * registered.  Unavailable devices will not get registered. */static struct platform_device *of_platform_device_create_pdata(					struct device_node *np,					const char *bus_id,					void *platform_data,					struct device *parent){	struct platform_device *dev;	/* Check whether the device node is available or already populated. */	if (!of_device_is_available(np) ||	    of_node_test_and_set_flag(np, OF_POPULATED))		return NULL;	/* Allocate platform device structure */	dev = of_device_alloc(np, bus_id, parent);	if (!dev)		goto err_clear_flag;	/* Set some properties of the platform device. */	dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);	if (!dev->dev.dma_mask)		dev->dev.dma_mask = &dev->dev.coherent_dma_mask;	dev->dev.bus = &platform_bus_type;	dev->dev.platform_data = platform_data;	of_msi_configure(&dev->dev, dev->dev.of_node);	/* Add platform device to device model */	if (of_device_add(dev) != 0) {		platform_device_put(dev);		goto err_clear_flag;	}	return dev;err_clear_flag:	/* Clear the filled flag of the device node */	of_node_clear_flag(np, OF_POPULATED);	return NULL;}

Function callof_device_allocIt is this function that decides to allocate a platform device structure and pass the device node pointer, device identifier, and parent device pointer to it.resource.num

of_device_allocThe function is as follows:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
// drivers/of/platform.c/** * of_device_alloc - Allocate and initialize an of_device * @np: device node to assign to device * @bus_id: Name to assign to the device.  May be null to use default name. * @parent: Parent device. */struct platform_device *of_device_alloc(struct device_node *np,				  const char *bus_id,				  struct device *parent){	struct platform_device *dev;	int rc, i, num_reg = 0, num_irq;	struct resource *res, temp_res;	dev = platform_device_alloc("", PLATFORM_DEVID_NONE);	if (!dev)		return NULL;	/* count the io and irq resources */	while (of_address_to_resource(np, num_reg, &temp_res) == 0)		num_reg++;	num_irq = of_irq_count(np);	/* Populate the resource table */	if (num_irq || num_reg) {		res = kcalloc(num_irq + num_reg, sizeof(*res), GFP_KERNEL);		if (!res) {			platform_device_put(dev);			return NULL;		}		dev->num_resources = num_reg + num_irq;		dev->resource = res;		for (i = 0; i < num_reg; i++, res++) {			rc = of_address_to_resource(np, i, res);			WARN_ON(rc);		}		if (of_irq_to_resource_table(np, res, num_irq) != num_irq)			pr_debug("not all legacy IRQ resources mapped for %pOFn\n",				 np);	}	dev->dev.of_node = of_node_get(np);	dev->dev.fwnode = &np->fwnode;	dev->dev.parent = parent ? : &platform_bus;	if (bus_id)		dev_set_name(&dev->dev, "%s", bus_id);	else		of_device_make_bus_id(&dev->dev);	return dev;}EXPORT_SYMBOL(of_device_alloc);

At line 32dev->num_resources = num_reg + num_irq;That is, the number of reg and the number of irq. Since no interrupt-related properties are added in the device tree,num_irqis 0, andnum_regby line 20 through this functionof_address_to_resource(np, num_reg, &temp_res) == 0obtained by loop countingnum_rg

123
/* count the io and irq resources */while (of_address_to_resource(np, num_reg, &temp_res) == 0)	num_reg++;

of_address_to_resourceThe function is defined as follows:

123456789101112131415161718192021222324252627
// drivers/of/address.c/** * of_address_to_resource - Translate device tree address and return as resource * * Note that if your address is a PIO address, the conversion will fail if * the physical address can't be internally converted to an IO token with * pci_address_to_pio(), that is because it's either called too early or it * can't be matched to any host bridge IO space */int of_address_to_resource(struct device_node *dev, int index,			   struct resource *r){	const __be32	*addrp;	u64		size;	unsigned int	flags;	const char	*name = NULL;	addrp = of_get_address(dev, index, &size, &flags);	if (addrp == NULL)		return -EINVAL;	/* Get optional "reg-names" property to add a name to a resource */	of_property_read_string_index(dev, "reg-names",	index, &name);	return __of_address_to_resource(dev, addrp, size, flags, name, r);}EXPORT_SYMBOL_GPL(of_address_to_resource);

Line 18 gets the address, size, and type of the reg property. Since the reg property already exists in the device tree, it will return correctly here.

Line 23 readsreg-namesthe property. Since this property is not defined in the device tree, this function will have no effect.

Finally, the decisive function is the returned__of_address_to_resourcefunction. Jumping to the definition of this function is as follows.

12345678910111213141516171819202122232425
// drivers/of/address.cstatic int __of_address_to_resource(struct device_node *dev,		const __be32 *addrp, u64 size, unsigned int flags,		const char *name, struct resource *r){	u64 taddr;	if (flags & IORESOURCE_MEM)		taddr = of_translate_address(dev, addrp);	else if (flags & IORESOURCE_IO)		taddr = of_translate_ioport(dev, addrp, size);	else		return -EINVAL;	if (taddr == OF_BAD_ADDR)		return -EINVAL;	memset(r, 0, sizeof(struct resource));	r->start = taddr;	r->end = taddr + size - 1;	r->flags = flags;	r->name = name ? name : dev->full_name;	return 0;}

The flags of the reg property areIORESOURCE_MEM, so it will execute line 9’sof_translate_addressfunction. Jump to this function; its definition is as follows.

12345678910111213141516
// drivers/of/address.cu64 of_translate_address(struct device_node *dev, const __be32 *in_addr){	struct device_node *host;	u64 ret;	ret = __of_translate_address(dev, of_get_parent,				     in_addr, "ranges", &host);	if (host) {		of_node_put(host);		return OF_BAD_ADDR;	}	return ret;}EXPORT_SYMBOL(of_translate_address);

The key point of this function is at line 7. The above function is actually__of_translate_addressa wrapper of the function, where the third parameter passed inranges is the key point we need to focus on. Continue to jump to the definition of this function; the specific content is as follows:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
// drivers/of/address.c/* * Translate an address from the device-tree into a CPU physical address, * this walks up the tree and applies the various bus mappings on the * way. * * Note: We consider that crossing any level with #size-cells == 0 to mean * that translation is impossible (that is we are not dealing with a value * that can be mapped to a cpu physical address). This is not really specified * that way, but this is traditionally the way IBM at least do things * * Whenever the translation fails, the *host pointer will be set to the * device that had registered logical PIO mapping, and the return code is * relative to that node. */static u64 __of_translate_address(struct device_node *dev,				  struct device_node *(*get_parent)(const struct device_node *),				  const __be32 *in_addr, const char *rprop,				  struct device_node **host){	struct device_node *parent = NULL;	struct of_bus *bus, *pbus;	__be32 addr[OF_MAX_ADDR_CELLS];	int na, ns, pna, pns;	u64 result = OF_BAD_ADDR;	pr_debug("**translation for device %pOF**\n", dev);	/* Increase refcount at current level */	of_node_get(dev);	*host = NULL;	/* Get parent & match bus type */	parent = get_parent(dev);	if (parent == NULL)		goto bail;	bus = of_match_bus(parent);	/* Count address cells & copy address locally */	bus->count_cells(dev, &na, &ns);	if (!OF_CHECK_COUNTS(na, ns)) {		pr_debug("Bad cell count for %pOF\n", dev);		goto bail;	}	memcpy(addr, in_addr, na * 4);	pr_debug("bus is %s (na=%d, ns=%d) on %pOF\n",	    bus->name, na, ns, parent);	of_dump_addr("translating address:", addr, na);	/* Translate */	for (;;) {		struct logic_pio_hwaddr *iorange;		/* Switch to parent bus */		of_node_put(dev);		dev = parent;		parent = get_parent(dev);		/* If root, we have finished */		if (parent == NULL) {			pr_debug("reached root node\n");			result = of_read_number(addr, na);			break;		}		/*		 * For indirectIO device which has no ranges property, get		 * the address from reg directly.		 */		iorange = find_io_range_by_fwnode(&dev->fwnode);		if (iorange && (iorange->flags != LOGIC_PIO_CPU_MMIO)) {			result = of_read_number(addr + 1, na - 1);			pr_debug("indirectIO matched(%pOF) 0x%llx\n",				 dev, result);			*host = of_node_get(dev);			break;		}		/* Get new parent bus and counts */		pbus = of_match_bus(parent);		pbus->count_cells(dev, &pna, &pns);		if (!OF_CHECK_COUNTS(pna, pns)) {			pr_err("Bad cell count for %pOF\n", dev);			break;		}		pr_debug("parent bus is %s (na=%d, ns=%d) on %pOF\n",		    pbus->name, pna, pns, parent);		/* Apply bus translation */		if (of_translate_one(dev, bus, pbus, addr, na, ns, pna, rprop))			break;		/* Complete the move up one level */		na = pna;		ns = pns;		bus = pbus;		of_dump_addr("one level translation:", addr, na);	} bail:	of_node_put(parent);	of_node_put(dev);	return result;}

Lines 34 to 37 get the parent node and the matching bus type.

Line 40 getsaddress-cellandsize-cellsstored into int variables na and ns, respectively.

Line 52 is a for loop. In the loop, at line 92, it usesof_translate_onefunction to convert, whererpropThe parameter represents the resource property to be converted, and its value is the passed-in string."ranges"Then we continue to jump to this function; its specific content is as follows:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
// drivers/of/address.cstatic int of_translate_one(struct device_node *parent, struct of_bus *bus,			    struct of_bus *pbus, __be32 *addr,			    int na, int ns, int pna, const char *rprop){	const __be32 *ranges;	unsigned int rlen;	int rone;	u64 offset = OF_BAD_ADDR;	/*	 * Normally, an absence of a "ranges" property means we are	 * crossing a non-translatable boundary, and thus the addresses	 * below the current cannot be converted to CPU physical ones.	 * Unfortunately, while this is very clear in the spec, it's not	 * what Apple understood, and they do have things like /uni-n or	 * /ht nodes with no "ranges" property and a lot of perfectly	 * useable mapped devices below them. Thus we treat the absence of	 * "ranges" as equivalent to an empty "ranges" property which means	 * a 1:1 translation at that level. It's up to the caller not to try	 * to translate addresses that aren't supposed to be translated in	 * the first place. --BenH.	 *	 * As far as we know, this damage only exists on Apple machines, so	 * This code is only enabled on powerpc. --gcl	 *	 * This quirk also applies for 'dma-ranges' which frequently exist in	 * child nodes without 'dma-ranges' in the parent nodes. --RobH	 */	ranges = of_get_property(parent, rprop, &rlen);	if (ranges == NULL && !of_empty_ranges_quirk(parent) &&	    strcmp(rprop, "dma-ranges")) {		pr_debug("no ranges; cannot translate\n");		return 1;	}	if (ranges == NULL || rlen == 0) {		offset = of_read_number(addr, na);		memset(addr, 0, pna * 4);		pr_debug("empty ranges; 1:1 translation\n");		goto finish;	}	pr_debug("walking ranges...\n");	/* Now walk through the ranges */	rlen /= 4;	rone = na + pna + ns;	for (; rlen >= rone; rlen -= rone, ranges += rone) {		offset = bus->map(addr, ranges, na, ns, pna);		if (offset != OF_BAD_ADDR)			break;	}	if (offset == OF_BAD_ADDR) {		pr_debug("not found !\n");		return 1;	}	memcpy(addr, ranges + na, 4 * pna); finish:	of_dump_addr("parent translation for:", addr, pna);	pr_debug("with offset: %llx\n", (unsigned long long)offset);	/* Translate it into parent bus space */	return pbus->translate(addr, offset, pna);}

used at line 30 of the functionof_get_propertyfunction gets"ranges"attribute, but since the device tree node we added does not have this attribute, the ranges value here is NULL, the condition on line 34 holds, and it returns 1.

Next, continue analyzing the parent function based on this return value:

of_translate_oneAfter the function returns 1, the upper-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_resourcethe function will return-EINVAL;

of_address_to_resourceReturn-EINVAL, sonum_reg0;

At this point, regarding whyplatform_get_resourcethe function fails to acquire resources is found, just because the parent node in its device tree does not have this attribute namedrangesthis attribute, so just addrangesattribute.

Introduction to the ranges attribute

rangesThe attribute is a kind ofattribute used to describe the address mapping relationship between devices.It is used in the Device Tree todescribe how the child device address space maps to the parent device address space. Device Tree is a hardware description language used to describe hardware components in embedded systems and the connections between them.

Each device node in the device tree can haverangesattribute, which contains address mapping information.

The following is a common format:

1
ranges = <child-bus-address parent-bus-address length>;

or

1
ranges;
  • child-bus-address

The starting address of the child device address space.

It specifies the position of the child device in the parent device’s address space. The specific word length is determined byrangesof the node where it is located#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 to map the child device. The specific word length is determined byrangesof the parent node of#address-cellsproperty.

  • length

The size of the mapping. It specifies the length of the child device address space in the parent device address space. The specific word length is determined byrangesof the parent node of#size-cellsproperty.

whenrangesWhen the value of the property is empty, it indicates that the child device address space and the parent device address space have exactly the same mapping, i.e., a 1:1 mapping. This is usually used to describe memory regions where the child device and the parent device have the same address range.

whenrangesWhen the value of the property is not empty, the child device address space is mapped to the parent device address space according to the specified mapping rules. The specific mapping rules depend on the structure of the device tree and the specific requirements of the device.

example

123456789101112131415161718192021
/dts-v1/;/ {	compatible = "acme,coyotes-revenge";	#address-cells = <1>;	#size-cells = <1>;	....	external-bus {		#address-cells = <2>;		#size-cells = <1>;		ranges = <0 0 0x10100000 0x10000			  1 0 0x10160000 0x10000		          2 0 0x30000000 0x30000000>;		// Chipselect 1, Ethernet		// Chipselect 2, i2c controller		// Chipselect 3, NOR Flash		.......	};	......};

Beforeexternal-busIn the node#address-cellsA property value of 2 indicateschild-bus-addressis represented by two values, namely 0 and 0, and the parent node’s#address-cellsproperty value and#size-cellsproperty value is 1, indicatingparent-bus-addressandlengthare each represented by 1 value, namely0x10100000and0x10000, thisrangesvalue indicates that the child address space (0x0-0xFFFF) is mapped to the parent address space0x10100000 - 0x1010FFFF, the example here is with a parameterrangesproperty mapping, while the parameterlessrangesproperty is a 1:1 mapping, which is relatively simple and will not be exemplified 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 property is used to describe this address mapping relationship.

Device Classification

Memory-Mapped Devices

Memory-mapped devices aredevices 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 devices by reading and writing memory addresses.

  • Features
    • 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, and communication with the device is performed by reading and writing memory.
    • Read/Write Operations: The CPU can exchange data with the device and perform control operations by reading and writing mapped memory addresses.

In the device tree, an example of a device tree for a memory-mapped device is as follows:

1234567891011121314151617181920212223
/dts-v1/;/ {	#address-cells = <1>;	#size-cells = <1>;	ranges;	serial@101f0000 {		compatible = "arm,pl011";		reg = <0x101f0000 0x1000>;	};	gpio@101f3000 {		compatible = "arm,pl061";		reg = <0x101f3000 0x1000			   0x101f4000 0x10>;	};	spi@10115000 {		compatible = "arm,pl022";		reg = <0x10115000 0x1000>;	};};

Non-Memory-Mapped Devices

Non-memory-mapped devices are devices that cannot be directly accessed via memory addresses. Such devices may communicate with the CPU using other methods, such as I/O ports, dedicated buses, or specific communication protocols.

  • Features
    • Non-Memory Access: Non-memory-mapped devices cannot be directly accessed via memory addresses like memory-mapped devices. They may use separate I/O ports or dedicated buses for communication.
    • Specific Interfaces: Devices typically use specific interfaces and protocols to communicate with the CPU and to be controlled, such as SPI, I2C, UART, etc.
    • Driver: Non-memory-mapped devices typically require specific device drivers to implement communication with and control by the CPU.

In the device tree, an example of a device tree for a non-memory-mapped device is as follows:

12345678910111213141516171819202122232425262728293031323334
/dts-v1/;/ {	compatible = "acme,coyotes-revenge";	#address-cells = <1>;	#size-cells = <1>;	....	external-bus {		#address-cells = <2>;		#size-cells = <1>;		ranges = <0 0 0x10100000 0x10000			  1 0 0x10160000 0x10000			  2 0 0x30000000 0x30000000>;		// Chipselect 1, Ethernet		// Chipselect 2, i2c controller		// Chipselect 3, NOR Flash		ethernet@0,0 {			compatible = "smc,smc91c111";			reg = <0 0 0x1000>;		};		i2c@1,0 {			compatible = "acme,a1234-i2c-bus";			#address-cells = <1>;			#size-cells = <0>;			reg = <1 0 0x1000>;			rtc@58 {				compatible = "maxim,ds1338";				reg = <0x58>;			};		};	};};

Mapped Address Calculation

Next, in the device tree of the non-memory-mapped device listed above,ethernet@0take the node as an example to calculate the mapped address of the network card device.

First, findethernet@0the node where it is located, and look at its reg property. In the given device tree fragment,ethernet@0the reg property is<0 0 0x1000>. In the root node,#address-cellsthe value is 1, indicating that the address consists of one cell.

Next, according torangesthe property, perform address mapping calculation. Inexternal-busthe node’srangesproperty, there are three mapping entries:

  • The first mapping entry is0 0 0x10100000 0x10000, indicating that the address range of the external bus is0x10100000to0x1010FFFF. The first value of this mapping entry is 0, indicating that it is associated withexternal-busthe first child node of the node (ethernet@0,0) is associated.
  • The second mapping entry:1 0 0x10160000 0x10000, indicating that the address range of the external bus is0x10160000to0x1016FFFF. The first value of this mapping entry is 1, indicating that it is associated withexternal-busthe second child node of the node (i2c@1,0) is associated.
  • 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 is associated.

Sinceethernet@0andexternal-busis associated with the first child node, and its reg property is<0 0 0x1000>, we can perform the following calculation:

ethernet@0The physical start address = external bus address start value = 0x10100000

ethernet@0The physical end address = external bus address start value + (ethernet@0the second value of the reg attribute - 1) = $$ 0x10100000 + 0xFFF = 0x10100FFF $$

Therefore,ethernet@0The physical address range is0x10100000 - 0x10100FFF

The concept of named resources

When a driver expects a resource list of a certain type, since the person writing the board device tree is usually not the person writing the driver, there is no guarantee that the list is ordered in the way the driver expects. For example, a driver may expect its device node to have 2 IRQ lines, one for the Tx event at index 0 and another for Rx at index 1.

If this order is not satisfied, the driver will behave abnormally. To avoid this mismatch, the concept of named resources (clock, irq, dma, reg, etc.) is introduced. It consists of defining resource lists and naming, so that regardless of the index, a given name will always match the resource.

The corresponding properties of named resources are as follows.

  • reg-names: The list of memory regions in the reg attribute.
  • clock-names: Named clocks in the clocks attribute.
  • interrupt-names: Assign a name to each interrupt in the interrupts attribute.
  • dma-names: Used for the dma attribute.

For example

1234567891011
fake_device {	compatible = "packt,fake-device";	reg = <0x4a064000 0x800>, <0x4a0648000x200>, <0x4a064c00 0x200>;	reg-names = "config", "ohci", "ehci";	interrupts = <0 66 IRQ_TYPE_LEVEL_HIGH>, <0 67 IRQ_TYPE_LEVEL_HIGH>;	interrupt-names = "ohci", "ehci";	clocks = <&clks IMX6QDL_CLK_UART_IPG>, <&clks IMX6QDL_CLK_UART_SERIAL>;	clock-names = "ipg", "per";	dmas = <&sdma 25 4 0>, <&sdma 26 4 0>;	dma-names = "rx", "tx";};

The code for extracting each named resource in the driver is as follows:

123456789101112131415
struct resource *res1, *res2;res1 = platform_get_resource_byname(pdev,IORESOURCE_MEM, "ohci");res2 = platform_get_resource_byname(pdev,IORESOURCE_MEM, "config");struct dma_chan *dma_chan_rx, *dma_chan_tx;dma_chan_rx = dma_request_slave_channel(&pdev->dev, "rx");dma_chan_tx = dma_request_slave_channel(&pdev->dev, "tx");int txirq, rxirq;txirq = platform_get_irq_byname(pdev, "ohci");rxirq = platform_get_irq_byname(pdev, "ehci");structclk *clck_per, *clk_ipg;clk_ipg = devm_clk_get(&pdev->dev, "ipg");clk_ipg = devm_clk_get(&pdev->dev, "pre");

Accessing the reg attribute

Here, the driver will occupy the memory region and map it to the virtual address space.

12345678910111213
struct resource *res;void __iomem *base;res = platform_get_resource(pdev,IORESOURCE_MEM, 0);/* * Here, this function is equivalent to usingrequest_mem_region(res->start,resource_size(res), pdev->name) * and ioremap(iores->start, resource_size(iores)Requesting and mapping memory regions **/base = devm_ioremap_resource(&pdev->dev, res);if (IS_ERR(base))	return PTR_ERR(base);

Obtaining interrupt resources

irq_of_parse_and_map()

ItemDescription
Function definitionunsigned int irq_of_parse_and_map(struct device_node *dev, int index);
Header file#include <linux/of_irq.h>
Parameter devDevice tree node pointer (struct device_node *), indicating the device node whose interrupt number is to be parsed
Parameter indexIndex number, indicating from the device node’sinterruptsget which interrupt number from the property
Functionfrom the device node’sinterruptsparse and map the corresponding interrupt number in the attribute
Return valueSuccess: 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 the platform implementation)

irqd_get_trigger_type()

ItemDescription
Function definitionu32 irqd_get_trigger_type(struct irq_data *d);
Header file#include <linux/irq.h>
Parameter dPointer to the interrupt data structure (struct irq_data *), indicating the interrupt for which to get the trigger type
FunctionGet the trigger type of the corresponding interrupt from the interrupt data structure
Return valueSuccess: returns an unsigned 32-bit integer representing the interrupt trigger type (such as level-triggered or edge-triggered). It will not fail because it always returns a valid trigger type definition macro.

irq_get_irq_data()

ItemDescription
Function definitionstruct irq_data *irq_get_irq_data(unsigned int irq);
Header file#include <linux/irq.h>
Parameter irqInterrupt number, indicating the interrupt number for which to get the interrupt data structure
FunctionGet the corresponding interrupt data structure based on the interrupt number
Return valueSuccess: return pointerstruct irq_datapointer;
Failure: returns NULL

gpio_to_irq()

ItemDescription
Function definitionint gpio_to_irq(unsigned int gpio);
Header file#include <linux/gpio.h>
Parameter gpioGPIO number, indicating the GPIO for which to get the interrupt number
FunctionGet the corresponding interrupt number based on the GPIO number
Return valueSuccess: returns the corresponding interrupt number (integer);
Failure: return a negative error code

of_irq_get()

ItemDescription
Function definitionint of_irq_get(struct device_node *dev, int index);
Header file#include <linux/of_irq.h>
Parameter devDevice node, indicating the device node for which to get the interrupt number
Parameter indexIndex number, indicating frominterruptsget which interrupt number from the property
Functionfrom the device node’sinterruptsproperty, get the corresponding interrupt number
Return valueSuccess: returns the corresponding interrupt number (integer);
Failure: return a negative error code

platform_get_irq()

ItemDescription
Function definitionint platform_get_irq(struct platform_device *dev, unsigned int num);
Header file#include <linux/platform_device.h>
Parameter devPlatform device, indicating the platform device for which to get the interrupt number
Parameter numIndex number, indicating which interrupt number to get from the device
FunctionGet the corresponding interrupt number based on the platform device and index number
Return valueSuccess: returns the corresponding interrupt number (integer);
Failure: return a negative error code

Example

123456789101112131415161718192021222324252627282930
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)/* * Copyright (c) 2020 Rockchip Electronics Co., Ltd. * */#include "rk3568-evb1-ddr4-v10.dtsi"#include "rk3568-linux.dtsi"#include <dt-bindings/display/rockchip_vop.h>/{        test_device{                #address-cells =<1>;                #size-cells = <1>;                ranges;                compatible = "simple-bus";                myLed{                        compatible = "my devicetree";                        reg = <0xFDD60000 0x00000004>;                };                myirq{                        compatible = "my irq";                        interrupt-parent=<&gpio3>;                        interrupts = <RK_PA5 IRQ_TYPE_LEVEL_LOW>;                };        };};

Driver code

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
#include <linux/module.h>#include <linux/init.h>#include <linux/platform_device.h>#include <linux/of.h>#include <linux/of_irq.h>#include <linux/gpio.h>static int my_platform_driver_probe(struct platform_device *pdev){        int irq;        struct irq_data *my_irq_data;        struct device_node *mydev_node;        u32 trigger_type;        pr_info("my_platform_probe: Probing platform device\n");        // Get device node        mydev_node = pdev->dev.of_node;        // Parsing and Mapping Interrupts        irq = irq_of_parse_and_map(mydev_node, 0);        pr_info("[irq_of_parse_and_map]: irq is %d\n", irq);        // Get the Interrupt Data Structure        my_irq_data = irq_get_irq_data(irq);        // Get the Interrupt Trigger Type        trigger_type = irqd_get_trigger_type(my_irq_data);        pr_info("[irqd_get_trigger_type]: trigger_type is %d\n", trigger_type);        // Convert GPIO to an Interrupt Number        irq = gpio_to_irq(101);        pr_info("[gpio_to_irq]: irq is %d\n", irq);        // Get the Interrupt Number from the Device Node        irq = of_irq_get(mydev_node, 0);        pr_info("[of_irq_get]: irq is %d\n", irq);        // Get the Platform Device's Interrupt Number        irq = platform_get_irq(pdev, 0);        pr_info("[platform_get_irq]: irq is %d\n", irq);        return 0;}static int my_platform_driver_remove(struct platform_device *pdev){        return 0;}static const struct of_device_id of_match_table[] = { { .compatible = "my irq" }, {} };static struct platform_driver my_platform_drv = {        .driver = {                .owner = THIS_MODULE,                .name = "my platform driver",                .of_match_table = of_match_table,        },        .probe = my_platform_driver_probe,        .remove = my_platform_driver_remove,};module_platform_driver(my_platform_drv);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for platform irq");

Test:

123456789
root@topeet:/root# insmod of_api_irq_test.ko[ 7138.032958] of_api_irq_test: loading out-of-tree module taints kernel.[ 7138.033958] my_platform_probe: Probing platform device[ 7138.033994] [irq_of_parse_and_map]: irq is 94[ 7138.034002] [irqd_get_trigger_type]: trigger_type is 8[ 7138.034013] [gpio_to_irq]: irq is 94[ 7138.034025] [of_irq_get]: irq is 94[ 7138.034037] [platform_get_irq]: irq is 94

Reference Documentation: dts-bindings

Documentation/devicetree/bindingsThe directory is an important directory in the Linux kernel source code, used to store the bindings documentation for the Device Tree. The Device Tree is a data structure that describes the hardware platform and device configuration. It describes 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 for 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:

  • arm: Contains bindings documentation related to ARM architecture devices and drivers.
  • clock: Contains bindings documentation related to clock devices and clock controllers.
  • dma: Contains bindings documentation related to Direct Memory Access (DMA) controllers and devices.
  • gpio: Contains bindings documentation related to General Purpose Input/Output (GPIO) controllers and devices.
  • i2c: Contains bindings documentation related to I2C buses and devices.
  • interrupt-controller: Contains bindings documentation related to interrupt controllers.
  • media: Contains bindings documentation related to multimedia devices and drivers.
  • mfd: Contains bindings documentation related to the Multi-Function Device (MFD) subsystem and devices.
  • networking: Contains bindings documentation related to network devices and drivers.
  • power: Contains bindings documentation related to the power management subsystem and devices.
  • spi: Contains bindings documentation related to SPI buses and devices.
  • usb: Contains bindings documentation related to USB controllers and devices.
  • video: Contains bindings documentation related to video devices and drivers.

Documents in each subdirectory are usually saved with the.txtor.yamlextension, written in text or YAML format.

The YAML format followsjson-schemathe written Devicetree binding, refer to:

Device Tree Overlay

After Linux 4.4, dynamic device tree (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 used in embedded systems, especially in systems based on the Linux kernel.

Device Tree Overlay**Allows dynamically modifying the contents of the device tree 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 restarting the system.

Device Tree Overlay (Dynamic DeviceTree) is usually defined in a text format called Device Tree Source (DTS). The DTS file describes the structure and properties of the device tree, including device nodes, register addresses, interrupt information, etc. Device tree overlays can dynamically modify the device tree by loading and parsing device tree files and merging them into the existing device tree.

Application scenarios

Using device tree overlays, some common configuration changes can be achieved, such as adding external devices, disabling unnecessary devices, modifying device properties, etc. This is very useful for the development and debugging of embedded systems, especially when facing multiple hardware configurations or needing to change hardware configurations frequently.

Device Tree Overlay Syntax

overlay.dts

  • Header Declaration
12
/dts-v1/;/plugin/;
  • 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 uses specific modifiers before the node name to indicate the overlay operation.

For example, the following node:

12345678
//arch/arm64/boot/dts/rockchip/topeet-rk3568-linux.dts		//RS485 enable pin        rk_485_ctl: rk-485-ctl {                compatible = "topeet,rs485_ctl";                gpios = <&gpio0 RK_PC6 GPIO_ACTIVE_HIGH>;                pinctrl-names = "default";                pinctrl-0 = <&rk_485_gpio>;        };

If you want to add to this node in the device tree overlayoverlay_nodenode:

There are the following expressions:

12345678910111213141516171819202122232425262728293031323334353637
/dts-v1/;/plugin/;// Method 1&{/rk-485-ctl}{	overlay_node{		status = "okay";	};};// Method 2&rk_485_ctl{	overlay_node{		status = "okay";	};};// Method 3/{    fragment@0{        target-path="/rk-485-ctl";        __overlay__{            overlay_node{                status = "okay";            };        };    };    fragment@1{        target=<&rk_485_ctl>;        __overlay__{            overlay_node{                status = "okay";            };        };    };};

Compiling device tree overlays is the same as compiling device trees.

1
dtc -I dts -O dtb overlay.dts -o overlay.dtbo

Porting Device Tree Driver Overlay

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 the device tree overlay driver.

Configure the kernel to support mounting the configfs virtual file system.

12345
$ make ARCH=arm64 menuconfig# The path is as follows.File System-> 	Pseudo filesystems ->		-*- UserSpace-driven configuration filesystem

After booting, check whether 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.

12345678910111213141516
$ make ARCH=arm64 menuconfig# The path is as follows.Device Driver ->	-*- Device Tree and Open Firmware support ->		--- Device Tree and Open Firmware support		                [*]   Enable dtc generation of symbols for overlays support # Generate symbols for the main device tree for overlay references.		                [ ]   Device Tree runtime unit tests # Runtime testing of the kernel device tree subsystem.		                [*]   Device Tree overlays # Support runtime dynamic loading of device tree overlays.File System ->		<*> Overlay filesystem support # Enable OverlayFS core support.        [*]   Overlayfs: turn on redirect directory feature by default # Redirect directory is an optimization feature of OverlayFS.	        [*]   Overlayfs: follow redirects even if redirects are turned off # Even if the redirect feature is disabled at mount time (redirect_dir=off), the kernel will still attempt to recognize and follow existing redirect metadata. This is used for compatibility with existing redirects.	        [*]   Overlayfs: turn on inodes index feature by default # The inode index feature is used to solve the 'stale inode' problem. It ensures inode consistency and supports hardlinks.	        [*]   Overlayfs: auto enable inode number mapping # Automatically assign globally unique and persistent inode numbers to files in the upper layer (via the index mechanism). This depends on the inode index feature above. Without this, the inode number of the same file may change each time OverlayFS is mounted.	        [*]   Overlayfs: turn on metadata only copy up feature by default # Metadata-only copy up is a performance optimization. It does not copy the entire file when modifying attributes.

Save configuration:

12
cp .config arch/arm64/configs/rockchip_linux_defconfig../build.sh kernel

Port the device tree overlay driver.

There is already a pre-written one on GitHub.Device tree overlay driver.We just need to compile this driver as a kernel module or into the kernel.

Compile:

12
$ git clone https://github.com/ikwzm/dtbocfg.git$ make KERNEL_SRC=/home/zhaohang/repository/linux/rk3568_linux_5.10/kernel ARCH=arm64 CROSS_COMPILE=/home/zhaohang/repository/linux/rk3568_linux_5.10/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-

Compile and generate.dtbocfg.koJust copy it to the development board.

Load the device tree overlay.

Load the device tree overlay (first you need the device tree overlay driver, throughcat proc/filesystemsCheck whether configfs is mounted successfully)

Enter the system directory/sys/kernel/config/device-tree/overlays/

123456789101112131415161718192021
$ insmod dtbocfg.ko[  430.997017] dtbocfg: loading out-of-tree module taints kernel.[  430.997630] dtbocfg: 0.1.1[  430.997675] dtbocfg: OK$ cd /sys/kernel/config/device-tree/overlays/# Create a kernel object$ mkdir test$ cd test$ lsdtbo status$ cat /root/overlay.dtb > dtbo# Enable dtbo$ echo 1 > status# At this point, we can use the following command to see the loaded nodes$ ls /proc/device-tree/rk-485-ctl/overlay_node/# If we want to remove the nodes modified by dtbo, just delete the created kernel object test$ cd /sys/kernel/config/device-tree/overlays/$ rmdir test

You can create multiple. If multiple modify the same property, the last overlay’s changes apply.

12345678910
$ cd /sys/kernel/config/device-tree/overlays/# Create a kernel object$ mkdir test1# Overlay with another device tree...# When deleted, it will restore to the device tree after test was loaded.$ rmdir test1

changeset

The nodes in the device tree overlay (dtbo) also need to be converted todevice_node, somedevice_nodealso need to be converted toplatform_device. However, before performing the conversion,of_overlay_fdt_applythe function will first create achangeset. Then it makes modifications based on this changeset.

The purpose of creating a changeset is to facilitate modification and restoration of the device tree.The device tree is a static data structure,Once compiled and loaded into the kernel,it is difficult to modify directly. To solve this problem, the device tree overlay feature introduces the concept of a changeset.

A changeset is a data structure that describes changes to the device tree. It records modification operations on the device tree, such as adding, deleting, or modifying nodes. By creating a changeset, we can dynamically modify the device tree at runtime without modifying the original device tree source file.

  • By creating a changeset, we can conveniently define the modification operations to be performed without directly manipulating the underlying structure of the device tree. This provides a high-level abstraction that allows us to describe device tree changes in a more concise and readable way.
  • At the same time, changesets can also be saved, transferred, and applied to other device trees, facilitating the configuration and customization of device trees in different systems or environments.
  • In addition, 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 restore it to its original state. By applying a reverse changeset, we can restore the device tree to its state before modification, achieving restoration of the changes.

Therefore, creating changesets provides a convenient, controllable, and reversible way to modify the device tree.

Introduction to the ConfigFS Virtual File System

In the Linux kernel, there are several commonly used virtual file systems. A virtual file system provides a kernel abstraction layer that allows applications to operate on different types of files and devices through a unified file access interface. It simplifies application development and maintenance, provides higher portability and flexibility, and offers functionality for managing file systems and accessing underlying hardware.

  • procfs

Virtual file system,provides an access interface to the runtime state of the system kernelIt represents processes, devices, drivers, and other system information in the kernel in the form of files and directories. By reading and writing files in procfs, information about system state can be obtained and modified.

  • sysfs

Virtual file system,used to represent devices, drivers, and other kernel objects in the systemIt provides a unified interface to access and configure the attributes 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

Virtual file system,used for dynamically configuring and managing kernel objectsIt 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 restarting the system. ConfigFS is often used to configure and manage devices, drivers, and subsystems

These virtual file systems have some differences in functionality:

  • procfsIt is mainly used to access and manage process information, providing information about processes, kernel parameters, and system state.
  • sysfsIt is mainly used to represent and configure devices, drivers, and other kernel objects in the system, providing a unified interface to access and control the attributes and states of these objects.
  • configfsIt is mainly used for dynamically configuring and managing kernel objects, providing an interface to access kernel objects in the form of files and directories, allowing kernel objects to be added, modified, and deleted at runtime.

Linux startup handling of the device tree
Linux startup handling of the device tree

To achieve the above functions, user space needs to interact with the kernel, that is, to load thedtbointo memory.sysfsThe role of the virtual file system is to export data and attributes in the kernel to user space in the form of files. After being exported to user space, reading these files means reading device files, and writing to these files means controlling devices.

configfsThe English explanation of its role is Userspace-driven kernel object configuration, which translates to user space configuring kernel objects.

SoconfigfsandsysfsOn the contrary,sysfsExport kernel objects to user space,configfsIt is to configure kernel objects from user space, and does not require recompiling the kernel or modifying kernel code. Thereforeconfigfsit is 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_subsystemIt is a top-level data structure used to represent the entire ConfigFS subsystem. It contains a pointer to the root configuration item group, as well as other attributes and status information of ConfigFS.

  • config_group

config_groupIt is a special type of configuration item, representing a configuration item group. It can contain a set of related configuration items, forming a hierarchical structure.config_groupThe structure contains a pointer to the parent configuration item, as well as a linked list pointing to child configuration items.

  • config_item

This is the most basic data structure in ConfigFS, used to represent a configuration item. Each configuration item is a kernel object, which can be a device, driver, subsystem, etc.config_itemThe structure contains information such as the type, name, attributes, and status of the configuration item, as well as pointers to the parent and child configuration items.

The relationships among these data structures can form a tree structure, in whichconfigfs_subsystemis the root node,config_grouprepresents a configuration item group,config_itemrepresents a single configuration item. Child configuration items are linked together through a linked list, forming parent-child relationships.

configfs_subsystem

1234
struct configfs_subsystem {	struct config_group su_group;	struct mutex su_mutex;};

struct configfs_subsystemThe structure containsstruct config_groupstructure,struct config_groupThe structure is as follows:

config_group

1234567
struct config_group {	struct config_item cg_item;	struct list_head cg_children;	struct configfs_subsystem *cg_subsys;	struct list_head default_groups;	struct list_head group_entry;};

struct config_groupThe structure containsstruct config_itemstructure,struct config_itemThe structure is as follows:

config_item

12345678910
struct config_item {	char *ci_name;	char ci_namebuf[CONFIGFS_ITEM_NAME_LEN]; //the name of the directory	struct kref ci_kref;	struct list_head ci_entry;	struct config_item *ci_parent;	struct config_group *ci_group;	const struct config_item_type *ci_type; //attribute files and attribute operations under the directory	struct dentry *ci_dentry;};

struct config_itemThe structure containsstruct config_item_typestructure,struct config_item_typeThe structure is as follows

config_item_type

1234567
struct config_item_type {	struct module *ct_owner;	struct configfs_item_operations *ct_item_ops; //operation methods of item (directory)	struct configfs_group_operations *ct_group_ops; //operation methods of group (container)	struct configfs_attribute **ct_attrs; //operation methods of attribute files	struct configfs_bin_attribute**ct_bin_attrs; //operation methods of bin attribute files};

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 files
  • struct configfs_bin_attribute **ct_bin_attrsoperation methods of bin attribute files

eachconfig_item(orconfig_group) must be bound to one during initializationconfig_item_type
This structure determines what kind of “operable object” the item represents in sysfs.

configfs_item_operations

1234567891011
struct configfs_item_operations {	// Called when the item's reference count drops to zero.	void (*release)(struct config_item *);		// Whether to allow creating a symbolic link between src and target (`ln -s`)	int (*allow_link)(struct config_item *src, struct config_item *target);	// Called when the link is deleted.	void (*drop_link)(struct config_item *src, struct config_item *target);};
release()
ItemDescription
Prototypevoid (*release)(struct config_item *);
TriggerReference count is 0
Responsibilitykfree / resource release
Must✅ Required

configfs_group_operations

1234567891011121314151617
struct configfs_group_operations {		//Method to create an item; called when the mkdir command is used under a group.	struct config_item *(*make_item)(struct config_group *group, const char *name);		//Method to create a group; when the user executes `mkdir <name>` and wants to create a child group, it is called.	struct config_group *(*make_group)(struct config_group *group, const char *name);		// (Optional) Called when the item is "committed" after configuration is complete (rarely used).	int (*commit_item)(struct config_item *item);		// Called before the item is about to be removed (can be used to clean up resources).	void (*disconnect_notify)(struct config_group *group, struct config_item *item);		// When the item is `rmdir` Called after deletion (note: the item has already been removed from the directory tree), usually used to unbind from the group.	void (*drop_item)(struct config_group *group, struct config_item *item);};
make_item()
ItemDescription
Prototypestruct config_item *(*make_item)(struct config_group *, const char *name);
TriggerUser mkdir
FunctionDynamically create item
MustIf item creation is allowed
make_group()
ItemDescription
Prototypestruct config_group *(*make_group)(struct config_group *, const char *name);
Triggermkdir
FunctionCreate child group (multi-level directory)
drop_item()
ItemDescription
Prototypevoid (*drop_item)(struct config_group *, struct config_item *);
Triggerrmdir
Responsibilityconfig_item_put()
Must✅ Yes

struct configfs_attribute

12345678910
struct configfs_attribute {	const char *ca_name; // Name of the property file	struct module *ca_owner;// Module to which the property file belongs	umode_t ca_mode; // Access permissions of the property file		// Called when reading a property; returns the number of bytes. The specific functionality needs to be implemented by yourself.	ssize_t (*show)(struct config_item *, char *);	// Called when writing a property; the specific functionality needs to be implemented by yourself.	ssize_t (*store)(struct config_item *, const char *, size_t);};

Key data structure relationship diagram

Key data structure relationship diagram
Key data structure relationship diagram

ConfigFS API

Core API

config_group_init()

ItemDescription
Function definitionvoid config_group_init(struct config_group *group);
Header file#include <linux/configfs.h>
Parameter groupThe config_group to be initialized
FunctionInitialize the basic structure of a group (without name/type)
Typical scenariosInitialize the su_group of the subsystem
Return valueNone

config_group_init_type_name()

ItemDescription
Function definitionvoid 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 groupThe group to be initialized
Parameter nameThe directory name of the group displayed in configfs
Parameter typeThe corresponding config_item_type
FunctionInitialize group + set name + bind type
Typical scenariosCreate a visible directory node
Return valueNone

config_item_init_type_name()

ItemDescription
Function definitionvoid 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 itemconfig_item object
Parameter nameitem name
Parameter typeItem type
FunctionInitialize an item and bind the type
Typical scenariosUsed in make_item
Return valueNone

configfs_register_subsystem()

ItemDescription
Function definitionint configfs_register_subsystem(struct configfs_subsystem *subsys);
Header file#include <linux/configfs.h>
Parameter subsysThe configfs subsystem to register
FunctionBefore/sys/kernel/config/Register the subsystem directory below
EffectCreate/sys/kernel/config/<name>
Return valueSuccess: 0, failure: negative error code

configfs_unregister_subsystem()

ItemDescription
Function definitionvoid configfs_unregister_subsystem(struct configfs_subsystem *subsys);
FunctionUnregister the entire configfs subsystem
EffectDelete the subsystem directory and all sub-items
Return valueNone

configfs_register_group()

ItemDescription
Function definitionint configfs_register_group(struct config_group *parent, struct config_group *group);
Parameter parentParent group
Parameter groupThe child group to register
FunctionRegister a group under the parent directory
Features✅ Static group (users cannot rmdir)
Return valueSuccess: 0

configfs_unregister_group()

ItemDescription
Function definitionvoid configfs_unregister_group(struct config_group *group);
FunctionUnregister group
NoteOnly applies to groups registered by register_group

config_item_put()

ItemDescription
Function definitionvoid config_item_put(struct config_item *item);
Functiondecrement reference count
TriggerReference count reaches 0 → call release
Common locationsIn the drop_item callback
Return valueNone

Can be usedCONFIGFS_ATTRRelated macros

CONFIGFS_ATTR_RO()

ItemDescription
Macro definitionCONFIGFS_ATTR_RO(prefix, name)
Generateread-only attribute
required functionprefix_name_show()
generated variableprefixattr_name

CONFIGFS_ATTR_WO()

ItemDescription
Macro definitionCONFIGFS_ATTR_WO(prefix, name)
Generatewrite-only attribute
required functionprefix_name_store()

CONFIGFS_ATTR()

ItemDescription
Macro definitionCONFIGFS_ATTR(prefix, name)
Generateread-write attribute

example

register configFS subsystem

12345678910111213141516171819202122232425262728293031323334353637383940414243
#include <linux/module.h>#include <linux/init.h>#include <linux/configfs.h>static const struct config_item_type myconfig_item_type = {        .ct_owner = THIS_MODULE,        .ct_item_ops = NULL,        .ct_group_ops = NULL,        .ct_attrs = NULL,        // .ct_bin_attrs = NULL,};static struct configfs_subsystem myconfigfs_subsystem = {        .su_group = {                .cg_item = {                        .ci_namebuf = "myconfigfs",                        .ci_type = &myconfig_item_type,                },        },};static int __init myconfigfs_init(void){        // initialize config_group        config_group_init(&myconfigfs_subsystem.su_group);        // Registration Subsystem        configfs_register_subsystem(&myconfigfs_subsystem);        return 0;}static void __exit myconfigfs_exit(void){        configfs_unregister_subsystem(&myconfigfs_subsystem);}module_init(myconfigfs_init);module_exit(myconfigfs_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for configfs ");

After loading, you can/sys/kernel/configsee the registered subsystem myconfigfs in the directory

12
$ ls /sys/kernel/configdevice-tree  myconfigfs

register group container

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
#include <linux/module.h>#include <linux/init.h>#include <linux/configfs.h>// create mygroup under myconfigfsstatic struct config_group mygroup;// mygroup_config_item_type, used to describe the configuration item type of mygroupstatic const struct config_item_type mygroup_config_item_type = {        .ct_owner = THIS_MODULE,        .ct_item_ops = NULL,        .ct_group_ops = NULL,        .ct_attrs = NULL,};// myconfig_item_type, a structure used to describe the configuration item typestatic const struct config_item_type myconfig_item_type = {        .ct_owner = THIS_MODULE,        .ct_group_ops = NULL,};static struct configfs_subsystem myconfigfs_subsystem = {        .su_group = {                .cg_item = {                        .ci_namebuf = "myconfigfs",                        .ci_type = &myconfig_item_type,                },        },};static int __init myconfigfs_group_init(void){        // Initialize configuration group.        config_group_init(&myconfigfs_subsystem.su_group);        // Registration Subsystem        configfs_register_subsystem(&myconfigfs_subsystem);        // initialize configuration group "mygroup"        config_group_init_type_name(&mygroup, "mygroup", &mygroup_config_item_type);        // configure group "mygroup" in the subsystem        configfs_register_group(&myconfigfs_subsystem.su_group, &mygroup);        return 0;}static void __exit myconfigfs_group_exit(void){	// unregister subsystem	configfs_unregister_subsystem(&myconfigfs_subsystem);}module_init(myconfigfs_group_init);module_exit(myconfigfs_group_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a sample for configfs: register group");

Test

1234
$ ls /sys/kernel/config/device-tree  myconfigfs$ ls /sys/kernel/config/myconfigfs/mygroup

create item in user space

We have successfully/sys/kernel/config/created in the directorymyconfigfssubsystem, and created under this subsystemmygroupcontainer, butmygroupCannot use mkdir to create an item under a container.

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
#include <linux/init.h>#include <linux/module.h>#include <linux/configfs.h>#include <linux/slab.h>static struct config_group mygroup;struct myitem {        struct config_item item;};void myitem_release(struct config_item *item){        struct myitem *mitem = container_of(item, struct myitem, item);        kfree(mitem);        pr_info("%s\n", __func__);}struct configfs_item_operations myitem_ops = {        .release = myitem_release,};static struct config_item_type mygroup_config_item_type = {        .ct_owner = THIS_MODULE,        .ct_item_ops = &myitem_ops,};struct config_item *rootgroup_make_item(struct config_group *group, const char *name){        struct myitem *my_config_item;        pr_info("%s\n", __func__);        my_config_item = kzalloc(sizeof(*my_config_item), GFP_KERNEL);        config_item_init_type_name(&my_config_item->item, name, &mygroup_config_item_type);        return &my_config_item->item;}struct configfs_group_operations rootgroup_ops = {        .make_item = rootgroup_make_item,};static struct config_item_type rootgroup_config_item_type = {        .ct_owner = THIS_MODULE,        .ct_group_ops = &rootgroup_ops,};static struct configfs_subsystem myconfigfs_subsystem = {        .su_group = {                .cg_item = {                        .ci_namebuf = "myconfigfs",                        .ci_type = &rootgroup_config_item_type,                },        },};static int __init myconfigfs_test_init(void){        // Initialize configuration group.        config_group_init(&myconfigfs_subsystem.su_group);        // Registration Subsystem        configfs_register_subsystem(&myconfigfs_subsystem);        // Initialize configuration group mygroup.        config_group_init_type_name(&mygroup, "mygroup", &mygroup_config_item_type);        // Mount mygroup onto myconfigfs._subsystem.su_under the group        configfs_register_group(&myconfigfs_subsystem.su_group, &mygroup);        return 0;}static void __exit myconfigfs_test_exit(void){        // Unregister myconfigfs_subsystem.        configfs_unregister_subsystem(&myconfigfs_subsystem);}module_init(myconfigfs_test_init);module_exit(myconfigfs_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("this is a test sample for configfs");

Test:

1234567891011121314
~ $ insmod configfs_make_item_test.ko[   12.066995] configfs_make_item_test: loading out-of-tree module taints kernel.~ $ ls /sys/kernel/config/device-tree  myconfigfs~ $ ls /sys/kernel/config/myconfigfs/mygroup~ $ cd /sys/kernel/config/myconfigfs//sys/kernel/config/myconfigfs $ lsmygroup/sys/kernel/config/myconfigfs $ mkdir test[   43.220027] rootgroup_make_item/sys/kernel/config/myconfigfs $ ls test/sys/kernel/config/myconfigfs $ rmdir test[   55.003042] myitem_release

Improve drop and release.

releaseanddrop_itemare two different member fields used for different purposes:

  • releaseThe member field is defined instruct config_item_typeA callback function pointer defined in the structure. It points to a function that the kernel calls to perform corresponding resource release operations when a configuration item in configfs is released or deleted. It is usually used to release resources associated with the configuration item, such as freeing dynamically allocated memory, closing open file descriptors, etc.
  • drop_itemis instruct configfs_group_operationsA callback function pointer defined in the structure. It points to a function that the kernel calls to handle operations related to the configuration group when a configuration group in configfs is deleted. This function is usually used to clean up the state of the configuration group, release related resources, and perform other necessary cleanup operations.drop_itemThe function is called when a configuration group is deleted, not when a single configuration item is deleted.

releaseThe member field is used for the release operation of configuration items, whiledrop_itemThe member field is used for the deletion operation of configuration groups. They perform different tasks in different contexts, but both are related to resource release and cleanup.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
#include <linux/init.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/configfs.h>struct myitem {        struct config_item conf_item;};void child_release(struct config_item *item){        struct myitem *myitem = container_of(item, struct myitem, conf_item);        kfree(myitem);        pr_info("%s\n", __func__);}static struct configfs_item_operations child_item_ops = {        .release = child_release,};static struct config_item_type child_config_item_type = {        .ct_owner = THIS_MODULE,        .ct_item_ops = &child_item_ops,};struct config_item *root_make_item(struct config_group *group, const char *name){        struct myitem *myitem;        myitem = kzalloc(sizeof(*myitem), GFP_KERNEL);        config_item_init_type_name(&myitem->conf_item, name, &child_config_item_type);        pr_info("%s\n", __func__);        return &myitem->conf_item;}// When a configuration group (group) in configfs is deleted (a group created under the root directory), the kernel calls this function to handle operations related to the configuration group.void root_drop_item(struct config_group *group, struct config_item *item){        struct myitem *myitem = container_of(item, struct myitem, conf_item);        config_item_put(&myitem->conf_item);        pr_info("%s\n", __func__);}static struct configfs_group_operations root_configfs_group_ops = {        .make_item = root_make_item,        .drop_item = root_drop_item,};static const struct config_item_type root_config_item_type = {        .ct_owner = THIS_MODULE,        .ct_group_ops = &root_configfs_group_ops,};static struct configfs_subsystem test_configfs_subsystem = {        .su_group = {                .cg_item = {                        .ci_namebuf = "myconfigfs",                        .ci_type = &root_config_item_type,                },        },};static struct config_group child_group1;static int __init configfs_test_init(void){        config_group_init(&test_configfs_subsystem.su_group);        configfs_register_subsystem(&test_configfs_subsystem);        // Initialize configuration group child_group1.        config_group_init_type_name(&child_group1, "child_group1", &child_config_item_type);        configfs_register_group(&test_configfs_subsystem.su_group, &child_group1);        return 0;}static void __exit configfs_test_exit(void){        configfs_unregister_subsystem(&test_configfs_subsystem);}module_init(configfs_test_init);module_exit(configfs_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test description for configfs");

Test:

123456789101112
$ insmod configfs_release_and_drop_test.ko[   11.641528] configfs_release_and_drop_test: loading out-of-tree module taints kernel.$ cd /sys/kernel/config/myconfigfs//sys/kernel/config/myconfigfs $ lschild_group1/sys/kernel/config/myconfigfs $ mkdir test[   26.684113] root_make_item/sys/kernel/config/myconfigfs $ rmdir test[   30.148208] root_drop_item[   30.148397] child_release/sys/kernel/config/myconfigfs $ rmdir child_group1/rmdir: 'child_group1/': Operation not permitted

child_group1is throughconfigfs_register_groupA statically registered subgroup is “fixed” by default and cannot be deleted by user space.

functionCall timingResponsibilityWhether required
root_drop_itemCalled by the parent group at the start of rmdir.Disassociate the item from the group and decrease the reference count.✅ Required (otherwise release cannot be triggered)
child_releaseAutomatically called when the reference count reaches zeroRelease the item’s own memory and resources✅ Required (otherwise memory leak)

Register attribute

We successfully created the item, but no attributes or operation items were created under the item.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
#include <linux/init.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/configfs.h>struct myitem {        struct config_item conf_item;        int size;        void *addr;};void child_release(struct config_item *item){        struct myitem *myitem = container_of(item, struct myitem, conf_item);        kfree(myitem);        pr_info("%s\n", __func__);}static struct configfs_item_operations child_item_ops = {        .release = child_release,};ssize_t myread_show(struct config_item *item, char *page){        struct myitem *myitem = container_of(item, struct myitem, conf_item);        memcpy(page, myitem->addr, myitem->size);        pr_info("%s\n", __func__);        return myitem->size;}ssize_t mywrite_store(struct config_item *item, const char *page, size_t size){        struct myitem *myitem = container_of(item, struct myitem, conf_item);        myitem->addr = kmemdup(page, size, GFP_KERNEL);        myitem->size = size;        pr_info("%s\n", __func__);        return myitem->size;}// Create read-only configuration item myreadCONFIGFS_ATTR_RO(my, read);// Create write-only configuration item mywriteCONFIGFS_ATTR_WO(my, write);static struct configfs_attribute *my_attrs[] = {        &myattr_read,        &myattr_write,        NULL,};static struct config_item_type child_config_item_type = { .ct_owner = THIS_MODULE,                                                          .ct_item_ops = &child_item_ops,                                                          .ct_attrs = my_attrs };struct config_item *root_make_item(struct config_group *group, const char *name){        struct myitem *myitem;        myitem = kzalloc(sizeof(*myitem), GFP_KERNEL);        config_item_init_type_name(&myitem->conf_item, name, &child_config_item_type);        pr_info("%s\n", __func__);        return &myitem->conf_item;}// When a configuration group (group) in configfs is deleted (a group created under the root directory), the kernel calls this function to handle operations related to the configuration group.void root_drop_item(struct config_group *group, struct config_item *item){        struct myitem *myitem = container_of(item, struct myitem, conf_item);        config_item_put(&myitem->conf_item);        pr_info("%s\n", __func__);}static struct configfs_group_operations root_configfs_group_ops = {        .make_item = root_make_item,        .drop_item = root_drop_item,};static const struct config_item_type root_config_item_type = {        .ct_owner = THIS_MODULE,        .ct_group_ops = &root_configfs_group_ops,};static struct configfs_subsystem test_configfs_subsystem = {        .su_group = {                .cg_item = {                        .ci_namebuf = "myconfigfs",                        .ci_type = &root_config_item_type,                },        },};static struct config_group child_group1;static int __init configfs_test_init(void){        config_group_init(&test_configfs_subsystem.su_group);        configfs_register_subsystem(&test_configfs_subsystem);        // Initialize configuration group child_group1.        config_group_init_type_name(&child_group1, "child_group1", &child_config_item_type);        configfs_register_group(&test_configfs_subsystem.su_group, &child_group1);        return 0;}static void __exit configfs_test_exit(void){        configfs_unregister_subsystem(&test_configfs_subsystem);}module_init(configfs_test_init);module_exit(configfs_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test description for configfs");

Test:

12345678910111213141516171819
$ insmod configfs_attribute_test.ko[   37.565258] configfs_attribute_test: loading out-of-tree module taints kernel.$ cd /sys/kernel/config//sys/kernel/config $ lsdevice-tree  myconfigfs/sys/kernel/config $ cd myconfigfs//sys/kernel/config/myconfigfs $ lschild_group1/sys/kernel/config/myconfigfs $ cd child_group1//sys/kernel/config/myconfigfs/child_group1 $ lsread   write/sys/kernel/config/myconfigfs/child_group1 $ echo 1 > write[   71.704345] mywrite_store/sys/kernel/config/myconfigfs/child_group1 $ cat read[   75.550669] myread_show1/sys/kernel/config/myconfigfs/child_group1 $ cat read[   78.938556] myread_show1

Implement multi-level directories

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
#include <linux/init.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/configfs.h>struct myitem {        struct config_item conf_item;        int size;        void *addr;};struct mygroup {        struct config_group conf_group;};void child_item_release(struct config_item *item){        struct myitem *myitem = container_of(item, struct myitem, conf_item);        kfree(myitem);        pr_info("%s\n", __func__);}static struct configfs_item_operations child_item_ops = {        .release = child_item_release,};// static struct configfs_group_operations child_group_ops = {// };ssize_t myread_show(struct config_item *item, char *page){        struct myitem *myitem = container_of(item, struct myitem, conf_item);        if (myitem->size > 0 && myitem->addr != NULL)                memcpy(page, myitem->addr, myitem->size);        pr_info("%s\n", __func__);        return myitem->size;}ssize_t mywrite_store(struct config_item *item, const char *page, size_t size){        struct myitem *myitem = container_of(item, struct myitem, conf_item);                myitem->addr = kmemdup(page, size, GFP_KERNEL);        myitem->size = size;        pr_info("%s\n", __func__);        return myitem->size;}CONFIGFS_ATTR_RO(my, read);CONFIGFS_ATTR_WO(my, write);struct configfs_attribute *child_item_attribute[] = {        &myattr_read,        &myattr_write,        NULL,};static struct config_item_type child_item_config_item_type = {        .ct_owner = THIS_MODULE,        .ct_item_ops = &child_item_ops,        .ct_attrs = child_item_attribute,};// Second-level folderstatic struct config_item_type child_group_config_item_type = {        .ct_owner = THIS_MODULE,        // .ct_group_ops = &child_group_ops,        .ct_group_ops = NULL,};// root folderstruct config_item *root_make_item(struct config_group *group, const char *name){        struct myitem *myitem;        myitem = kzalloc(sizeof(*myitem), GFP_KERNEL);        config_item_init_type_name(&myitem->conf_item, name, &child_item_config_item_type);        pr_info("%s\n", __func__);        return &myitem->conf_item;}struct config_group *root_make_group(struct config_group *group, const char *name){        struct mygroup *mygroup;        mygroup = kzalloc(sizeof(*mygroup), GFP_KERNEL);        config_group_init_type_name(&mygroup->conf_group, name, &child_group_config_item_type);        pr_info("%s\n", __func__);        return &mygroup->conf_group;}void root_drop_item(struct config_group *group, struct config_item *item){        config_item_put(item);        pr_info("%s\n", __func__);}static struct configfs_group_operations root_group_ops = {        .make_item = root_make_item,        .make_group = root_make_group,        .drop_item = root_drop_item,};static struct config_item_type root_config_item_type = {        .ct_owner = THIS_MODULE,        .ct_group_ops = &root_group_ops,};static struct configfs_subsystem configfs_test_subsystem = {        .su_group = {                .cg_item = {                        .ci_namebuf = "myconfigfs",                        .ci_type = &root_config_item_type,                },        },};static struct config_group mygroup;static int __init configfs_test_init(void){        config_group_init(&configfs_test_subsystem.su_group);        configfs_register_subsystem(&configfs_test_subsystem);        config_group_init_type_name(&mygroup, "mygroup", &child_group_config_item_type);        configfs_register_group(&configfs_test_subsystem.su_group, &mygroup);        return 0;}static void __exit configfs_test_exit(void){        configfs_unregister_group(&mygroup);}module_init(configfs_test_init);module_exit(configfs_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@outlook.com>");MODULE_DESCRIPTION("This is a test sample for configfs");

Note that:
User executesmkdir /sys/kernel/config/myconfigfs/xxx, ConfigFS first calls.make_item(if defined), on failure it attempts to call.make_group(if defined)

dtbocfg driver analysis

Next, let’s analyze the device tree overlay driver.dtbocfg.koCode:

123456789
static struct configfs_subsystem dtbocfg_root_subsys = {    .su_group = {        .cg_item = {            .ci_namebuf = "device-tree",            .ci_type   = &dtbocfg_root_type,        },    },    .su_mutex = __MUTEX_INITIALIZER(dtbocfg_root_subsys.su_mutex),};

This code defines a file nameddtbocfg_root_subsysofconfigfs_subsystemstructure instance,represents ConfigFS a subsystem in

First,dtbocfg_root_subsys.su_groupis aconfig_groupstructure, which represents the root configuration group of the subsystem. Here, the structure’scg_itemfield represents the basic configuration items of the root configuration group.

.ci_namebuf = "device-tree": The name of the configuration item is set todevice-tree, indicating that the name of this configuration item isdevice-tree

.ci_type = &dtbocfg_root_type: The type of the configuration item is set todtbocfg_root_type, this is a custom configuration item type.
Next,.su_mutexThe field is a mutex lock used to protect the operations of the subsystem. Here, it is used.__MUTEX_INITIALIZERMacro to initialize the mutex lock.

Summary: The above code created a nameddevice-treeA subsystem whose root configuration item group is empty. More configuration items and configuration item groups can be added under this subsystem to dynamically configure and manage kernel objects related to the device tree.

12
$ ls /sys/kernel/config/devicetree usb_gadget

Driver code entry section:

12345678910111213141516171819202122232425262728293031
static int __init dtbocfg_module_init(void){    int retval = 0;    pr_info("%s\n", __func__);    // Initialize configfs group    config_group_init(&dtbocfg_root_subsys.su_group);    config_group_init_type_name(&dtbocfg_overlay_group, "overlays", &dtbocfg_overlays_type);    // Registration Subsystem    retval = configfs_register_subsystem(&dtbocfg_root_subsys);    if (retval != 0) {        pr_err("%s: couldn't register subsys\n", __func__);        goto register_subsystem_failed;    }    // Registration Group    retval = configfs_register_group(&dtbocfg_root_subsys.su_group, &dtbocfg_overlay_group);    if (retval != 0) {        pr_err("%s: couldn't register group\n", __func__);        goto register_group_failed;    }    pr_info("%s: OK\n", __func__);    return 0;register_group_failed:    configfs_unregister_subsystem(&dtbocfg_root_subsys);register_subsystem_failed:    return retval;}

This code is an initialization function.dtbocfg_module_init(), used to initialize and register the ConfigFS subsystem and configuration item groups.

  1. First, throughconfig_group_init()The function has been initialized.dtbocfg_root_subsys.su_group, that is, the root configuration item group of the subsystem.
  2. useconfig_group_init_type_name()The function has been initialized.dtbocfg_overlay_group, indicating the name isoverlaysof the configuration item group, and specified the type of the configuration item group asdtbocfg_overlays_type, this is a custom configuration item type.
  3. Callconfigfs_register_subsystem()Function registered.dtbocfg_root_subsysSubsystem. If registration fails, an error message will be printed, and it will jump toregister_subsystem_failedPerform error handling at the label.
  4. Callconfigfs_register_group()Function registered.dtbocfg_overlay_groupa configuration item group, and adds it todtbocfg_root_subsys.su_groupunder.

The purpose of this code is to initialize and register adevice-treeConfigFS subsystem, and create under it aoverlaysconfiguration item group.

That is, under the Linux system, indevice-treecreated under the subsystemoverlayscontainer

12
$ ls /sys/kernel/config/device-tree/overlays

References

kernel/Documentation/filesystems/configfsunder the directoryconfigfs.txt

kernel/samples/configfsunder the directoryconfigfs_sample.c

Loading comments…