Getting Started with Linux Drivers-Device Tree DTS

Device tree ( DTS : device tree source), literally means a tree of devices on a circuit board such as CPU, DDR, I2C, GPIO, SPI, etc. in the picture above, which are depicted according to the tree structure . According to the idea of ​​separation of strategy and function , the driver code (function) and the device tree DTS configuration file (policy) are designed separately. In this way, for different circuit boards, the Linux driver code does not need to be changed, and only the DTS needs to be modified. , the configuration in DTSwill determine which drivers to run.

    Linux related knowledge is very important in the embedded field. To learn, you can find an environment that can run Linux code . It is best to have a development board. You can also use qemu to run on ubuntu . You can refer to the previous article: Linux driver-IMX6ULL development Build the board qemu environment or build a reference yourself:

https://zhuanlan.zhihu.com/p/521196386

  1.  Device tree origin

    In Linux 2.6 , too many board hardware details of the ARM architecture are hard-coded in arch/arm/plat-xxx and arch/arm/mach-xxx. If the peripherals change accordingly, the driver code needs to be changed. .

    In 2011, after Linus Torvalds, the father of Linux, discovered this problem, he sent an email to the ARM-Linux development community through email, and couldn't help but say "This whole ARM thing is af*cking pain in the ass ". After that, the ARM community introduced the Flattened Device Tree mechanism already used by architectures such as PowerPC to separate the board-level information content from the Linux kernel and describe it in a dedicated file format, which is now .dts file .

    Starting from version 3.x , the use of device trees is supported. This is of great significance. It can isolate the driver code and the hardware information of the device and reduce the coupling in the code . Through the abstraction of hardware information by the device tree , the driver code only needs to be responsible for processing logic , and the specific information about the device is stored in the device tree file. In this way, if only the hardware interface information changes but not the driver logic, the developer only needs to modify Device tree file information, no need to rewrite driver code.

    The device tree consists of a series of named nodes and properties , and the nodes themselves can contain child nodes. In the device tree, the information that can be described includes:

  • Number and type of CPU.

  • Memory base address and size.

  • Buses and bridges.

  • Peripheral connections.

  • Interrupt controller and interrupt usage.

  • GPIO controller and GPIO usage.

  • Clock controller and clock usage.

    Basically, it is to draw a tree composed of CPU, bus, and devices on the circuit board. The bootloader will pass this tree to the kernel , and then the kernel can recognize this tree and expand the platform_device, i2c_client, and spi_device in the Linux kernel based on it. and other devices, and the memory, IRQ and other resources used by these devices are also passed to the kernel, and the kernel will bind these resources to the corresponding expanded devices.

2. Introduction to basic concepts

Image

2.1 dts

    The dts ( device tree source device tree source file) file is a device tree description file in ASCII text format. This file is suitable for human reading and is mainly for users.

    The corresponding information of the hardware will be written in files with the .dts suffix. Each piece of hardware can write a separate copy of xxxx.dts. Generally, there are a large number of dts files in the Linux source code. For the arm architecture, it can be found in arch/arm/boot/ dts finds the corresponding dts, and mips is in arch/mips/boot/dts, and powerpc is in arch/powerpc/boot/dts.

For imx6ull development board

arch/arm/boot/dts/100ask_imx6ull_qemu.dts

Dts usually contains a public part of the dtsi file, as follows:

#include "imx6ull.dtsi"

2.2 dtsi

    It is worth mentioning that some of the same dts configurations can be abstracted into dtsi files , and then included into the dts files in a way similar to C language. For the imx6ull development board arch/arm/boot/dts/imx6ull.dtsi

    For the settings of the same node, the configuration in dts will override the configuration in dtsi . The details are shown in the figure below;

Image

2.3 dtc

    dtc is a tool for compiling dts. You can install the dtc tool through the command apt-get install device-tree-compiler on the Ubuntu system. However, the dtc tool is already included in the kernel source code scripts/dtc path ;

2.4 dtb

    dtb (Device Tree Blob), dts will get the dtb file after being compiled by dtc, and the dtb is loaded into the kernel through the Bootloader boot program. Therefore, Bootloader needs to support device tree; Kernel also needs to add support for device tree;

The dtb file layout is as follows:

Image

As can be seen from the above figure, the DTB file mainly contains four parts:

  1. struct ftdheader : used to indicate the offset address of each branch, the size of the entire file, version number, etc.;

  2. memory reservation block : Use reserved memory information defined by /memreserve/ in the device tree;

  3. structure block : saves node information and node structure;

  4. strings block : save the name of the attribute, saved as a separate string;

    For code-level analysis of dtb files, please refer to:

https://cloud.tencent.com/developer/article/1887823

(1) The structure diagram of the dtb file is as follows:

Image

 (2) The structure diagram of the device node is as follows:

Image

2.5 DTB loading and parsing process

U-Boot handles it as follows:

Image

    The picture below is the basic structure of a device tree file; it looks a bit similar to an XML file . Let’s briefly summarize the following parts:

Image

one example:

1 dual-core ARM Cortex-A9 32-bit processor; the memory mapped area on the ARM local bus is distributed

Two serial ports (located at 0x101F1000 and 0x101F2000 respectively )

GPIO controller (located at 0x101F3000 )

SPI controller (located at 0x10170000 )

Interrupt controller (located at 0x10140000 )

The devices connected to the external bus bridge are as follows:

SMC SMC91111 Ethernet (located at 0x10100000 )

I2C controller (located at 0x10160000 )

64MB NOR Flash (located at 0x30000000 )

The Maxim DS1338 real-time clock (I2C address 0x58 ) is connected to the I2C bus corresponding to the I2C controller connected to the external bus bridge, as shown in the figure below;

Image

An example of porting a network card:

    For example, for the dm9000 network card, you need to first attach the sample information to our board-level device tree, configure the corresponding attributes according to the chip manual and circuit schematic diagram, and then configure the corresponding driver. It should be noted that the address line of dm9000 is generally connected to the chip select line, so it should belong to the corresponding chip select line node in the device tree. The exynos4412 I use here is connected to bank1, so it is "<0x50000000 0x2 0x50000004 0x2>"

The final configuration result is:

Image

Then make menuconfig checks the corresponding options to compile the dm9000 driver into the kernel.

[*] Networking support  --->        Networking options  --->                <*> Packet socket                <*>Unix domain sockets                 [*] TCP/IP networking                [*]   IP: kernel level autoconfigurationDevice Drivers  --->        [*] Network device support  --->                [*]   Ethernet driver support (NEW)  --->                        <*>   DM9000 supportFile systems  --->        [*] Network File Systems (NEW)  --->                <*>   NFS client support                [*]     NFS client support for NFS version 3                [*]       NFS client support for the NFSv3 ACL protocol extension                [*]   Root file system on NFS

Execute make uImage; make dtbs , tftp download, successfully load the nfs root file system and enter the system, indicating that the network card transplantation is successful.

Detailed syntax reference: https://www.cnblogs.com/xiaojiang1025/p/6131381.html

4. Modify DTS test

4.1 dts modification

    Modify device tree file

arch/arm/boot/dts/100ask_imx6ull_qemu.dts , add our own module dts_tree1:

Image

    After the modification is completed, execute make dtbs to recompile the device tree file. After the compilation is completed, arch/arm/boot/dts/100ask_imx6ull_qemu.dtb is downloaded to the chip.

Or when running with qemu, modify the reference to point to this new dtb file.

View the device tree node and enter the kernel, execute

ls  /proc/device-tree/

We will find that the device tree section just created already exists

Image

Image

    Just like what we modified in dts, it becomes a file format here. The name of the file is the name of the attribute, and the content is the value.

Specifically look at the contents of the node and execute

Image

Add driver module to 4.2 kernel

Reference: Linux driver practice : taking you step by step to compile the kernel driver - IOT Internet of Things Town - Blog Park

Create the dts_test folder under the /drivers folder, and then create the Kconfig file

Bash
 
config  DTS_TEST
         tristate "dts test"
         default y
         help
             This is the dts test

Create Makefile

JavaScript
 
obj-$(CONFIG_DTS_TEST) += dts_test.o

Add them to the Kconfig and Makefile files in the drivers folder.

C
 
source  "drivers/dts_test/Kconfig"
 obj-$(CONFIG_DTS_TEST)                += dts_test/

Create dts_test.c file

C++
 #include  <linux/init.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/of.h>
 #include <linux/of_gpio.h>
 
 #include <linux/miscdevice.h>
 #include <linux/fs.h>
 #include <linux/errno.h>
 #include <linux/gpio/consumer.h>
 #include <linux/interrupt.h>
 #include <linux/irq.h>
 #include <linux/gpio_keys.h>
 #include <linux/of_irq.h>
 #include <linux/gpio.h>
 #include <linux/property.h>
 #include <asm/io.h>
 #include <asm/uaccess.h>
 #include <linux/slab.h>
 #include <linux/device.h>
 
 #define DRIVER_NAME "imx6ul,dts-tree"
 
 
 static int devtree_probe(struct platform_device * pdev)
 {
     struct fwnode_handle *child;
     const char *p1,*p2[3];
     u32 p3[2],value;
     u8 testmac[6];
     int i=0;
 
     printk(KERN_INFO  "\n**********devtree_probe******************\n");
 
      device_property_read_string(&pdev->dev,"test-string",&p1);
     printk("devtree_probe  node  test-string is: %s\n",p1);
 
      device_property_read_string_array(&pdev->dev,  "test-strings", p2, 3);
     printk("devtree_probe node  test-strings is: %s%s%s\n",p2[0],p2[1],p2[2]);
 
      device_property_read_u32(&pdev->dev,"test-u32",&value);
     printk("devtree_probe node  test-u32 is: <%d>\n",value);
 
      device_property_read_u32_array(&pdev->dev,  "test-u32s", p3, 2);
     printk("devtree_probe node  test-u32s is: <%d>,<%d>\n",p3[0],p3[1]);
 
      device_property_read_string(&pdev->dev,"compatible",&p1);
     printk("devtree_probe node  compatible is: %s\n",p1);
 
      device_property_read_string(&pdev->dev,"status",&p1);
     printk("devtree_probe  node  status is: %s\n",p1);
 
     printk(" \n * devtree_probe  child node \n");
 
      device_for_each_child_node(&pdev->dev, child){
 
         printk("*************childnode%d*************\n",i++);     
          fwnode_property_read_string(child,"test-string",&p1);
         printk("childnode  test-string is: %s\n",p1);
 
          fwnode_property_read_string_array(child,"test-strings",p2,3);
         printk("childnode  test-strings is:  %s%s%s\n",p2[0],p2[1],p2[2]);
 
          fwnode_property_read_u32_array(child,"test-u32",&value,1);
         printk("childnode test-u32  is: <%d>\n",value);
 
          fwnode_property_read_u32_array(child,"test-u32s",p3,2);
         printk("childnode  test-u32s is:  <%d>,<%d>\n",p3[0],p3[1]);
 
          fwnode_property_read_u8_array(child,"test-u8s",testmac,6);
         printk("childnode  test-u32s is:  [%x,%x,%x,%x,%x,%x]\n",testmac[0],testmac[1],testmac[2],testmac[3],testmac[4],testmac[5]); 
 
     }
 
     return 0;
 }
 static int devtree_remove(struct platform_device * pdev)
 {
     printk(KERN_INFO  "devtree_remove\n");
 
     return 0;
 }
 
 static const struct of_device_id of_devtree_dt_match[] = {
     {.compatible = DRIVER_NAME},
     {},
 };
 
 MODULE_DEVICE_TABLE(of,of_devtree_dt_match);
 
 static struct platform_driver devtree_test_driver = {
     .probe  = devtree_probe,
     .remove = devtree_remove,
     .driver = {
         .name = DRIVER_NAME,
         .owner = THIS_MODULE,
         .of_match_table = of_devtree_dt_match,
         },
 };
 
 
 static int devtree_test_init(void)
 {
     int num=0,i=0,value;
     const char *p1;
     struct device_node  *node1,*childnode1;
     u32 p2[2];
     u8 testmac[6];
    
     pr_warn(KERN_INFO  "^^^^^^^^^^^^^^^^^^^devtree_test_init^^^^^^^^^^^ \n");
     printk(KERN_INFO  "^^^^^^^^^^^^^^^^^^^devtree_test_init^^^^^^^^^^^ \n");
     printk("\n*************devtree  init start ***************\n");
 
     node1 =  of_find_node_by_path("/dts-tree1");
     if(node1 == NULL){
         printk("of_find_node_by_path  failed\n");
         return -ENODEV;
     }
     else{
          printk("of_find_node_by_path dts-tree1 ok\n");
     }
     //read string
     of_property_read_string(node1,  "test-string", &p1);
     printk("dts-tree1 node  :test-string is: %s\n",p1);
     //read strings
     num =  of_property_count_strings(node1, "test-strings");
     printk("dts-tree1 node  test-strings num is: %d\n",num);
     for(i=0;i<num;i++){
          of_property_read_string_index(node1,"test-strings",i,&p1);
         printk("%s",p1);
     }
     //read string  "compatible"
     of_property_read_string(node1,  "compatible", &p1);
     printk("dts-tree1 node  compatible is: %s\n",p1);
 
     //read string "status"
     of_property_read_string(node1,  "status", &p1);
     printk("dts-tree1 node   status is: %s\n",p1);
 
     //read u32 "test-u32"
      of_property_read_u32(node1,"test-u32",&value);
     printk("dts-tree1 node  test-u32 is: <%d>\n",value);
 
     //read  u32s test-u32s
     of_property_read_u32_array(node1,  "test-u32s", p2, 2);
     printk("dts-tree1 node  test-u32s is:  <%d>,<%d>\n",p2[0],p2[1]);
 
     //read u8s test-u8s
     of_property_read_u8_array(node1,  "test-u8s", testmac, 6);
     printk("dts-tree1 node  test-u8s is:  <%x>,<%x>,<%x>,<%x>,<%x>,<%x>\n",testmac[0],testmac[1],testmac[2],testmac[3],testmac[4],testmac[5]);
 
     //get "dts_child_node1"  device node 
     childnode1 =  of_get_child_by_name(node1,"dts_child_node1");
     if(childnode1 == NULL){
          printk("of_get_child_by_name failed\n");
         return -ENODEV;
     }
     printk("of_get_child_by_name  dts_child_node1 ok\n");
     of_property_read_string(childnode1,  "test-string", &p1);
     printk("dts_child_node1 node  test-string is: %s\n",p1);
 
 
     return  platform_driver_register(&devtree_test_driver);
 }
 
 static void devtree_test_exit(void)
 {
     printk(KERN_INFO  "\ndevtree_test_exit\n");
      platform_driver_unregister(&devtree_test_driver);
 }
 
 module_init(devtree_test_init);
 module_exit(devtree_test_exit);
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("zheng");

Kconfig is y, so after compiling and running, you will see the print directly:

Image

4.3 Commonly used OF APIs

    Functions related to the device tree in the Linux kernel. The kernel drivers for the device tree are placed under /drivers/of . Users can use the functions here to operate the device tree.

Guess you like

Origin blog.csdn.net/usstmiracle/article/details/132827356