Exynos4412 内核移植(六)—— 设备树解析

一、描述

        ARM Device Tree起源于OpenFirmware (OF),在过去的Linux中,arch/arm/plat-xxx和arch/arm/mach-xxx中充斥着大量的垃圾代码,相当多数的代码只是在描述板级细节,而这些板级细节对于内核来讲,不过是垃圾,如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data。为了改变这种局面,Linux社区的大牛们参考了PowerPC等体系架构中使用的Flattened Device Tree(FDT),也采用了Device Tree结构,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。  

       Device Tree是一种描述硬件的数据结构,它起源于 OpenFirmware (OF)。在Linux 2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx,采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。  

      Device Tree由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):

  • CPU的数量和类别
  • 内存基地址和大小
  • 总线和桥
  • 外设连接
  • 中断控制器和中断使用情况
  • GPIO控制器和GPIO使用情况
  • Clock控制器和Clock使用情况

     它基本上就是画一棵电路板上CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,然后内核可以识别这棵树,并根据它展开出Linux内核中的platform_device、i2c_client、spi_device等设备,而这些设备用到的内存、IRQ等资源,也被传递给了内核,内核会将这些资源绑定给展开的相应的设备。

     通常由.dts文件以文本方式对系统设备树进行描述,经过Device Tree Compiler(dtc)将dts文件转换成二进制文件binary device tree blob(dtb),.dtb文件可由Linux内核解析,有了device tree就可以在不改动Linux内核的情况下,对不同的平台实现无差异的支持,只需更换相应的dts文件,即可满足。


二、相关结构体

1、U-Boot需要将设备树在内存中的存储地址传给内核。该树主要由三大部分组成:头(Header)、结构块(Structure block)、字符串块(Strings block)。

设备树在内存中的存储布局图:



1.1 头(header)
      头主要描述设备树的一些基本信息,例如设备树大小,结构块偏移地址,字符串块偏移地址等。偏移地址是相对于设备树头的起始地址计算的。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct boot_param_header {  
  2.     __be32 magic;                //设备树魔数,固定为0xd00dfeed  
  3.     __be32 totalsize;            //整个设备树的大小  
  4.     __be32 off_dt_struct;        //保存结构块在整个设备树中的偏移  
  5.     __be32 off_dt_strings;        //保存的字符串块在设备树中的偏移  
  6.     __be32 off_mem_rsvmap;        //保留内存区,该区保留了不能被内核动态分配的内存空间  
  7.     __be32 version;            //设备树版本  
  8.     __be32 last_comp_version;    //向下兼容版本号  
  9.     __be32 boot_cpuid_phys;    //为在多核处理器中用于启动的主cpu的物理id  
  10.     __be32 dt_strings_size;    //字符串块大小  
  11.     __be32 dt_struct_size;     //结构块大小  
  12. };  

1.2 结构块(struct block)

       设备树结构块是一个线性化的结构体,是设备树的主体,以节点node的形式保存了目标单板上的设备信息。

       在结构块中以宏OF_DT_BEGIN_NODE标志一个节点的开始,以宏OF_DT_END_NODE标识一个节点的结束,整个结构块以宏OF_DT_END结束。一个节点主要由以下几部分组成。

(1)节点开始标志:一般为OF_DT_BEGIN_NODE。
(2)节点路径或者节点的单元名(ersion<3以节点路径表示,version>=0x10以节点单元名表示)
(3)填充字段(对齐到四字节)
(4)节点属性。每个属性以宏OF_DT_PROP开始,后面依次为属性值的字节长度(4字节)、属性名称在字符串块中的偏移量(4字节)、属性值和填充(对齐到四字节)。
(5)如果存在子节点,则定义子节点。
(6)节点结束标志OF_DT_END_NODE。


1.3 字符串块

       通过节点的定义知道节点都有若干属性,而不同的节点的属性又有大量相同的属性名称,因此将这些属性名称提取出一张表,当节点需要应用某个属性名称时直接在属性名字段保存该属性名称在字符串块中的偏移量。

1.4 设备树源码 DTS 表示

      设备树源码文件(.dts)以可读可编辑的文本形式描述系统硬件配置设备树,支持 C/C++方式的注释,该结构有一个唯一的根节点“/”,每个节点都有自己的名字并可以包含多个子节点。设备树的数据格式遵循了 Open Firmware IEEE standard 1275。这个设备树中有很多节点,每个节点都指定了节点单元名称。每一个属性后面都给出相应的值。以双引号引出的内容为 ASCII 字符串,以尖括号给出的是 32 位的16进制值。这个树结构是启动 Linux 内核所需节点和属性简化后的集合,包括了根节点的基本模式信息、CPU 和物理内存布局,它还包括通过/chosen 节点传递给内核的命令行参数信息。

1.5 machine_desc结构

      内核提供了一个重要的结构体struct machine_desc ,这个结构体在内核移植中起到相当重要的作用,内核通过machine_desc结构体来控制系统体系架构相关部分的初始化。machine_desc结构体通过MACHINE_START宏来初始化,在代码中, 通过在start_kernel->setup_arch中调用setup_machine_fdt来获取。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct machine_desc {  
  2.     unsigned int nr; /* architecture number */  
  3.     const char *name; /* architecture name */  
  4.     unsigned long atag_offset; /* tagged list (relative) */  
  5.     const char *const *dt_compat; /* array of device tree* 'compatible' strings */  
  6.     unsigned int nr_irqs; /* number of IRQs */  
  7.       
  8. #ifdef CONFIG_ZONE_DMA  
  9.     phys_addr_t dma_zone_size; /* size of DMA-able area */  
  10. #endif  
  11.       
  12.     unsigned int video_start; /* start of video RAM */  
  13.     unsigned int video_end; /* end of video RAM */  
  14.       
  15.     unsigned char reserve_lp0 :1; /* never has lp0 */  
  16.     unsigned char reserve_lp1 :1; /* never has lp1 */  
  17.     unsigned char reserve_lp2 :1; /* never has lp2 */  
  18.     enum reboot_mode reboot_mode; /* default restart mode */  
  19.     struct smp_operations *smp; /* SMP operations */  
  20.     bool (*smp_init)(void);  
  21.     void (*fixup)(struct tag *, char **,struct meminfo *);  
  22.     void (*init_meminfo)(void);  
  23.     void (*reserve)(void);/* reserve mem blocks */  
  24.     void (*map_io)(void);/* IO mapping function */  
  25.     void (*init_early)(void);  
  26.     void (*init_irq)(void);  
  27.     void (*init_time)(void);  
  28.     void (*init_machine)(void);  
  29.     void (*init_late)(void);  
  30. #ifdef CONFIG_MULTI_IRQ_HANDLER  
  31.     void (*handle_irq)(struct pt_regs *);  
  32. #endif  
  33.     void (*restart)(enum reboot_mode, const char *);  
  34. };  

1.6 设备节点结构体
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct device_node {  
  2.     const char *name;    //设备name  
  3.     const char *type; //设备类型  
  4.     phandle phandle;  
  5.     const char *full_name; //设备全称,包括父设备名  
  6.   
  7.     struct property *properties; //设备属性链表  
  8.     struct property *deadprops; //removed properties  
  9.     struct device_node *parent; //指向父节点  
  10.     struct device_node *child; //指向子节点  
  11.     struct device_node *sibling; //指向兄弟节点  
  12.     struct device_node *next; //相同设备类型的下一个节点  
  13.     struct device_node *allnext; //next in list of all nodes  
  14.     struct proc_dir_entry *pde; //该节点对应的proc  
  15.     struct kref kref;  
  16.     unsigned long _flags;  
  17.     void *data;  
  18. #if defined(CONFIG_SPARC)  
  19.     const char *path_component_name;  
  20.     unsigned int unique_id;  
  21.     struct of_irq_controller *irq_trans;  
  22. #endif  
  23. };  

1.7 属性结构体

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct property {  
  2.     char *name;        //属性名  
  3.     int length;        //属性值长度  
  4.     void *value;        //属性值  
  5.     struct property *next; //指向下一个属性  
  6.     unsigned long _flags; //标志  
  7.     unsigned int unique_id;  
  8. };  


三、设备树初始化及解析

        分析Linux内核的源码,可以看到其对扁平设备树的解析流程如下:

(1)首先在内核入口处将从u-boot传递过来的镜像基地址。

(2)通过调用early_init_dt_scan()函数来获取内核前期初始化所需的bootargs,cmd_line等系统引导参数。

(3)根据bootargs,cmd_line等系统引导参数进入start_kernel()函数,进行内核的第二阶段初始化。

(4)调用unflatten_device_tree()函数来解析dtb文件,构建一个由device_node结构连接而成的单项链表,并使用全局变量of_allnodes指针来保存这个链表的头指针。

(5)内核调用OF提供的API函数获取of_allnodes链表信息来初始化内核其他子系统、设备等。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. //kernel 初始化的代码(init/main.c)  
  2. asmlinkage void __init start_kernel(void)  
  3. {  
  4.     ...  
  5.     //这个setup_arch就是各个架构自己的设置函数,哪个参与了编译就调用哪个,arm架构应当是arch/arm/kernel/setup.c中的 setup_arch。  
  6.     setup_arch(&command_line);  
  7.     ...  
  8. }  
  9.   
  10. void __init setup_arch(char **cmdline_p)  
  11. {  
  12.     const struct machine_desc *mdesc;  
  13.        
  14.     setup_processor();  
  15.     //setup_machine_fdt函数获取内核前期初始化所需的bootargs,cmd_line等系统引导参数  
  16.     mdesc = setup_machine_fdt(__atags_pointer);//__atags_pointer是bootloader传递参数的物理地址  
  17.     if (!mdesc)  
  18.         mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);  
  19.     machine_desc = mdesc;  
  20.     machine_name = mdesc->name;  
  21.           
  22.     if (mdesc->reboot_mode != REBOOT_HARD)  
  23.         reboot_mode = mdesc->reboot_mode;  
  24.           
  25.     init_mm.start_code = (unsigned long) _text;  
  26.     init_mm.end_code = (unsigned long) _etext;  
  27.     init_mm.end_data = (unsigned long) _edata;  
  28.     init_mm.brk = (unsigned long) _end;  
  29.   
  30.     strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);  
  31.     *cmdline_p = cmd_line;  
  32.    
  33.     parse_early_param();  
  34.    
  35.     sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL);  
  36.    
  37.     early_paging_init(mdesc, lookup_processor_type(read_cpuid_id()));  
  38.     setup_dma_zone(mdesc);  
  39.     sanity_check_meminfo();  
  40.     arm_memblock_init(&meminfo, mdesc);  
  41.    
  42.     paging_init(mdesc);  
  43.     request_standard_resources(mdesc);  
  44.    
  45.     if (mdesc->restart)  
  46.         arm_pm_restart = mdesc->restart;  
  47.    
  48.      //解析设备树  
  49.     unflatten_device_tree();  
  50.    
  51.     ......  
  52. }</span>  

(一) 函数获取内核前期初始化所需的bootargs,cmd_line等系统引导参数

1. setup_machine_fdt()函数获取内核前期初始化所需的bootargs,cmd_line等系统引导参数。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)  
  2. {  
  3.     const struct machine_desc *mdesc, *mdesc_best = NULL;  
  4.    
  5. #ifdef CONFIG_ARCH_MULTIPLATFORM  
  6.     DT_MACHINE_START(GENERIC_DT, "Generic DT based system")  
  7.     MACHINE_END  
  8.        
  9.     mdesc_best = &__mach_desc_GENERIC_DT;  
  10. #endif  
  11.   
  12.     //bootloader传递参数的物理地址不为空,并将物理地址转化为虚拟地址,  
  13.     //通过函数early_init_dt_scan从设备树中读出bootargs,cmd_line等系统引导参数。  
  14.     if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys)))  
  15.         return NULL;  
  16.       
  17.     //根据设备树中根节点属性"compatible"的属性描述,找到系统中定义的最匹配的machine_desc结构,该结构控制系统体系架构相关部分的初始化  
  18.     mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);  
  19.     if (!mdesc) {  
  20.         const char *prop;  
  21.         long size;  
  22.         unsigned long dt_root;  
  23.    
  24.         early_print("\nError: unrecognized/unsupported ""device tree compatible list:\n[ ");  
  25.    
  26.         //找到设备树的根节点,dt_root指向根节点的属性地址处  
  27.         dt_root = of_get_flat_dt_root();  
  28.         //读出根节点的"compatible"属性的属性值  
  29.         prop = of_get_flat_dt_prop(dt_root, "compatible", &size);  
  30.         //将根节点的"compatible"属性的属性值打印出来  
  31.         while (size > 0) {  
  32.             early_print("'%s' ", prop);  
  33.             size -= strlen(prop) + 1;  
  34.             prop += strlen(prop) + 1;  
  35.         }  
  36.         early_print("]\n\n");  
  37.           
  38.         dump_machine_table(); /* does not return */  
  39.     }  
  40.            
  41.     //Change machine number to match the mdesc we're using  
  42.     __machine_arch_type = mdesc->nr;  
  43.   
  44.     return mdesc;  
  45. }  
  46.   
  47. struct boot_param_header *initial_boot_params;  
  48. bool __init early_init_dt_scan(void *params)  
  49. {  
  50.     if (!params)  
  51.         return false;  
  52.           
  53.     //参数params是bootloader传递参数的物理地址转化为的虚拟地址,保存设备树起始地址  
  54.     initial_boot_params = params;  
  55.           
  56.     //验证设备树的magic   
  57.     if (be32_to_cpu(initial_boot_params->magic) != OF_DT_HEADER) {  
  58.         initial_boot_params = NULL;  
  59.         return false;  
  60.     }  
  61.           
  62.     //从设备树中读取chosen节点的信息,包括命令行boot_command_line,initrd location及size  
  63.     of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);  
  64.           
  65.     //得到根节点的{size,address}-cells信息  
  66.     of_scan_flat_dt(early_init_dt_scan_root, NULL);  
  67.           
  68.     //读出设备树的系统内存设置  
  69.     of_scan_flat_dt(early_init_dt_scan_memory, NULL);  
  70.           
  71.     return true;  
  72. }  
  73.   
  74. int __init of_scan_flat_dt(int (*it)(unsigned long node,const char *uname, int depth,void *data),void *data)  
  75. {  
  76.     //找到设备树中结构块的地址  
  77.     unsigned long p = ((unsigned long)initial_boot_params) + be32_to_cpu(initial_boot_params->off_dt_struct);  
  78.     int rc = 0;  
  79.     int depth = -1;  
  80.    
  81.     do {  
  82.         //获得节点起始标志,即OF_DT_BEGIN_NODE,OF_DT_PROP等  
  83.         u32 tag = be32_to_cpup((__be32 *)p);  
  84.         const char *pathp;  
  85.           
  86.         p += 4;//跳过节点起始标志  
  87.           
  88.         //如果是OF_DT_END_NODE标志,表示该节点结束,继续下一结点  
  89.         if (tag == OF_DT_END_NODE) {  
  90.             depth--;  
  91.             continue;  
  92.         }  
  93.           
  94.         //OF_DT_NOP标志代表空节点  
  95.         if (tag == OF_DT_NOP)  
  96.             continue;  
  97.               
  98.         //OF_DT_END标志整个结构块结束  
  99.         if (tag == OF_DT_END)  
  100.             break;  
  101.               
  102.         //OF_DT_PROP标示属性,Property:属性值的字节长度、属性名称在字符串块中的偏移量、属性值和填充。  
  103.         if (tag == OF_DT_PROP) {  
  104.             //属性值的字节大小  
  105.             u32 sz = be32_to_cpup((__be32 *)p);  
  106.             p += 8;//跳过属性值的字节长度、属性名称在字符串块中的偏移量  
  107.             if (be32_to_cpu(initial_boot_params->version) < 0x10)  
  108.                 p = ALIGN(p, sz >= 8 ? 8 : 4);  
  109.             //跳过该属性值的大小  
  110.             p += sz;  
  111.             //地址对齐  
  112.             p = ALIGN(p, 4);  
  113.             //表示一个属性节点遍历完成,因为这里并不是寻找属性节点,而是找OF_DT_BEGIN_NODE开始的节点  
  114.             continue;  
  115.         }  
  116.           
  117.         //若都不是以上节点类型,也不是节点开始标示(OF_DT_BEGIN_NODE),则出错返回  
  118.         if (tag != OF_DT_BEGIN_NODE) {  
  119.             pr_err("Invalid tag %x in flat device tree!\n", tag);  
  120.             return -EINVAL;  
  121.         }  
  122.           
  123.         //执行到这里,标示tag=OF_DT_BEGIN_NODE,表示一个节点的开始,探索深度加1  
  124.         depth++;  
  125.         //节点路径或者节点名  
  126.         pathp = (char *)p;  
  127.         //节点地址四字节对齐,然后p指向节点属性地址  
  128.         p = ALIGN(p + strlen(pathp) + 1, 4);  
  129.         //如果是节点路径,则返回路径名的最后一段,假如为/root/my_root,则返回my_root,即获得节点名  
  130.         if (*pathp == '/')  
  131.             pathp = kbasename(pathp);  
  132.         //调用相应的节点处理函数,p指向节点属性地址  
  133.         rc = it(p, pathp, depth, data);  
  134.         if (rc != 0)  
  135.             break;  
  136.     } while (1);  
  137.    
  138.     return rc;  
  139. }  

1.1 chosen节点
chosen 节点并不代表一个真实的设备,只是作为一个为固件和操作系统之间传递数据的地方,比如引导参数。chosen 节点里的数据也不代表硬件。通常,chosen 节点在.dts 源文件中为空,并在启动时填充。在我们的示例系统中,固件可以往 chosen 节点添加以下信息:
chosen {
bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200"; //节点属性
    linux,initrd-start = <0x85500000>; //节点属性
    linux,initrd-end = <0x855a3212>; //节点属性
};
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,int depth, void *data)  
  2. {  
  3.     unsigned long l;  
  4.     char *p;  
  5.       
  6.     pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);  
  7.   
  8.     //depth深度要为1,表示在根节点下(一般根节点/的depth为0)  
  9.     //data表示系统启动命令行boot_command_line要分配空间  
  10.     //检查节点名是否为chosen节点      
  11.     if (depth != 1 || !data || (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))  
  12.         return 0;  
  13.       
  14.     //从设备树的chosen节点中读出initrd的起始、结束地址  
  15.     early_init_dt_check_for_initrd(node);  
  16.    
  17.     //设备树的chosen节点中读取bootargs属性的属性值,并拷贝给boot_command_line  
  18.     p = of_get_flat_dt_prop(node, "bootargs", &l);  
  19.     if (p != NULL && l > 0)  
  20.         strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE));  
  21.   
  22.     pr_debug("Command line is: %s\n", (char*)data);  
  23.        
  24.     return 1;  
  25. }  
  26.   
  27. static void __init early_init_dt_check_for_initrd(unsigned long node)  
  28. {  
  29.     u64 start, end;  
  30.     unsigned long len;  
  31.     __be32 *prop;  
  32.           
  33.     pr_debug("Looking for initrd properties... ");  
  34.            
  35.     //返回该chosen节点中属性名为"linux,initrd-start"的地址  
  36.     prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);  
  37.     if (!prop)  
  38.         return;  
  39.     //从该地址读出initrd-start的地址  
  40.     start = of_read_number(prop, len/4);  
  41.           
  42.     //返回该chosen节点中属性名为"linux,initrd-end"的地址  
  43.     prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);  
  44.     if (!prop)  
  45.         return;  
  46.     //从该地址读出initrd-end的地址  
  47.     end = of_read_number(prop, len/4);  
  48.           
  49.     //将读出的地址赋值给全局变量initrd_start和initrd_end,用于跟文件系统的挂载  
  50.     initrd_start = (unsigned long)__va(start);  
  51.     initrd_end = (unsigned long)__va(end);  
  52.     initrd_below_start_ok = 1;  
  53.           
  54.     pr_debug("initrd_start=0x%llx initrd_end=0x%llx\n",(unsigned long long)start, (unsigned long long)end);  
  55. }  
  56.   
  57. void *__init of_get_flat_dt_prop(unsigned long node, const char *name,unsigned long *size)  
  58. {  
  59.     return of_fdt_get_property(initial_boot_params, node, name, size);  
  60. }  
  61.   
  62. void *of_fdt_get_property(struct boot_param_header *blob,unsigned long node, const char *name,unsigned long *size)  
  63. {  
  64.     //p指向该chosen节点的节点属性地址  
  65.     unsigned long p = node;  
  66.        
  67.     //因为一个节点中可能包含多个属性,所以这里遍历chosen节点中的所有属性,找到属性名为name的属性  
  68.     do {  
  69.         //取得该节点属性的起始标志OF_DT_PROP  
  70.         u32 tag = be32_to_cpup((__be32 *)p);  
  71.         u32 sz, noff;  
  72.         const char *nstr;  
  73.            
  74.         p += 4;//跳过节点属性的起始标志  
  75.   
  76.         //空节点则继续  
  77.         if (tag == OF_DT_NOP)  
  78.             continue;  
  79.         //非属性标志则返回NULL  
  80.         if (tag != OF_DT_PROP)  
  81.             return NULL;  
  82.            
  83.         //运行到这里表示为属性OF_DT_PROP  
  84.         //取得该节点属性的的属性值size  
  85.         sz = be32_to_cpup((__be32 *)p);  
  86.         //取得该属性名的在字符串块中的偏移值  
  87.         noff = be32_to_cpup((__be32 *)(p + 4));  
  88.         p += 8;  
  89.         //跳过对齐填充字段  
  90.         if (be32_to_cpu(blob->version) < 0x10)  
  91.             p = ALIGN(p, sz >= 8 ? 8 : 4);  
  92.            
  93.         //在字符串块取出该属性的名称  
  94.         nstr = of_fdt_get_string(blob, noff);  
  95.         if (nstr == NULL) {  
  96.             pr_warning("Can't find property index name !\n");  
  97.             return NULL;  
  98.         }  
  99.   
  100.         //若名称一致,表示找到我们要找的属性  
  101.         if (strcmp(name, nstr) == 0) {  
  102.             if (size)  
  103.                 *size = sz;//返回该属性的属性值size  
  104.             //返回该属性值所在的地址  
  105.             return (void *)p;  
  106.         }  
  107.         //否则继续下一个属性  
  108.         p += sz;  
  109.         p = ALIGN(p, 4);  
  110.     } while (1);  
  111. }  
  112.   
  113. char *of_fdt_get_string(struct boot_param_header *blob, u32 offset)  
  114. {  
  115.     //从设备树的字符串块的offset处读出name  
  116.     return ((char *)blob) + be32_to_cpu(blob->off_dt_strings) + offset;  
  117. }  
  118.   
  119. static inline u64 of_read_number(const __be32 *cell, int size)  
  120. {  
  121.     u64 r = 0;  
  122.     //读出属性值,属性值大小为size  
  123.     while (size--)  
  124.         r = (r << 32) | be32_to_cpu(*(cell++));  
  125.     return r;  
  126. }  

1.2 根节点"/"
设备树有且仅有一个根节点,即“/”,根节点下包含很多子节点,例入下图,根节点为"/",根节点的子节点为"chosen",根节点的属性包含"compatible","#address-cells","#size-cells","interrupt-parent"等。属性model指明了目标板平台或模块的名称,属性compatible值指明和目标板为同一系列的兼容的开发板名称。对于大多数32位平台,属性#address-cells和#size-cells的值一般为1。#address-cells = <1>; 1表示地址32位,2表示地址64位。#size-cells = <1>;1表示rangs的每部分占一个cell,依此类推 
{
    compatible = "sprd,spx15";
    #address-cells = <1>;
    #size-cells = <1>;
    interrupt-parent = <&gic>;


    chosen {
        bootargs = "loglevel=8 console=ttyS1,115200n8 init=/init root=/dev/ram0 rw";
        linux,initrd-start = <0x85500000>;
        linux,initrd-end = <0x855a3212>;
    };
}
所以本函数就是读取根节点的"#address-cells","#size-cells"属性
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. int __init early_init_dt_scan_root(unsigned long node, const char *uname,int depth, void *data)  
  2. {  
  3.     __be32 *prop;  
  4.       
  5.     //根节点的探索深度depth一定为0,否则不是根节点"/",node为根节点的属性地址      
  6.     if (depth != 0)  
  7.         return 0;  
  8.           
  9.     dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;  
  10.     dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;  
  11.           
  12.     //返回根节点节点属性名为"#size-cells"的地址  
  13.     prop = of_get_flat_dt_prop(node, "#size-cells", NULL);  
  14.     if (prop)  
  15.         dt_root_size_cells = be32_to_cpup(prop);//从该属性获得root size  
  16.           
  17.     pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells);  
  18.           
  19.     //返回根节点节点属性名为"#address-cells"的地址  
  20.     prop = of_get_flat_dt_prop(node, "#address-cells", NULL);  
  21.     if (prop)  
  22.         dt_root_addr_cells = be32_to_cpup(prop);//从该属性获得root address  
  23.     pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells);  
  24.           
  25.     return 1;  
  26. }  


1.3 memory节点
      memory节点用于描述目标板上物理内存范围,一般称作/memory节点,可以有一个或多个。当有多个节点时,需要后跟单元地址予以区分;只有一个单元地址时,可以不写单元地址,默认为0。此节点包含板上物理内存的属性,一般要指定device_type(固定为"memory")和reg属性。其中reg的属性值以<起始地址 空间大小>的形式给出,如下示例中目标板内存起始地址为0x80000000,大小为0x20000000字节。 
memory {
    device_type = "memory";
    reg = <0x80000000 0x20000000>;
};
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. int __init early_init_dt_scan_memory(unsigned long node, const char *uname,int depth, void *data)  
  2. {  
  3.     //获取该节点中属性"device_type"的属性值  
  4.     char *type = of_get_flat_dt_prop(node, "device_type", NULL);  
  5.     __be32 *reg, *endp;  
  6.     unsigned long l;  
  7.       
  8.     //检查"device_type"的属性值,确定该节点是否为memory节点  
  9.     if (type == NULL) {  
  10.         if (depth != 1 || strcmp(uname, "memory@0") != 0)  
  11.             return 0;  
  12.     } else if (strcmp(type, "memory") != 0)  
  13.         return 0;  
  14.       
  15.     //从该memory节点中获取属性"linux,usable-memory"的属性值,及属性值大小l  
  16.     reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);  
  17.     if (reg == NULL)  
  18.         reg = of_get_flat_dt_prop(node, "reg", &l);  
  19.     if (reg == NULL)  
  20.         return 0;  
  21.       
  22.     //reg为属性值的起始地址,endp为结束地址  
  23.     endp = reg + (l / sizeof(__be32));  
  24.       
  25.     pr_debug("memory scan node %s, reg size %ld, data: %x %x %x %x,\n",  
  26.                         uname, l, reg[0], reg[1], reg[2], reg[3]);  
  27.       
  28.     while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {  
  29.         u64 base, size;  
  30.       
  31.         //读出物理内存的起始地址以及size  
  32.         base = dt_mem_next_cell(dt_root_addr_cells, &reg);  
  33.         size = dt_mem_next_cell(dt_root_size_cells, &reg);  
  34.       
  35.         if (size == 0)  
  36.             continue;  
  37.         pr_debug(" - %llx , %llx\n", (unsigned long long)base,(unsigned long long)size);  
  38.       
  39.         //添加物理内存到memblock中进行管理,这里不再展开  
  40.         early_init_dt_add_memory_arch(base, size);  
  41.     }  
  42.       
  43.     return 0;  
  44. }  


2. 通过比较根节点属性compatible值指明和目标板为同一系列的兼容的开发板名称
compatible制定系统的名称。它包含"<manufacture>,<model>"格式的字符串。准确地确定器件型号是非常重要的,并且我们需要包含厂商的名字来避免名字空间冲突。因为操作系统会使用compatible这个值来决定怎样在这个机器上运行,所以在这个属性中放入正确的值是非常重要的。
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. const void * __init of_flat_dt_match_machine(const void *default_match,  
  2.                                             const void * (*get_next_compat)(const char * const**))  
  3. {  
  4.     const void *data = NULL;  
  5.     const void *best_data = default_match;  
  6.     const char *const *compat;  
  7.     unsigned long dt_root;  
  8.     unsigned int best_score = ~1, score = 0;  
  9.       
  10.     //读出设备树的根节点,dt_root指向根节点的属性地址处  
  11.     dt_root = of_get_flat_dt_root();  
  12.   
  13.     //调用arch_get_next_mach,遍历该系统中的所有machine_desc结构,返回给data,并且返回该结构的compatible  
  14.     while ((data = get_next_compat(&compat))) {  
  15.         //将系统中的所有machine_desc结构的compatible字符串与设备树根节点的compatible属性进行match  
  16.         score = of_flat_dt_match(dt_root, compat);  
  17.         //返回与根节点属性compatible属性值最匹配的machine_desc结构  
  18.         if (score > 0 && score < best_score) {  
  19.             best_data = data;  
  20.             best_score = score;  
  21.         }  
  22.     }  
  23.       
  24.     if (!best_data) {  
  25.         const char *prop;  
  26.         long size;  
  27.       
  28.         pr_err("\n unrecognized device tree list:\n[ ");  
  29.        
  30.         prop = of_get_flat_dt_prop(dt_root, "compatible", &size);  
  31.         if (prop) {  
  32.             while (size > 0) {  
  33.                 printk("'%s' ", prop);  
  34.                 size -= strlen(prop) + 1;  
  35.                 prop += strlen(prop) + 1;  
  36.             }  
  37.         }  
  38.         printk("]\n\n");  
  39.         return NULL;  
  40.     }  
  41.        
  42.     pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());  
  43.       
  44.     return best_data;  
  45. }  
  46.   
  47.   
  48. //查找设备树的根节点  
  49. unsigned long __init of_get_flat_dt_root(void)  
  50. {  
  51.     //找到设备树的设备块起始地址  
  52.     unsigned long p = ((unsigned long)initial_boot_params) +  
  53.                                     be32_to_cpu(initial_boot_params->off_dt_struct);  
  54.       
  55.     //跳过空节点   
  56.     while (be32_to_cpup((__be32 *)p) == OF_DT_NOP)  
  57.         p += 4;  
  58.     BUG_ON(be32_to_cpup((__be32 *)p) != OF_DT_BEGIN_NODE);  
  59.     p += 4;  
  60.     //第一个节点就是根节点,p指向根节点的属性地址处  
  61.     return ALIGN(p + strlen((char *)p) + 1, 4);  
  62. }  
  63.   
  64. //arch/arm/kernel/devtree.c  
  65. __arch_info_begin 和 __arch_info_end是在 arch/arm/kernel/vmlinux.lds.S中:  
  66. 00034: __arch_info_begin = .;  
  67. 00035: *(.arch.info.init)  
  68. 00036: __arch_info_end = .;  
  69. 这里是声明了两个变量:__arch_info_begin 和 __arch_info_end,其中等号后面的"."是location counter。在__arch_info_begin 的位置上,放置所有文件中的 ".arch.info.init" 段的内容,然后紧接着是 __arch_info_end 的位置.".arch.info.init" 段中定义了设备的machine_desc结构。  
  70. //这里就是取出一个machine_desc结构  
  71. static const void * __init arch_get_next_mach(const char *const **match)  
  72. {  
  73.     static const struct machine_desc *mdesc = __arch_info_begin;  
  74.     const struct machine_desc *m = mdesc;  
  75.           
  76.     if (m >= __arch_info_end)  
  77.         return NULL;  
  78.           
  79.     mdesc++;//指针后移,确保下次取出下一个machine_desc结构  
  80.     *match = m->dt_compat;  
  81.     return m;//返回当前的machine_desc结构  
  82. }  
  83.   
  84. //与设备树根节点进行match  
  85. int __init of_flat_dt_match(unsigned long node, const char *const *compat)  
  86. {  
  87.     //initial_boot_params指向设备树起始地址  
  88.     //node指向根节点的属性地址  
  89.     //compat为系统中machine_desc结构的compatible字符串  
  90.     return of_fdt_match(initial_boot_params, node, compat);  
  91. }  
  92.   
  93. int of_fdt_match(struct boot_param_header *blob, unsigned long node,const char *const *compat)  
  94. {  
  95.     unsigned int tmp, score = 0;  
  96.           
  97.     if (!compat)  
  98.         return 0;  
  99.       
  100.     //遍历compatible字符串数组      
  101.     while (*compat) {  
  102.         //返回compatible的匹配值  
  103.         tmp = of_fdt_is_compatible(blob, node, *compat);  
  104.         if (tmp && (score == 0 || (tmp < score)))  
  105.             score = tmp;//返回最大的匹配值  
  106.         compat++;//下一个字符串  
  107.     }  
  108.           
  109.     return score;  
  110. }  
  111.   
  112. int of_fdt_is_compatible(struct boot_param_header *blob,unsigned long node, const char *compat)  
  113. {  
  114.     const char *cp;  
  115.     unsigned long cplen, l, score = 0;  
  116.       
  117.     //从根节点中读出属性"compatible"的属性值  
  118.     cp = of_fdt_get_property(blob, node, "compatible", &cplen);  
  119.     if (cp == NULL)  
  120.         return 0;  
  121.       
  122.     //比较compatible的指定的属性字符串的一致性  
  123.     while (cplen > 0) {  
  124.         score++;  
  125.         if (of_compat_cmp(cp, compat, strlen(compat)) == 0)  
  126.             return score;  
  127.         l = strlen(cp) + 1;  
  128.         cp += l;  
  129.         cplen -= l;  
  130.     }  
  131.           
  132.     return 0;  
  133. }  


(二)、解析设备树

       unflatten_device_tree()函数来解析dtb文件,构建一个由device_node结构连接而成的单项链表,并使用全局变量of_allnodes指针来保存这个链表的头指针。内核调用OF提供的API函数获取of_allnodes链表信息来初始化内核其他子系统、设备。
[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. void __init unflatten_device_tree(void)  
  2. {  
  3.     //解析设备树,将所有的设备节点链入全局链表    of_allnodes中  
  4.     __unflatten_device_tree(initial_boot_params, &of_allnodes,early_init_dt_alloc_memory_arch);  
  5.        
  6.     //设置内核输出终端,以及遍历“/aliases”节点下的所有的属性,挂入相应链表  
  7.     of_alias_scan(early_init_dt_alloc_memory_arch);  
  8. }  
  9.   
  10. static void __unflatten_device_tree(struct boot_param_header *blob,  
  11.                                     struct device_node **mynodes,  
  12.                                     void * (*dt_alloc)(u64 size, u64 align))  
  13. {  
  14.     unsigned long size;  
  15.     void *start, *mem;  
  16.     struct device_node **allnextp = mynodes;  
  17.       
  18.     pr_debug(" -> unflatten_device_tree()\n");  
  19.        
  20.     if (!blob) {  
  21.         pr_debug("No device tree pointer\n");  
  22.         return;  
  23.     }  
  24.        
  25.     pr_debug("Unflattening device tree:\n");  
  26.     pr_debug("magic: %08x\n", be32_to_cpu(blob->magic));  
  27.     pr_debug("size: %08x\n", be32_to_cpu(blob->totalsize));  
  28.     pr_debug("version: %08x\n", be32_to_cpu(blob->version));  
  29.       
  30.     //检查设备树magic  
  31.     if (be32_to_cpu(blob->magic) != OF_DT_HEADER) {  
  32.         pr_err("Invalid device tree blob header\n");  
  33.         return;  
  34.     }  
  35.       
  36.     //找到设备树的设备节点起始地址  
  37.     start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct);  
  38.     //第一次调用mem传0,allnextpp传NULL,实际上是为了计算整个设备树所要的空间  
  39.     size = (unsigned long)unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);  
  40.     size = ALIGN(size, 4);//4字节对齐  
  41.        
  42.     pr_debug(" size is %lx, allocating...\n", size);  
  43.        
  44.     //调用early_init_dt_alloc_memory_arch函数,为设备树分配内存空间  
  45.     mem = dt_alloc(size + 4, __alignof__(struct device_node));  
  46.     memset(mem, 0, size);  
  47.       
  48.     //设备树结束处赋值0xdeadbeef,为了后边检查是否有数据溢出  
  49.     *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);   
  50.     pr_debug(" unflattening %p...\n", mem);  
  51.        
  52.     //再次获取设备树的设备节点起始地址  
  53.     start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct);  
  54.     //mem为设备树分配的内存空间,allnextp指向全局变量of_allnodes,生成整个设备树  
  55.     unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0);  
  56.     if (be32_to_cpup(start) != OF_DT_END)  
  57.         pr_warning("Weird tag at end of tree: %08x\n", be32_to_cpup(start));  
  58.     if (be32_to_cpup(mem + size) != 0xdeadbeef)  
  59.         pr_warning("End of tree marker overwritten: %08x\n",be32_to_cpup(mem + size));  
  60.     *allnextp = NULL;  
  61.        
  62.     pr_debug(" <- unflatten_device_tree()\n");  
  63. }  
  64.   
  65. static void * unflatten_dt_node(struct boot_param_header *blob,  
  66.                                 void *mem,void **p,  
  67.                                 struct device_node *dad,  
  68.                                 struct device_node ***allnextpp,  
  69.                                 unsigned long fpsize)  
  70. {  
  71.     struct device_node *np;  
  72.     struct property *pp, **prev_pp = NULL;  
  73.     char *pathp;  
  74.     u32 tag;  
  75.     unsigned int l, allocl;  
  76.     int has_name = 0;  
  77.     int new_format = 0;  
  78.       
  79.     //*p指向设备树的设备块起始地址  
  80.     tag = be32_to_cpup(*p);  
  81.     //每个有孩子的设备节点,其tag一定是OF_DT_BEGIN_NODE  
  82.     if (tag != OF_DT_BEGIN_NODE) {  
  83.         pr_err("Weird tag at start of node: %x\n", tag);  
  84.         return mem;  
  85.     }  
  86.   
  87.     *p += 4;//地址+4,跳过tag,这样指向节点的名称或者节点路径名  
  88.     pathp = *p;//获得节点名或者节点路径名  
  89.     l = allocl = strlen(pathp) + 1;//该节点名称的长度  
  90.     *p = PTR_ALIGN(*p + l, 4);//地址对齐后,*p指向该节点属性的地址  
  91.       
  92.     //如果是节点名则进入,若是节点路径名则(*pathp) == '/'  
  93.     if ((*pathp) != '/') {  
  94.         new_format = 1;  
  95.         if (fpsize == 0) {//fpsize=0  
  96.             fpsize = 1;  
  97.             allocl = 2;  
  98.             l = 1;  
  99.             *pathp = '\0';  
  100.         } else {  
  101.             fpsize += l;//代分配的长度=本节点名称长度+父亲节点绝对路径的长度  
  102.             allocl = fpsize;  
  103.         }  
  104.     }  
  105.       
  106.     //分配一个设备节点device_node结构,*mem记录分配了多大空间,最终会累加计算出该设备树总共分配的空间大小  
  107.     np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl,__alignof__(struct device_node));  
  108.   
  109.     //第一次调用unflatten_dt_node时,allnextpp=NULL  
  110.     //第一次调用unflatten_dt_node时,allnextpp指向全局变量of_allnodes的地址  
  111.     if (allnextpp) {  
  112.         char *fn;  
  113.         //full_name保存完整的节点名,即包括各级父节点的名称  
  114.         np->full_name = fn = ((char *)np) + sizeof(*np);  
  115.         //若new_format=1,表示pathp保存的是节点名,而不是节点路径名,所以需要加上父节点的name  
  116.         if (new_format) {  
  117.             if (dad && dad->parent) {  
  118.                 strcpy(fn, dad->full_name);//把父亲节点绝对路径先拷贝  
  119.                 fn += strlen(fn);  
  120.             }  
  121.             *(fn++) = '/';  
  122.         }  
  123.         memcpy(fn, pathp, l);//拷贝本节点的名称  
  124.       
  125.         //prev_pp指向节点的属性链表  
  126.         prev_pp = &np->properties;  
  127.   
  128.         //当前节点插入全局链表of_allnodes  
  129.         **allnextpp = np;  
  130.         *allnextpp = &np->allnext;  
  131.   
  132.         //若父亲节点不为空,则设置该节点的parent  
  133.         if (dad != NULL) {  
  134.             np->parent = dad;//指向父亲节点  
  135.             if (dad->next == NULL)//第一个孩子  
  136.                 dad->child = np;//child指向第一个孩子  
  137.             else  
  138.                 dad->next->sibling = np;//把np插入next,这样孩子节点形成链表  
  139.             dad->next = np;  
  140.         }  
  141.         kref_init(&np->kref);  
  142.     }  
  143.   
  144.     //分析该节点的属性  
  145.     while (1) {  
  146.         u32 sz, noff;  
  147.         char *pname;  
  148.        
  149.         //前边已经将*p移到指向节点属性的地址处,取出属性标识  
  150.         tag = be32_to_cpup(*p);  
  151.         //空属性,则跳过  
  152.         if (tag == OF_DT_NOP) {  
  153.             *p += 4;  
  154.             continue;  
  155.         }  
  156.         //tag不是属性则退出,对于有孩子节点退出时为OF_DT_BEGIN_NODE,对于叶子节点退出时为OF_DT_END_NODE  
  157.         if (tag != OF_DT_PROP)  
  158.             break;  
  159.         //地址加4,跳过tag  
  160.         *p += 4;  
  161.         //获得属性值的大小,是以为占多少整形指针计算的  
  162.         sz = be32_to_cpup(*p);  
  163.         //获取属性名称在节点的字符串块中的偏移  
  164.         noff = be32_to_cpup(*p + 4);  
  165.         //地址加8,跳过属性值的大小和属性名称在节点的字符串块中的偏移  
  166.         *p += 8;  
  167.         //地址对齐后,*P指向属性值所在的地址  
  168.         if (be32_to_cpu(blob->version) < 0x10)  
  169.             *p = PTR_ALIGN(*p, sz >= 8 ? 8 : 4);  
  170.           
  171.         //从节点的字符串块中noff偏移处,得到该属性的name  
  172.         pname = of_fdt_get_string(blob, noff);  
  173.         if (pname == NULL) {  
  174.             pr_info("Can't find property name in list !\n");  
  175.             break;  
  176.         }  
  177.   
  178.         //如果有名称为name的属性,表示变量has_name为1  
  179.         if (strcmp(pname, "name") == 0)  
  180.             has_name = 1;  
  181.         //计算该属性name的大小  
  182.         l = strlen(pname) + 1;  
  183.   
  184.         //为该属性分配一个属性结构,即struct property,  
  185.         //*mem记录分配了多大空间,最终会累加计算出该设备树总共分配的空间大小  
  186.         pp = unflatten_dt_alloc(&mem, sizeof(struct property),__alignof__(struct property));  
  187.   
  188.         //第一次调用unflatten_dt_node时,allnextpp=NULL  
  189.         //第一次调用unflatten_dt_node时,allnextpp指向全局变量of_allnodes的地址  
  190.         if (allnextpp) {  
  191.             if ((strcmp(pname, "phandle") == 0) || (strcmp(pname, "linux,phandle") == 0)) {  
  192.                 if (np->phandle == 0)  
  193.                     np->phandle = be32_to_cpup((__be32*)*p);  
  194.             }  
  195.             if (strcmp(pname, "ibm,phandle") == 0)  
  196.                 np->phandle = be32_to_cpup((__be32 *)*p);  
  197.             pp->name = pname;//属性名  
  198.             pp->length = sz;//属性值长度  
  199.             pp->value = *p;//属性值  
  200.   
  201.             //属性插入该节点的属性链表np->properties  
  202.             *prev_pp = pp;  
  203.             prev_pp = &pp->next;  
  204.         }  
  205.         *p = PTR_ALIGN((*p) + sz, 4);//指向下一个属性  
  206.     }  
  207.     //至此遍历完该节点的所有属性  
  208.   
  209.     //如果该节点没有"name"的属性,则为该节点生成一个name属性,插入该节点的属性链表  
  210.     if (!has_name) {  
  211.         char *p1 = pathp, *ps = pathp, *pa = NULL;  
  212.         int sz;  
  213.        
  214.         while (*p1) {  
  215.             if ((*p1) == '@')  
  216.                 pa = p1;  
  217.             if ((*p1) == '/')  
  218.                 ps = p1 + 1;  
  219.                 p1++;  
  220.         }  
  221.         if (pa < ps)  
  222.             pa = p1;  
  223.         sz = (pa - ps) + 1;  
  224.         pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz,__alignof__(struct property));  
  225.         if (allnextpp) {  
  226.             pp->name = "name";  
  227.             pp->length = sz;  
  228.             pp->value = pp + 1;  
  229.             *prev_pp = pp;  
  230.             prev_pp = &pp->next;  
  231.             memcpy(pp->value, ps, sz - 1);  
  232.             ((char *)pp->value)[sz - 1] = 0;  
  233.             pr_debug("fixed up name for %s -> %s\n", pathp,(char *)pp->value);  
  234.         }  
  235.     }  
  236.   
  237.     //若设置了allnextpp指针  
  238.     if (allnextpp) {  
  239.         *prev_pp = NULL;  
  240.         //设置节点的名称  
  241.         np->name = of_get_property(np, "name", NULL);  
  242.         //设置该节点对应的设备类型  
  243.         np->type = of_get_property(np, "device_type", NULL);  
  244.       
  245.         if (!np->name)  
  246.             np->name = "<NULL>";  
  247.         if (!np->type)  
  248.             np->type = "<NULL>";  
  249.     }  
  250.   
  251.     //前边在遍历属性时,tag不是属性则退出  
  252.     //对于有孩子节点退出时tag为OF_DT_BEGIN_NODE,对于叶子节点退出时tag为OF_DT_END_NODE  
  253.     while (tag == OF_DT_BEGIN_NODE || tag == OF_DT_NOP) {  
  254.         //空属性则指向下个属性  
  255.         if (tag == OF_DT_NOP)  
  256.             *p += 4;  
  257.         else  
  258.             //OF_DT_BEGIN_NODE则表明其还有子节点,所以递归分析其子节点  
  259.             mem = unflatten_dt_node(blob, mem, p, np, allnextpp,fpsize);  
  260.         tag = be32_to_cpup(*p);  
  261.     }  
  262.   
  263.     //对于叶子节点或者分析完成  
  264.     if (tag != OF_DT_END_NODE) {  
  265.         pr_err("Weird tag at end of node: %x\n", tag);  
  266.         return mem;  
  267.     }  
  268.     *p += 4;  
  269.     //mem返回整个设备树所分配的内存大小,即设备树占的内存空间  
  270.     return mem;  
  271. }  
  272.   
  273.   
  274. //从mem分配内存空间,*mem记录分配了多大空间  
  275. static void *unflatten_dt_alloc(void **mem, unsigned long size,unsigned long align)  
  276. {  
  277.     void *res;  
  278.        
  279.     *mem = PTR_ALIGN(*mem, align);  
  280.     res = *mem;  
  281.     *mem += size;  
  282.           
  283.     return res;  
  284. }  
  285.   
  286. //一个特定的节点通常是以完整的路径来引用,比如/external-bus/ethernet@0,0,不过当一个用户真的想知道“哪个设备是eth0”时,这将会很繁琐。aliases节点可以用来为一个完整的设备路径分配一个短的别名。比如:  
  287. //aliases {  
  288. //    serial0 = &uart0;  
  289. //    serial1 = &uart1;  
  290. //    serial2 = &uart2;  
  291. //    serial3 = &uart3;  
  292. //    ethernet0 = ð0;  
  293. //    serial0 = &serial0;  
  294. //};  
  295. //当需要为设备指定一个标示符时,操作系统欢迎大家使用别名。  
  296. //设置内核输出终端,以及遍历“/aliases”节点下的所有的属性,挂入相应链表  
  297. void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align))  
  298. {  
  299.     struct property *pp;  
  300.       
  301.     //根据全局的device_node结构的链表of_allnodes,查找节点名为“/chosen”或者“/chosen@0”的节点,赋值给全局变量of_chosen  
  302.     of_chosen = of_find_node_by_path("/chosen");  
  303.     if (of_chosen == NULL)  
  304.         of_chosen = of_find_node_by_path("/chosen@0");  
  305.       
  306.     //找到的话,则在该节点查找"linux,stdout-path"    属性  
  307.     //"linux,stdout-path"的属性值,常常为标准终端设备的节点路径名,内核会以此作为默认终端  
  308.     if (of_chosen) {  
  309.         const char *name;  
  310.         //返回属性"linux,stdout-path"的属性值  
  311.         name = of_get_property(of_chosen, "linux,stdout-path", NULL);  
  312.         //根据属性值查找设备节点device_node,即内核默认终端的设备节点,赋值给全局变量of_stdout  
  313.         if (name)  
  314.             of_stdout = of_find_node_by_path(name);  
  315.     }  
  316.       
  317.     //据全局链表of_allnodes,查找节点名为“/aliases”的节点,赋值给全局变量of_aliases  
  318.     of_aliases = of_find_node_by_path("/aliases");  
  319.     if (!of_aliases)  
  320.         return;  
  321.        
  322.     //遍历“/aliases”节点下的所有的属性  
  323.     for_each_property_of_node(of_aliases, pp) {  
  324.         const char *start = pp->name;//属性名  
  325.         const char *end = start + strlen(start);//属性名结尾  
  326.         struct device_node *np;  
  327.         struct alias_prop *ap;  
  328.         int id, len;  
  329.           
  330.         //跳过"name"、"phandle"和"linux,phandle"的属性  
  331.         if (!strcmp(pp->name, "name") ||  
  332.             !strcmp(pp->name, "phandle") ||  
  333.             !strcmp(pp->name, "linux,phandle"))  
  334.             continue;  
  335.               
  336.             //根据属性值找到对应的设备节点  
  337.             np = of_find_node_by_path(pp->value);  
  338.             if (!np)  
  339.                 continue;  
  340.           
  341.             //去除属性名中结尾的数字,即设备id  
  342.             while (isdigit(*(end-1)) && end > start)  
  343.                 end--;  
  344.             //len为属性名去掉结尾数字序号的长度  
  345.             len = end - start;  
  346.           
  347.             //此时end指向属性名中结尾的数字,即开始时start指向“&uart0”,end指向字符串结尾。  
  348.             //经过上步操作,start仍指向“&uart0”字符串开始处,而end指向字符‘0’。  
  349.             //将end字符串转化为10进制数,赋值给id,作为设备的id号  
  350.             if (kstrtoint(end, 10, &id) < 0)  
  351.                 continue;  
  352.           
  353.             //分配alias_prop结构  
  354.             ap = dt_alloc(sizeof(*ap) + len + 1, 4);  
  355.             if (!ap)  
  356.                 continue;  
  357.             memset(ap, 0, sizeof(*ap) + len + 1);  
  358.             ap->alias = start;  
  359.             //将该设备的aliases指向对应的device_node,并且链入aliases_lookup链表中  
  360.             of_alias_add(ap, np, id, start, len);  
  361.         }  
  362. }  


四、OF提供的常用API函数
    OF提供的函数主要集中在drivers/of/目录下,有address.c,base.c,device.c,fdt.c,irq.c,platform.c等等
1. 用来查找在dtb中的根节点
unsigned long __init of_get_flat_dt_root(void)


2. 根据deice_node结构的full_name参数,在全局链表of_allnodes中,查找合适的device_node
struct device_node *of_find_node_by_path(const char *path)
例如:
struct device_node *cpus;
cpus=of_find_node_by_path("/cpus");


3. 若from=NULL,则在全局链表of_allnodes中根据name查找合适的device_node
struct device_node *of_find_node_by_name(struct device_node *from,const char *name)
例如:
struct device_node *np;
np = of_find_node_by_name(NULL,"firewire");


4. 根据设备类型查找相应的device_node
struct device_node *of_find_node_by_type(struct device_node *from,const char *type)
例如:
struct device_node *tsi_pci;
tsi_pci= of_find_node_by_type(NULL,"pci");


5. 根据compatible字符串查找device_node
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)


6. 根据节点属性的name查找device_node
struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name)


7. 根据phandle查找device_node
struct device_node *of_find_node_by_phandle(phandle handle)


8. 根据alias的name获得设备id号
int of_alias_get_id(struct device_node *np, const char *stem)


9. device node计数增加/减少
struct device_node *of_node_get(struct device_node *node)
void of_node_put(struct device_node *node)


10. 根据property结构的name参数,在指定的device node中查找合适的property
struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)


11. 根据property结构的name参数,返回该属性的属性值
const void *of_get_property(const struct device_node *np, const char *name,int *lenp)


12. 根据compat参数与device node的compatible匹配,返回匹配度
int of_device_is_compatible(const struct device_node *device,const char *compat)


13. 获得父节点的device node
struct device_node *of_get_parent(const struct device_node *node)


14. 将matches数组中of_device_id结构的name和type与device node的compatible和type匹配,返回匹配度最高的of_device_id结构
const struct of_device_id *of_match_node(const struct of_device_id *matches,const struct device_node *node)


15. 根据属性名propname,读出属性值中的第index个u32数值给out_value
int of_property_read_u32_index(const struct device_node *np,const char *propname,u32 index, u32 *out_value)


16. 根据属性名propname,读出该属性的数组中sz个属性值给out_values
int of_property_read_u8_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz)
int of_property_read_u16_array(const struct device_node *np,const char *propname, u16 *out_values, size_t sz)
int of_property_read_u32_array(const struct device_node *np,const char *propname, u32 *out_values,size_t sz)


17. 根据属性名propname,读出该属性的u64属性值
int of_property_read_u64(const struct device_node *np, const char *propname,u64 *out_value)


18. 根据属性名propname,读出该属性的字符串属性值
int of_property_read_string(struct device_node *np, const char *propname,const char **out_string)


19. 根据属性名propname,读出该字符串属性值数组中的第index个字符串
int of_property_read_string_index(struct device_node *np, const char *propname,int index, const char **output)


20. 读取属性名propname中,字符串属性值的个数
int of_property_count_strings(struct device_node *np, const char *propname)


21. 读取该设备的第index个irq号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)


22. 读取该设备的第index个irq号,并填充一个irq资源结构体
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)


23. 获取该设备的irq个数
int of_irq_count(struct device_node *dev)


24. 获取设备寄存器地址,并填充寄存器资源结构体
int of_address_to_resource(struct device_node *dev, int index,struct resource *r)
const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,unsigned int *flags)


25. 获取经过映射的寄存器虚拟地址
void __iomem *of_iomap(struct device_node *np, int index)


24. 根据device_node查找返回该设备对应的platform_device结构
struct platform_device *of_find_device_by_node(struct device_node *np)


25. 根据device node,bus id以及父节点创建该设备的platform_device结构
struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent)
static struct platform_device *of_platform_device_create_pdata(struct device_node *np,const char *bus_id,
                                                            void *platform_data,struct device *parent)


26. 遍历of_allnodes中的节点挂接到of_platform_bus_type总线上,由于此时of_platform_bus_type总线上还没有驱动,所以此时不进行匹配
int of_platform_bus_probe(struct device_node *root,const struct of_device_id *matches,struct device *parent)


27. 遍历of_allnodes中的所有节点,生成并初始化platform_device结构
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;
    
    //获得设备树的根节点 
    root = root ? of_node_get(root) : of_find_node_by_path("/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/402103.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Github Page创建个人主页以及绑定域名

2019独角兽企业重金招聘Python工程师标准>>> 在github中 我们可以通过github page创建个人主页 以及绑定域名 据说有300m空间 以及无限流量 不过只能支持静态html 以及一些脚本语言 顺便吐槽一下 阿里云最低配那个云空间服务器 512m内存 启动web服务器后 mys…

Exynos4412 内核移植(五)—— 驱动的移植

以移植自己制作的驱动&#xff0c;学习内核移植中的驱动移植&#xff0c;及 驱动程序的动态编译和静态编译 硬件环境&#xff1a; Linux 内核版本&#xff1a;Linux 3.14 主机&#xff1a;Ubuntu 12.04发行版 目标机&#xff1a;FS4412平台 交叉编译工具&#xff1a;arm-none-l…

FTP文件共传输服务

FTP文件共传输服务一&#xff0c;vsftpd服务基础&#xff08;1&#xff09;&#xff0c;FTP服务概述FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#xff09;是典型的C/S结构的应用层协议&#xff0c;需要由服务端软件、客户端软件共同实现文件传达输功能…

Exynos4412 内核移植(四)—— MMU 相关知识解析

一、MMU的产生 许多年以前&#xff0c;当人们还在使用DOS或是更古老的操作系统的时候&#xff0c;计算机的内存还非常小&#xff0c;一般都是以K为单位进行计算&#xff0c;相应的&#xff0c;当时的程序规模也不大&#xff0c;所以内存容量虽然小&#xff0c;但还是可以容纳当…

Mysql limit 子查询

为什么80%的码农都做不了架构师&#xff1f;>>> &#xff08;1&#xff09;mysql limit 不支持子查询像下面这条语句无法执行 SELECT * FROM b_c_s1 where CT_ID IN (SELECT DISTINCT CT_ID FROM b_c_s1 LIMIT 0,2); &#xff08;2&#xff09;应该在只查询外面再…

Exynos4412 内核移植(二)—— 内核编译过程分析

内核的编译同样是从Makefile 来分析&#xff1a; 一、内核源码结构 Linux内核文件数目近2万&#xff0c;出去其他架构CPU的相关文件&#xff0c;他们分别位于顶层目录下的17个子目录&#xff0c;各个目录功能独立&#xff0c;下面是常用目录&#xff1a; arch:体系结构相关代码…

深入理解Java:注解(Annotation)

2019独角兽企业重金招聘Python工程师标准>>> 一、概述 1.什么是注解&#xff08;Annotation&#xff09; Annotation&#xff08;注解&#xff09;就是Java提供了一种元程序中的元素关联任何信息和着任何元数据&#xff08;metadata&#xff09;的途径和方法。Ann…

Exynos4412 内核移植(三)—— 内核启动过程分析

内核启动所用函数如下&#xff1a; 与移植U-Boot 的过程相似&#xff0c;在移植Linux 之前&#xff0c;先了解它的启动过程。Linux 的过程可以分为两部分&#xff1a;架构/开发板相关的引导过程、后续的通用启动过程。对于uImage、zImage ,它们首先进行自解压得到vmlinux &…

开源自己用python封装的一个Windows GUI(UI Automation)自动化工具,支持MFC,Windows Forms,WPF,Metro,Qt...

首先&#xff0c;大家可以看下这个链接 Windows GUI自动化测试技术的比较和展望 。 这篇文章介绍了Windows中GUI自动化的三种技术&#xff1a;Windows API, MSAA - Microsoft Active Accessibility, UIAutomation 用脚本语言AutoIT实现自动化就是第一种技术Windows API, 查找窗…

Exynos4412 Uboot 移植(六)—— 相关知识补充

Uboot版本&#xff1a;u-boot-2013.01 一、gd结构体的定义与使用 gd_t 和 bd_t 是u-boot中两个重要的数据结构&#xff0c;在初始化操作很多都要靠这两个数据结构来保存或传递。 gd_t 定义在/u-boot-2013.01/arch/arm/include/asm/global_data.h bd_t 定义在 ./include/asm-ar…

Exynos4412 Uboot 移植(五)—— Uboot 移植过程

Uboot 版本&#xff1a;u-boot-2013.01 开发板&#xff1a;FS_4412 平台&#xff08;Exynos4412,可以根据自己的板子修改&#xff0c;只要是4412的过程都是一样的&#xff09; 一、建立自己的平台 1、下载源码 我们可以在下面这个网站上下载最新的和以前任一版本的uboot ftp://…

Nagios 安装及常见错误

一、实验环境监控服务器&#xff08;nagios服务器--192.168.1.100&#xff09;CentOS5.4 nagios-3.2.1 nagios-plugins-1.4.14 nrpe-2.12被监控客户端&#xff08;linux客户端--192.168.1.200&#xff09;CentOS5.4 nagios-plugins-1.4.14 nrpe-2.12二、nrpe插件1、nrpe插…

Exynos4412 Uboot 移植(四)—— Uboot引导内核过程分析

bootloader 要想启动内核&#xff0c;可以直接跳到内核的第一个指令处&#xff0c;即内核的起始地址&#xff0c;这样便可以完成内核的启动工作了。但是要想启动内核还需要满足一些条件&#xff0c;如下所示&#xff1a; 1、cpu 寄存器设置 * R0 0 * R1 机器类型 id …

Exynos4412 Uboot 移植(三)—— Uboot添加自定义命令

Uboot添加自定义命令&#xff1a;uboot中的命令使用U_BOOT_CMD这个宏声明来注册进系统&#xff0c;链接脚本会把所有的cmd_tbl_t结构体放在相邻的地方。 UBoot版本&#xff1a;u-boot-2013.01 一、U-Boot命令的格式 即使是内核的启动&#xff0c;也是通过U-Boot命令来实现的。…

Exynos4412 Uboot 移植(二)—— Uboot 启动流程分析

uboot启动流程分析如下&#xff1a; 第一阶段&#xff1a; a -- 设置cpu工作模式为SVC模式 b -- 关闭中断&#xff0c;mmu,cache v -- 关看门狗 d -- 初始化内存&#xff0c;串口 e -- 设置栈 f -- 代码自搬移 g -- 清bss h -- 跳c 第二阶段 a -- 初始化外设&#xff0c;进入超…

Linux内核学习四库全书

http://blog.csdn.net/21aspnet/article/details/6585602 关于内核学习我建议不要上来就读内核而是先了解内核的构成和特性&#xff0c;然后通过思考发现疑问这时再去读内核源码。即先了解概貌在读局部细节。而且内核分成好多部分&#xff0c;不要只是按照顺序去读&#xff0c;…

Exynos4412 Uboot 移植(一)—— Uboot 编译流程分析

Uboot 所用版本 u-boot-2013.01 u-boot-2013.01 中有上千文件&#xff0c;要想了解对于某款开发板&#xff0c;使用哪些文件、哪些文件首先执行、可执行文件占用内存的情况&#xff0c;最好的方法就是阅读它的Makefile。 根据顶层Readme文件的说明&#xff1a; 可以知道如果使…

Exynos4412 所用内存 —— DDR2

一、SDRAM 二、DDR 三、DDR2 四、DDR2的配置

Exynos4412启动过程分析

学习Exynos4412启动流程前&#xff0c;我们先看看三星4412芯片启动框图&#xff1a; 我们从图中可以看到4412内部有64K的ROM和256K SRAM&#xff0c;在ROM中已经固化好了一段代码&#xff0c;当硬件上电后首先运行的就是这段代码&#xff0c;这段代码三星起名为BLO&#xff08;…

Exynos4412 所用外存 —— eMMC

Exynos4412所用外存不是原来的Nand Flash 与 Nor Flash&#xff0c;而是eMMC。eMMC是什么呢&#xff1f;和Nand Flash有什么区别呢&#xff1f; 一、eMMC概述 eMMC&#xff08;Embeded MultiMedia Card&#xff09;&#xff1a;它并非是一种全新尺寸的存储卡&#xff0c;而…