[Linux driver] Device tree (Part 1)

00. Table of Contents

01. Linux device tree overview

The device tree was introduced in versions after Linux 3.x, and the device tree is used to describe the board-level details of a hardware platform. In earlier Linux kernels, these "board-level details of the hardware platform" were saved in the Linux kernel directory "/arch". Taking the ARM platform as an example, the "board-level details of the hardware platform" were saved in "/arch/arm/plat-xxx ” and “/arch/arm/mach-xxx” directories. As the number of processors increases, more and more files are used to describe "hardware platform board-level details", which makes the Linux kernel very bloated. After discovering this problem, the father of Linux decided to use the device tree to solve this problem. The device tree is simple, easy to use, and highly reusable. After Linux 3.x, most devices use the device tree to write drivers.

For details about device tree, please refer to: https://www.devicetree.org/

The function of the device tree is to describe the hardware resources of a hardware platform. This "device tree" can be passed to the kernel by the bootloader (uboot), and the kernel can obtain hardware information from the device tree.
Insert image description here

The device tree has two characteristics when describing hardware resources.

  • First, hardware resources are described in a "tree" structure. For example, the local bus is the "backbone" of the tree and is called the "root node" in the device tree. The IIC bus, SPI bus and UART bus mounted to the local bus are the "branches" of the tree and is called the "root node" in the device tree. There are more than one IIC devices under the IIC bus, and these "branches" can be further divided.
  • Second, the device tree can be like a header file (.h file). One device tree file refers to another device tree file, so that "code" can be reused. For example, multiple hardware platforms use i.MX6ULL as the main control chip, then we can write the hardware resources of the i.MX6ULL chip into a separate device tree file, generally using the ".dtsi" suffix, and other device tree files directly use " #includexxx" can be quoted.

DTS, DTC and DTB are a few common abbreviations in documents.

  • DTS refers to a file in .dts format, which is a device tree description in ASII text format. It is also the device tree source code we want to write. Generally, one .dts file corresponds to one The hardware platform is located in the "/arch/arm/boot/dts" directory of the Linux source code.
  • DTC refers to the tool for compiling device tree source code. Generally, we need to install this compilation tool manually.
  • DTB is a file generated by compiling the device tree source code, similar to the ".bin" file generated by compiling the ".C" file in our C language.

02. Device tree framework

We have briefly understood the function of the device tree before. Until now, we still don't know what the "device tree" looks like.

Reference path:/home/deng/a72/x3399/kernel/arch/arm64/boot/dts/rockchip

Insert image description here

The following content will focus on the device tree source code to explain the device tree framework and basic syntax.

#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"

/ {
    
    
    model = "Embedfire i.MX6ULL Board";
    compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

    aliases {
    
    
        pwm0 = &pwm1;
        pwm1 = &pwm2;
        pwm2 = &pwm3;
        pwm3 = &pwm4;
    };
    chosen {
    
    
        stdout-path = &uart1;
    };

    memory {
    
    
        reg = <0x80000000 0x20000000>;
    };

    reserved-memory {
    
    
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        linux,cma {
    
    
            compatible = "shared-dma-pool";
            reusable;
            size = <0x14000000>;
            linux,cma-default;
        };
    };
    /*-------------以下内容省略-------------*/
};

&cpu0 {
    
    
    /*dc-supply = <&reg_gpio_dvfs>;*/
        clock-frequency = <800000000>;
};


&clks {
    
    
        assigned-clocks = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
        assigned-clock-rates = <786432000>;
};

&fec1 {
    
    
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_enet1>;
        phy-mode = "rmii";
        phy-handle = <&ethphy0>;
        status = "okay";
};
/*-------------以下内容省略--------------*/

/imx6ull.dtsi

cpus {
    
    
    #address-cells = <1>;
    #size-cells = <0>;

    cpu0: cpu@0 {
    
    
        compatible = "arm,cortex-a7";
        device_type = "cpu";
    /*-------------以下内容省略--------------*/
    };
};

The device tree source code is divided into three parts, which are introduced as follows:

  • Lines 1-2: Header file. The device tree can use "#include" to reference the header file with the ".h" suffix like the C language, or you can reference the header file with the ".dtsi" suffix of the device tree. imx6ull.dtsi is officially provided by NXP and is a device tree file "shared" by the imx6ull platform.
  • Lines 4-35: Device tree node. The most intuitive feeling that the device tree gives us is that it consists of some nested braces "{}", and each "{}" is a "node". "/{...};" means "root node", and each device tree has only one root node. If you open the "imx6ull.dtsi" file you can find that it also has a root node. Although "imx6ull-mmc-npi.dts" refers to the "imx6ull.dtsi" file, this does not represent the "imx6ull-mmc-npi.dts" device. The tree has two root nodes because the root nodes of different files will eventually be merged into one. Characters such as "aliases {...}", "chosen {...}", and "memory {...}" inside the root node are all child nodes of the root node.
  • Lines 37-53: Add content to the device tree node. The child nodes in the third part have one more "&" than the child nodes under the root node, which means that the node is appending data to the existing child nodes. These "existing nodes" may be defined in the "imx6ull-mmc-npi.dts" file, or they may be defined in the device tree file included in the "imx6ull-mmc-npi.dts" file. The additional target nodes of "&cpu0 {…}", "&clks {…}", "&fec1 {…}" and so on in this code are defined in "imx6ull.dtsi".

So far we know that the device tree consists of a root node and many child nodes. Child nodes can also continue to contain other nodes, that is, child nodes of child nodes. The composition of the device tree is very simple. Let's take a look at the basic format and node attributes of the nodes.

03. Basic format of device tree node

Each node in the device tree is named according to the following convention:

3.1 Basic format of nodes

node-name@unit-address{
    
    
    属性1 =
    属性2 =
    属性3 =
    子节点…
}

3.2 node-name node name

node-name in node format is used to specify the name of the node. It has a length of 1 to 31 characters and can only consist of the following characters

character describe
0-9 number
a-z Lower case letters
A-Z uppercase letter
, English comma
. English period
_ Underline
+ plus
- minus sign

Additionally, node names should begin with an uppercase or lowercase letter and describe the device class.

Note, the root node does not have a node name, it directly uses "/" to refer to it as a root node.

@unit-address

@unit-address, the symbol "@" can be understood as a separator, "unit-address" is used to specify the "unit address", and its value must be consistent with the first address of the node "reg" attribute. If the node does not have a "reg" attribute value, you can directly omit "@unit-address", but please note that the node names under the same level of device tree (same level of child nodes) must be unique. You can also understand from this side, The node names of child nodes at the same level can be the same, but the "unit addresses" are required to be different. The entire node-name@unit-address must be unique at the same level.

Insert image description here

3.3 Node labels

In the imx6ull.dtsi header file, there is an additional "cpu0" in front of the node name "cpu". This "cpu0" is what we call the node label. Usuallythe node label is the abbreviation of the node name, so its function is to use the node label to append content to the node when other locations need to reference it.

3.4 Node path

A node in the device tree can be uniquely identified by specifying the complete path from the root node to the desired node,不同层次的设备树节点名字可以相同,同层次的设备树节点要唯一。 This is somewhat similar to the files on our Windows, one path is unique Identifies a file or folder. File names in different directories can be the same.

3.5 Node attributes

The content contained in the "{}" of the node is the node attribute. Normally, a node contains multiple attribute information. These attribute information is the "board-level hardware description information" to be passed to the kernel. The driver will pass some API functions. Get this information.

For example, the root node "/" has attributes compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull". We can use this attribute to learn that the name related to the hardware device is "imx6ull-14x14-evk", and the device uses the "imx6ull" SOC.

The most important thing when we write the device tree is to write the node attributes of the node. Usually, a node represents a device. What attributes does the device have, how to write these attributes, how to reference these attributes in the driver, etc. What device nodes can be set? Attributes. There are some node attributes that are common to all nodes, and some that act on specific nodes. We introduce those common node attributes here, and we will introduce other node attributes in detail when they are used.

Node attributes are divided into standard attributes and custom attributes, which means that we can define and add device attributes in the device tree according to our actual needs. The attribute names of standard attributes are fixed, and custom attribute names can be defined as required.

compatible attribute

Property value type: string

compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";

intc: interrupt-controller@a01000 {
    
    
    compatible = "arm,cortex-a7-gic";
    #interrupt-cells = <3>;
    interrupt-controller;
    reg = <0xa01000 0x1000>,<0xa02000 0x100>;
};

The compatible attribute value consists of one or more strings. If there are multiple strings, use "," to separate them.

Each node in the device tree that represents a device must have a compatible attribute. Compatible is the key used by the system to determine the device driver bound to the device. The compatible attribute is one of the methods used to find nodes. In addition, you can also find the specified node by node name or node path.

For example, when the system initializes the devices on the platform bus, it will load the corresponding driver based on the "compatible" attribute of the device node and the value corresponding to of_match_table in the driver.

model attribute

Property value type: string

model = "Embedfire i.MX6ULL Board";

The model attribute is used to specify the manufacturer and model of the device. It is recommended to use the "manufacturer, model" format. Of course, it can also be customized. This format is not used in this example.

status attribute

Property value type: string

/* External sound card */
sound: sound {
    
    
    status = "disabled";
};

status attribute

Property value type: string

/* External sound card */
sound: sound {
    
    
    status = "disabled";
};

The status attribute is used to indicate the "operational status" of the device. You can disable or enable the device through status. By default, the device is enabled without setting the status attribute.

#address-cells 和 #size-cells

Attribute value type: u32

soc {
    
    
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "simple-bus";
    interrupt-parent = <&gpc>;
    ranges;
    ocrams: sram@900000 {
    
    
            compatible = "fsl,lpm-sram";
            reg = <0x900000 0x4000>;
    };
};

The #address-cells and #size-cells attributes exist at the same time. In the device tree ocrams structure, they are used in device nodes (nodes) with child nodes to set the "writing format" of the "reg" attribute of the child node.

Supplement: The reg attribute value consists of a string of numbers, such as reg = <0x900000 0x4000> in the above figure. The writing format of the ret attribute is reg = < cells cells cells cells cells cells…>. The length depends on the actual situation. These data are divided into address data (address field) and length data (size field).

#address-cells, used to specify the length of the "address field" of the sub-node reg attribute (the number of cells). #size-cells, used to specify the length of the "size field" of the child node reg attribute (the number of cells).

For example, #address-cells=2, #address-cells=1, then the meaning of the data in reg is reg =

, because each cell is a 32-bit wide number, for example, when a 64-bit wide address needs to be represented, two address units must be used to represent it. And if #address-cells=1, #address-cells=1, the meaning of the data in reg is reg = < address size address size address size>.

In short, #size-cells and #address-cells determine which data in the reg attribute of the child node is the "address" and which data is the "length" information.

reg attribute

Attribute value type: address, length data pair

The reg attribute describes the address of the device resource within the address space defined by its parent bus. It is usually used to represent the starting address (offset address) and length of a register. It also has different meanings in specific circumstances. For example, in the above example, #address-cells = <1>, #address-cells = <1>, reg = <0x9000000 x4000>, where 0x9000000 represents the address and 0x4000 represents the address length. The reg attribute here An address space with a starting address of 0x9000000 and a length of 0x4000 is specified.

ranges

Attribute value type: any number of <child address, parent address, address length> encodings

soc {
    
    
            #address-cells = <1>;
            #size-cells = <1>;
            compatible = "simple-bus";
            interrupt-parent = <&gpc>;
            ranges;

            busfreq {
    
    
        /*-------------以下内容省略--------------*/
            };
}

This attribute provides a mapping (conversion) method between the child node address space and the parent address space. The common format is ranges = <child address, parent address, conversion length>. If the parent address space and child address space are the same, no conversion is required. As shown in the example, only renges is written and the content is empty. We can also directly omit the renges attribute.

For example, if #address-cells and #size-cells are both 1, taking ranges=<0x0 0x10 0x20> as an example, it means that the address space from 0x0~(0x0 + 0x20) of the sub-address is mapped to 0x10 of the parent address. ~(0x10 + 0x20).

name and device_type

Property value type: string.

example{
    
    
    name = "name"
}


cpus {
    
    
    #address-cells = <1>;
    #size-cells = <0>;

    cpu0: cpu@0 {
    
    
        compatible = "arm,cortex-a7";
        device_type = "cpu";
        reg = <0>;
    }
}

These two properties are rarely used (have been deprecated) and are not recommended. name is used to specify the node name. In the old device tree it was used to determine the node name. The device tree we are using now has been deprecated. The device_type attribute is also a rarely used attribute and is only used on CPU and memory nodes. As shown in the above example, device_type is used on the CPU node.

3.6 Add/modify node content

&cpu0 {
    
    
    dc-supply = <&reg_gpio_dvfs>;
    clock-frequency = <800000000>;
};

These source codes are not included in the root node "/{...}". They are not a new node, but append content to the original node. Taking the source code above as an example, "&cpu0" means appending data to the node whose "node label" is "cpu0". This node may be defined in this file or in the device tree file included in this file. In this example The "cpu0" of the source code is defined in the "imx6ull.dtsi" file.

3.7 Special nodes

aliases child node

The function of the aliases sub-node is to give an alias to other nodes, as shown below.

aliases {
    
    
    can0 = &flexcan1;
    can1 = &flexcan2;
    ethernet0 = &fec1;
    ethernet1 = &fec2;
    gpio0 = &gpio1;
    gpio1 = &gpio2;
    gpio2 = &gpio3;
    gpio3 = &gpio4;
    gpio4 = &gpio5;
    i2c0 = &i2c1;
    i2c1 = &i2c2;
    /*----------- 以下省略------------*/
}

Take "can0 = &flexcan1;" as an example. "flexcan1" is the name of a node. After setting the alias, we can use "can0" to refer to the flexcan1 node, similar to the node label. In the device tree, it is more about adding labels to nodes without using node aliases. The function of aliases is to "quickly find device tree nodes". If we want to find a node in the driver, usually we can use "node path" to find the node step by step. You can also use aliases to find nodes "in one go".

chosen child node

The chosen child node is located under the root node as shown below

chosen {
    
    
    stdout-path = &uart1;
};

The chosen subnode does not represent the actual hardware, it is mainly used to pass parameters to the kernel. Only one attribute "stdout-path =&uart1;" is set here, which means that the system standard output stdout uses the serial port uart1. In addition, this node is also used as a "channel" for uboot to transfer configuration parameters to the Linux kernel. The parameters we set in Uboot are passed to the kernel through this node. This part of the content is automatically completed by uboot and the kernel. As beginners, we do not have to get to the bottom.

The interrupt and clock parts also have their own node standard attributes. With in-depth study, we will introduce these node standard attributes in detail.

04. Get device tree node information

The "node" in the device tree corresponds to the device in the actual hardware. We added a "led" node in the device tree. Under normal circumstances, we can obtain all the information used in writing the LED driver from this node, such as LED-related control registers. address, led clock control register address, etc.

In this section we start to learn how to get the data we want from the device node of the device tree. The kernel provides a set of functions for obtaining resources (attributes defined in the device node) from device nodes. These functions start with of_ and are called OF operation functions. The commonly used OF functions are introduced as follows:

4.1 Find node function

Find node functions based on node paths

of_find_node_by_path function (kernel source code/include/linux/of.h)

struct device_node *of_find_node_by_path(const char *path)

parameter:

  • path: Specifies the path of the node in the device tree.

return value:

  • device_node: Structure pointer. If the search fails, NULL is returned. Otherwise, a structure pointer of device_node type is returned, which holds the information of the device node.

The device_node structure is shown below.

struct device_node {
    
    
    const char *name;
    const char *type;
    phandle phandle;
    const char *full_name;
    struct fwnode_handle fwnode;

    struct  property *properties;
    struct  property *deadprops;    /* removed properties */
    struct  device_node *parent;
    struct  device_node *child;
    struct  device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
    struct  kobject kobj;
#endif
    unsigned long _flags;
    void    *data;
#if defined(CONFIG_SPARC)
    const char *path_component_name;
    unsigned int unique_id;
    struct of_irq_controller *irq_trans;
#endif
};
  • name: The value of the attribute name in the node
  • type: The value of the attribute device_type in the node
  • full_name: The name of the node, put a string after the device_node structure, full_name points to it
  • properties: linked list, connecting all properties of the node
  • parent: points to the parent node
  • child: points to the child node
  • sibling: points to sibling node

After getting the device_node structure, we can use other of functions to obtain the detailed information of the node.

Find node functions based on node names

of_find_node_by_name function (kernel source code/include/linux/of.h)

struct device_node *of_find_node_by_name(struct device_node *from,const char *name);

parameter:

  • from: specifies which node to start searching from. It is not in the search queue itself, only the nodes behind it are searched. If set to NULL, it means starting from the root node.
  • name: The name of the node to be found.

return value:

  • device_node: Structure pointer. If the search fails, NULL is returned. Otherwise, a structure pointer of device_node type is returned, which holds the information of the device node.
Find node functions based on node type

of_find_node_by_type function (kernel source code/include/linux/of.h)

struct device_node *of_find_node_by_type(struct device_node *from,const char *type)

parameter:

  • from: specifies which node to start searching from. It is not in the search queue itself, only the nodes behind it are searched. If set to NULL, it means starting from the root node.
  • type: To find the type of node, this type is device_node-> type.

return value:

  • device_node: A structure pointer of device_node type, which saves the obtained node. Likewise, NULL is returned on failure.
Find node functions based on node type and compatible attributes

of_find_compatible_node function (kernel source code/include/linux/of.h)

struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)

Compared with the of_find_node_by_name function, a compatible attribute is added as a filtering condition.

parameter:

  • from: specifies which node to start searching from. It is not in the search queue itself, only the nodes behind it are searched. If set to NULL, it means starting from the root node.
  • type: To find the type of node, this type is device_node-> type.
  • compatible: The compatible attribute of the node to be found.

return value:

  • device_node: A structure pointer of device_node type, which saves the obtained node. Likewise, NULL is returned on failure.
Find node function based on matching table

of_find_matching_node_and_match function (kernel source code/include/linux/of.h)

static inline struct device_node *of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match)

It can be seen that this structure contains more matching parameters, which means that compared with the first three node-finding functions, this function matches more parameters and filters nodes more carefully. Parameter match, search results.

parameter:

  • from: specifies which node to start searching from. It is not in the search queue itself, only the nodes behind it are searched. If set to NULL, it means starting from the root node.
  • matches: Source matching table, find the device node that matches the matching table.
  • of_device_id: The structure is as follows.

return value:

  • device_node: A structure pointer of device_node type, which saves the obtained node. Likewise, NULL is returned on failure.

of_device_id structure

/*
  * Struct used for matching a device
  */

struct of_device_id {
    
    
    char    name[32];
    char    type[32];
    char    compatible[128];
    const void *data;
};
  • name: The value of the attribute name in the node
  • type: The value of the attribute device_type in the node
  • compatible: The name of the node, put a string after the device_node structure, full_name points to it
  • data: linked list, connecting all attributes of the node
Find parent node function

of_get_parent function (kernel source code/include/linux/of.h)

struct device_node *of_get_parent(const struct device_node *node)

parameter:

  • node: Specifies who (node) is to find the parent node.

return value:

  • device_node: A structure pointer of device_node type, which saves the obtained node. Likewise, NULL is returned on failure.
Find child node function

of_get_next_child function (kernel source code/include/linux/of.h)

struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)

parameter:

  • node: Specifies who (node) is to find its child nodes.
  • prev: The previous child node is looking for the node after the prev node. This is an iterative search process. For example, if you are looking for the second child node, you need to fill in the first child node here. The parameter is NULL to find the first child node.

return value:

  • device_node: A structure pointer of device_node type, which saves the obtained node. Likewise, NULL is returned on failure.

These seven functions have one thing in common - the return value type is the same. As long as the node is found, the device_node structure corresponding to the node will be returned. In the driver, we use this device_node to obtain the attribute information of the device node, follow the clues to find its parent, child nodes, etc. The first function of_find_node_by_path is different from the following six. It searches for nodes through the node path. The "node path" is obtained from the device tree source file (.dts). The middle four functions are to search for a device node that meets the requirements after a certain node based on the node attributes. This "certain node" is the device node structure (device_node), which means that this node has been found. The last two functions are similar to the middle four, except that the last two do not use node attributes but search based on parent and child relationships.

05. Of function to extract attribute values

We have explained 7 functions for finding nodes. They have a common feature. When a device node is found, the structure pointer corresponding to the device node (device_node*) will be returned. This process can be understood as "getting" the device node in the device tree into the driver. After the "get" is successful, we then use a set of functions to obtain the device node attribute information we want from the device node structure (device_node).

5.1 Find node attribute function

of_find_property function (kernel source code/include/linux/of.h)

struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)

parameter:

  • np: Specifies the attribute information of the device node to be obtained.
  • name: Attribute name.
  • lenp: Get the size of the obtained attribute value. This pointer is used as the output parameter. The value "brought back" by this parameter is the actual obtained attribute size.

return value:

  • property: Get the obtained property. The property structure, we call it the node property structure, is shown below. Returns NULL on failure. From this structure we can get the desired attribute values.

property attribute structure

struct property {
    
    
    char    *name;
    int     length;
    void    *value;
    struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
    unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
    unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
    struct bin_attribute attr;
#endif
};
  • name:attribute name
  • length: attribute length
  • value: attribute
  • next: next attribute
5.2 Read integer attribute function

The read attribute function is a set of functions for reading 8, 16, 32, and 64-bit data.

of_property_read_uX_array function group (kernel source code/include/linux/of.h)

//8位整数读取函数
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)

//16位整数读取函数
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz)

//32位整数读取函数
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz)

//64位整数读取函数
int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz)

parameter:

  • np: Specifies which device node structure to read, that is to say, reads the data of that device node.
  • propname: Specifies which property of the device node is to be obtained.
  • out_values: This is an output parameter, the "return value" of the function, and saves the read data.
  • sz: This is an input parameter that sets the length of the read.

return value:

  • Return value, success returns 0, error returns error status code (non-zero value), -EINVAL (attribute does not exist), -ENODATA (no data to be read), -EOVERFLOW (attribute value list is too small).
5.3 Simplified reading integer attribute function

The function here is a simple encapsulation of the function that reads integer attributes, and sets the read length to 1. The usage is exactly the same as reading the attribute function, so I won’t go into details here.

of_property_read_uX function group (kernel source code/include/linux/of.h)

//8位整数读取函数
int of_property_read_u8 (const struct device_node \*np, const char \*propname,u8 \*out_values)

//16位整数读取函数
int of_property_read_u16 (const struct device_node \*np, const char \*propname,u16 \*out_values)

//32位整数读取函数
int of_property_read_u32 (const struct device_node \*np, const char \*propname,u32 \*out_values)

//64位整数读取函数
int of_property_read_u64 (const struct device_node \*np, const char \*propname,u64 \*out_values)

5.4 Read string attribute function

There are many string attributes in device nodes, such as compatible, status, type, etc. These attributes can be obtained using the find node attribute function of_find_property, but this is more cumbersome. The kernel provides a set of functions for reading string attributes, which are introduced as follows:

of_property_read_string function (kernel source code/include/linux/of.h)

int of_property_read_string(const struct device_node *np,const char *propname,const char **out_string)

parameter:

  • np: Specifies the attribute information of the device node to be obtained.
  • propname: Attribute name.
  • out_string: Gets the string pointer, which is an "out" parameter and brings back a string pointer. That is, the first address of the string attribute value. This address is the real location of the "attribute value" in memory, which means we can obtain the entire string attribute by operating on the address (a string attribute may contain multiple strings, and these strings are stored continuously in memory, using '0' separated).

return value:

  • Return value: 0 is returned on success, and error status code is returned on failure.

This function is relatively cumbersome to use. It is recommended to use the following function.

of_property_read_string_index function (kernel source code/include/linux/of.h)

int of_property_read_string_index(const struct device_node *np,const char *propname, int index,const char **out_string)

Compared with the previous function, the parameter index is added, which is used to specify which string in the attribute value to read. The index starts counting from zero. The first function can only get the address of the attribute value, which is the address of the first string. Other strings require us to manually modify the mobile address, which is very troublesome. It is recommended to use the second function.

5.5 Read Boolean attribute function

Some attributes in the device node are of BOOL type. Of course, the kernel will provide functions for reading BOOL type attributes. The introduction is as follows

of_property_read_string_index function (kernel source code/include/linux/of.h)

static inline bool of_property_read_bool(const struct device_node \*np, const char \*propname)

parameter:

  • np: Specifies the attribute information of the device node to be obtained.
  • propname: Attribute name.

return value:

This function does not follow the routine. It does not read the value of a certain Boolean attribute, but only reads whether the attribute exists or does not exist. If you want or get a value, you can use the "all-purpose" function explained before to find the node property function of_find_property.

5.6 Memory mapping related of function

Most of the device nodes in the device tree will contain some memory-related attributes, such as the commonly used reg attributes. Normally, after getting the register address, we have to convert the physical address into a virtual address through the ioremap function. Now the kernel provides the of function to automatically complete the conversion of physical addresses to virtual addresses. The introduction is as follows:

of_iomap function (kernel source code/drivers/of/address.c)

void __iomem *of_iomap(struct device_node *np, int index)

parameter:

  • np: Specifies the attribute information of the device node to be obtained.
  • index: Usually the reg attribute contains multiple segments, index is used to specify which segment to map, and the label starts from 0.

return value:

  • Successfully, the converted address is obtained. Returns NULL on failure.

The kernel also provides regular of functions for obtaining addresses. The values ​​obtained by these functions are the address values ​​we set in the device tree. The introduction is as follows:

of_address_to_resource function (kernel source code/drivers/of/address.c)

int of_address_to_resource(struct device_node \*dev, int index, struct resource \*r)

parameter:

  • np: Specifies the attribute information of the device node to be obtained.
  • index: Usually the reg attribute contains multiple segments, index is used to specify which segment to map, and the label starts from 0.
  • r: This is a resource structure, which is the "output parameter" used to return the obtained address information.

return value:

  • Returns 0 on success, and returns an error status code on failure.

The resource structure is as follows:

resource attribute structure

struct resource {
    
    
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    unsigned long desc;
    struct resource *parent, *sibling, *child;
};
  • start: starting address
  • end: Landmarks
  • name:Attribute name

This structure is relatively simple, and it is easy to obtain specific information from it. I won’t go into details here.

Three types of commonly used of functions are introduced here. These basically meet our needs. If other of functions are used later, we will introduce them in detail.

06. Appendix

Guess you like

Origin blog.csdn.net/dengjin20104042056/article/details/134435020
Recommended