Three Linux character device driver writing methods-3: device tree

This article mainly takes novices to understand the difference between using the device tree and the previously mentioned bus device driver model and basic framework driver.

Three Linux character device driver writing methods-1: the simplest basic framework

Three Linux character device driver writing methods-2: bus device driver framework
 

1. What is a device tree

Still taking the LED driver as an example, what if you want to replace the GPIO pin used by the LED?

After we learned the first basic framework, we should know that it is to modify the corresponding pin value, and then recompile the entire .c file, but the disadvantage is that it is troublesome to modify;

After learning the second bus device driver model, we separate the device (resource) from the driver , compile and load dev.c and driver.c into the kernel separately, then modifying the pin only needs to modify the device (resource) part.

But here comes the problem, there are too many boards supported by the Linux kernel, and each design is different. Take LED as an example, use GPF4 on board A, and use GPF5 on board B...as in the kernel file In order to match the board, there will be a large number of .c files.

Many development board adaptation files are added under the linux kernel arm architecture, open the kernel source code, and put the files related to the arm development board in the directory arch/arm/mxch-xxx. These c files are only used to adapt a certain development board, and are not compatible with the Linux kernel No new features have been submitted , but a bunch of files are needed to adapt to a new development board, which makes the Linux kernel more and more bloated

Linus, the founder of Linux, was furious: "this whole ARM thing is af*cking pain in theass".

Thus, the Linux kernel began to introduce the device tree (device tree) .

We write a device tree file (dts: device tree source) , and then compile it into a dtb (device tree blob) file . The kernel parses the dtb. The essence of dts is a data structure used to describe board-level device information . The kernel uses a dtb file, which can replace a bunch of .c files.

The structure of the device tree is based on the bus device driver model mentioned in the second article, the difference is only in the representation of devices (resources).

As shown in the figure, the system bus is a tree trunk, each device controller is a node (or a device), and each device is a child node. Is it particularly like a tree?

In fact, the writing structure of dts is similar to this picture.

2. dts writing syntax

First put a simple dts, and then a little introduction:

/dts-v1/;

/ {
	model = "SMDK24440";
	compatible = "samsung,smdk2440";
	#address-cells = <0x1>;
	#size-cells = <0x1>;

	memory@30000000 {
		device_type = "memory";
		reg = <0x30000000 0x4000000>;
	};

	chosen {
		bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
	};

	led {
		compatible = "jz2440_led";
		pin = <0x50006>;
	};
};

2.1 Device tree version and reserved memory

Device Tree Version

/dts-v1/

reserved memory

/memreserve/<address><length>;

In the next line of the device tree version, you can add the reserved memory option to let the kernel reserve a section of memory for use. If you let the kernel use all the memory, it can be omitted. The example above does not have this option.

2.2 Device Tree Nodes

The device tree is composed of nodes, and each node is equivalent to a leaf on the tree.

2.2.1 Nodes

/ {
...
};

 The outermost one is called the root node.

memory@30000000 {
...
};

chosen {
...
};

led {
...
};

Inside are nodes, separated by {};, here are three nodes, and there can be nodes in the nodes.

Node syntax:

[label:] node-name[@unit-address] {    
    [properties definitions];    
    [child nodes];
};

1) label: node alias (label) , can be written or not, :separated by , in order to facilitate access to the node, you can directly &lableaccess the node by

2) node-name: node name

3) unit-address: device address , can be written or not. Because the names of nodes at the same level cannot be the same, they can be distinguished by device addresses. For example:

memory@30000000{
    device_type="memory";
    reg=<0x30000000 0x4000000>;
}
memory@0{
    device_type="memory";
    reg=<0 4096>;
}

4) properties definitions: property definitions

5) child nodes: child nodes

2.2.2 Node properties

[label:] property-name = value;
[label:] property-name;

Attributes are divided into attribute values ​​and non-attribute values

There are three types of attribute values :

1. arrays of cells (one or more 32-bit data, 64-bit data is represented by two 32-bit data), use angle brackets to represent < >, such as:

example=<0x11223344 123>;

2.string (string) , use double quotes to represent " ", such as:

example="hello";

3. bytestring (1 or more bytes), expressed in square brackets [ ], a byte must be represented by two hexadecimal numbers , such as 0 must be written as [00], such as [00 11 22], spaces can be Omit, such as [001122]

Three types can be combined, separated by ", such as:

example=<0x10101010 11>,"hello",[001122];

Generally don't do this.

2.2.2.1 compatible

The compatible attribute value is composed of string list (a list of strings), separated by "", to define the compatibility of the device. The recommended format is, manufacturer manufacturer,modeldescribes the manufacturer, and model describes the model.

compatible = "samsung,smdk2440","samsung,smdk2410";

This sentence means that it is compatible with both 2440 and 2410 boards. The compatible attribute is used to find the corresponding machine_decs under the root node and execute the corresponding initialization function, and it is used to find the corresponding driver program under the node. In the final analysis, it is the matching function.

The driver first uses the first compatible value to search in the Linux kernel to see if it can find the corresponding driver file; if not found, it uses the second compatible value to search.

Generally, the driver program file will have an OF matching table, which stores some compatible values. If the compatible attribute value of the device node is equal to any value in the OF matching table, it means that the device can use this driver.

2.2.2.2 model

The value of the model attribute is a string indicating the manufacturer and model of the device. The recommended format is manufacturer,model.

model = "samsung smdk2440";

 compatible is used to indicate what is compatible, and model indicates what it is.

2.2.2.4 reg

The original meaning of reg is register, which is used to indicate the register address. But in the device tree, it can be used to describe a space. Anyway, for the ARM system, registers and memory are uniformly addressed, that is, a certain address is used when accessing registers, and a certain address is used when accessing memory, and there is no difference in access methods.

The reg attribute value is used to describe the device address space resource information, generally the register address range information of a certain peripheral , including the start address and address length.

reg = <address1 length1 address2 length2 address3 length3……>

As in the example:

reg = <0x30000000 0x4000000>;

Represents 0x4000000 bytes of memory starting at address 0x30000000. Another example:

reg = <0x30000000 0x4000000 0 4096>;

Represents two segments of memory: 0x4000000 bytes of memory starting at address 0x30000000, and 4096 bytes of memory starting at address 0.

What about 64-bit addresses? Then two 32-bit numbers are needed to represent an address, how to distinguish? Use #address-cells and #size-cells below .

2.2.2.3 #address-cells 和 #size-cells

The #address-cells and #size-cells attribute value is a u32, which can be used in any device with child nodes, and describes how the child device nodes should be addressed .

#address-cellsThe attribute defines the word length occupied by the address field in the reg attribute of the child node , that is, the number of u32 cells occupied.

#size-cellsThe attribute defines the number of u32 cells occupied by the length of the reg attribute value of the child node .

#address-cells = <0x1>;
#size-cells = <0x1>;

Indicates that one 32-digit address is used to represent the address, and one 32-digit number is used to represent the length.

2.2.2.5 chosen

The chosen node is for uboot to transfer data to the Linux kernel , the focus is on the bootargs parameter, which involves kernel startup, which is not covered in this article.

2.2.3 Referencing other nodes

(1) phandle attribute reference

The phandle attribute in the node, its value must be unique (not the same as other phandle values)

pic@10000000 {
    phandle = <1>;
    interrupt-controller;
};

another-device-node {
    interrupt-parent = <1>;   // 使用phandle值为1来引用上述节点
};

pic represents the interrupt controller, and the interrupt parent of the interrupt-parent named node another-device-node is pic@100000000

(2) Use the alias label (essentially still phandle)

PIC: pic@10000000 {
    interrupt-controller;
};

another-device-node {
    interrupt-parent = <&PIC>;   // 使用label来引用上述节点, 
                                 // 使用lable时实际上也是使用phandle来引用, 
                                 // 在编译dts文件为dtb文件时, 编译器dtc会在dtb中插入phandle属性
};

 2.2.4 Include dtsi file

#include "2440.dtsi"

The public part can be written as a dtsi file, the syntax is exactly the same as dts.

There will be a "/" root node in each .dsti and .dts, so if a .dtsi file is included in a device tree file, then there are multiple "/" root nodes? When the compiler DTC compiles .dts to generate dtb, it will merge nodes, and the final generated dtb has only one root node.

Note that attributes in nodes with the same name in dts can override attributes in dtsi.

like:

dts:

/dts-v1/;
#include<2440.dtsi>
/{
    model = "SMDK2440";
    compatible = "samsung,smdk2440";
    #address-cells = <0x1>;
    #size-cells = <0x1>;
/{
    led{
        pin=<3>;    
    };
}

2440.dtsi:

/dts-v1/;
/{
    model = "SMDK2440";
    compatible = "samsung,smdk2440";
    #address-cells = <0x1>;
    #size-cells = <0x1>;
    led {
        pin=<6>;
    };
};

Finally, the pin attribute of led is 3, it is this attribute that can make the common part be written in dtsi, and write some differences of each board in dts.

2.2.5 View device tree

Check the device tree after the board starts, and execute the following command after the board starts:

# ls /sys/firmware/

Get: devicetree fdt

The /sys/firmware/devicetree directory is a dtb file presented in a directory structure. The root node corresponds to the base directory, each node corresponds to a directory, and each attribute corresponds to a file.

If the value of these attributes is a string, you can use the cat command to print it out; for the value, you can use hexdump to print it out. You can also see the /sys/firmware/fdt file, which is the device tree file in dtb format. You can copy it and put it on ubuntu, and execute the following command to decompile it (-I dtb: the input format is dtb, -O dts: the output format is dts): the kernel source code directory used by the cd board

./scripts/dtc/dtc -I dtb -O dts /从板子上/复制出来的/fdt -o tmp

3. Device tree driver

3.1 The processing of the device tree by the kernel

The kernel parses the dtb file and converts each node into a device_node structure ; for some device_node structures, it will be converted into a platform_device structure .

Which device tree nodes are converted to platform_device?

1) Child nodes with compatile attribute under the root node

2) Child nodes of nodes with specific compatile attributes

If a node has a compatile attribute, its value is one of these 4:

"simplebus", "simplemfd", "isa", "arm,amba-bus", then its child nodes (need to contain compatile attribute) can also be converted to platform_device.

3) Child nodes under bus I2C and SPI nodes: do not convert to platform_device

When a bus goes down to child nodes, it should be handed over to the corresponding bus driver for processing, and they should not be converted to platform_device.

For example in the following nodes:

{
    mytest {
        compatile = "mytest", "simple-bus";
        mytest@0 {
            compatile = "mytest_0";
        };
    };
    i2c {
        compatile = "samsung,i2c";
        at24c02 {
            compatile = "at24c02";
        };
    };
    spi {
        compatile = "samsung,spi";
        flash@0 {
            compatible = "winbond,w25q32dw";
            spi-max-frequency = <25000000>;
            reg = <0>;
        };
    };
};
  • /mytest will be converted to platform_device because it is compatible with "simple-bus"; its child node /mytest/mytest@0 will also be converted to platform_device
  •  The /i2c node generally represents the i2c controller, which will be converted into a platform_device, and has a corresponding platform_driver in the kernel;
  •  The /i2c/at24c02 node will not be converted to platform_device, how it is processed is completely determined by the parent node platform_driver, which is generally created as an i2c_client.
  •  There is also a /spi node similarly, which is generally used to represent the SPI controller, which will be converted to platform_device, and there is a corresponding platform_driver in the kernel;
  •  The /spi/flash@0 node will not be converted to a platform_device, how it is handled is completely determined by the platform_driver of the parent node, and is generally created as a spi_device.

platform_device contains a resource array, which comes from the reg and interrupts attributes of device_node;

platform_device.dev.of_node points to device_node, through which other attributes can be obtained

How to match platform_dev and platform_drv will not be mentioned here, just go to the source code to search for the platform_match function, it is very simple and you can definitely understand it.

The matching process, in order of priority, is as follows:

a. Compare platform_dev.driver_override and platform_driver.drv-> name
b. Compare the compatition attributes of platform_dev.dev.of_node and platform_driver.drv-> of_match_table
c. Compare potffffff. ORM_DEV.NAME and PATFORM_DRIVER.ID_TABLE
D. Compare platform_dev.name and platform_driver. drv->name

platform_get_resource function

After the nodes in the device tree are converted to platform_device, the reg attribute and interrupts attribute in the device tree will also be converted to "resource". At this time, you can use this function to retrieve these resources.
The function prototype is:

struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);

For the reg attribute in the device tree node, it corresponds to the IORESOURCE_MEM type resource; for the interrupts attribute in the device tree node, it corresponds to the IORESOURCE_IRQ type resource.

of_property_read_u32 function

Read the u32 number from the device_node node familiarity, np is the node pointer, and propname is the attribute name

static inline int of_property_read_u32(const struct device_node *np,
				       const char *propname,
				       u32 *out_value)
{
	return of_property_read_u32_array(np, propname, out_value, 1);
}

3.2 Modify the device tree

Directly decompile the original device tree file and execute it in the kernel directory:

./scripts/dtc/dtc -I dtb -O dts /从板子上/复制出来的/fdt -o tmp

Then add myled0 and myled1 nodes.

	myled0 {
		compatible = "my_led";
		pin = <5>;
	};
	myled1 {
		compatible = "my_led";
		pin = <6>;
	};

3.3 Modify the driver

The platform structure is defined as in the second article, the difference is that of_match_table is added, a matching list, which is used to match the compatible attribute of the node.

static struct platform_driver myled_drv = {
	.probe  = myled_probe,
	.remove = myled_remove,
	.driver = {
		.name = "myled",
		.of_match_table = of_match_leds,
	}
};
static const struct of_device_id of_match_leds[] = {
	{ .compatible = "my_led", .data = NULL },
};

In the probe function register_chrdev, construct the class, construct the device

static int myled_probe(struct platform_device * pdev)
{
	struct device_node *np;
    int err = 0;

    np = pdev->dev.of_node;
    if (!np)
        return -1;
	
	if(major == 0){	
		gpio_con = ioremap(0x56000050, 8);
		gpio_dat = gpio_con + 1;
		major=register_chrdev(0,"myled",&myled_opr);
		myled_class = class_create(THIS_MODULE, "myled_class");
		if(myled_class==NULL){
				printk("class_create error \n");
				return -1;
		}
	}
	
	err = of_property_read_u32(np, "pin", &led_pin[led_num]);
	device_create(myled_class,NULL, MKDEV(major, led_num), NULL,"myled%d",led_num);
    led_num++; 
	
    return 0;
	
}

of_property_read_u32(np, "pin", &led_pin[led_num]); This is the function to read the pin property, read it out and put it in the led_pin array.

Every time a node in the device tree matches the driver, the probe function will be called once, and a device will be created under /dev.

But register_chrdev and class_create only need one time, so use major to judge whether it is the first time to enter the probe.

Complete code: https://download.csdn.net/download/freestep96/86743909

Obviously, it is extremely convenient to use the device tree, and you can even use uboot to directly modify the device tree file.

Of course, the code here is very simple for novices to understand. After you learn it, you can go to see how other drivers in the kernel source code are written. It is a very good way to learn from mature code. Layers, separation, and object-oriented design ideas, but the easier it is to transplant, the more complicated it is, and it needs to be weighed in the work.

Guess you like

Origin blog.csdn.net/freestep96/article/details/127213211