热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

我眼中的Linux设备树(五根节点)

五根节点一个最简单的设备树必须包含根节点,cpus节点,memory节点。根节点的名字及全路径都是“”,至少需要包含model和compatible两个属性。model属性

五 根节点

一个最简单的设备树必须包含根节点,cpus节点,memory节点。根节点的名字及全路径都是“/”,至少需要包含model和compatible两个属性。model属性我们在属性那节已经说过是用来描述产品型号的,类型为字符串,推荐的格式为“manufacturer,model-number”(非强制的)。根节点的model属性描述的是板子的型号或者芯片平台的型号,如:
model = "Atmel AT91SAM9G20 family SoC"
model = "Samsung SMDK5420 board based on EXYNOS5420"

从软件的层面讲model属性仅仅表示一个名字而已,没有更多的作用。compatible属性则不同,该属性决定软件如何匹配硬件对硬件进行初始化。属性那一节我们说过compatible属性的类型是字符串数组,按照范围从小到大的顺序排列,每个字符串表示一种匹配类型。根节点的compatible属性表示平台如何匹配,比如‘compatible = "samsung,smdk5420", "samsung,exynos5420", "samsung,exynos5"’,表示软件应该首先匹配'samsung,smdk5420',这个是一款开发板。如果无法匹配,再试着匹配"samsung,exynos5420",这个是一款芯片平台。如果还是无法匹配,还可以试着匹配 "samsung,exynos5",这是一个系列的芯片平台。这里说的匹配是指软件根据该信息找到对应的代码,如对应的初始化函数。

根节点表示的是整个板子或者芯片平台,所以在系统初始化比较早的时候就需要确认是什么平台,怎样初始化。对于Linux,是通过在start_kernel函数调用setup_arch函数实现的。不同的架构,setup_arch函数的实现不同,对于arm架构,setup_arch函数源代码位于arch/arm/kernel/setup.c中。以下是该函数的部分源代码(代码来自内核版本4.4-rc7的官方版本,本节后边所有代码都来自该版本)。

 935 void __init setup_arch(char **cmdline_p)
 936 {
 937     const struct machine_desc *mdesc;
 938
 939     setup_processor();
 940     mdesc = setup_machine_fdt(__atags_pointer);
 941     if (!mdesc)
 942         mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
 943     machine_desc = mdesc;
 944     machine_name = mdesc->name;


第940行setup_machine_fdt函数的输入是设备树(DTB)首地址,返回的mdesc是描述平台信息的结构体。还记得我们在概述那节说过启动程序如uboot把设备树读到内存中,然后在启动内核的同时将设备树首地址传给内核,此处__atags_pointer就是启动程序传给内核的设备树地址(此时内存中的设备树已经是DTB形式)。setup_machine_fdt中的fdt是flat device tree的缩写,fdt的意思是说设备树在内存中是在一块连续地址存储的,fdt和dtb说的都是同一个东西。setup_machine_tags是在设备树初始化失败的时候才调用的,所以不用管他。machine_desc和machine_name都是静态全局变量,用来保存指针方便后边引用的。为了更好的理解setup_machine_fdt具体实现了什么功能,我们首先看下machine_desc结构体。不同的架构,该结构体定义差别很大,arm架构源代码位于arch/arm/include/asm/mach/arch.h,复制如下:
 
 27 struct machine_desc {
 28     unsigned int        nr;     /* architecture number  */
 29     const char      *name;      /* architecture name    */
 30     unsigned long       atag_offset;    /* tagged list (relative) */
 31     const char *const   *dt_compat; /* array of device tree
 32                          * 'compatible' strings */
 33
 34     unsigned int        nr_irqs;    /* number of IRQs */
 35
 36 #ifdef CONFIG_ZONE_DMA
 37     phys_addr_t     dma_zone_size;  /* size of DMA-able area */
 38 #endif
 39
 40     unsigned int        video_start;    /* start of video RAM   */
 41     unsigned int        video_end;  /* end of video RAM */
 42
 43     unsigned char       reserve_lp0 :1; /* never has lp0    */
 44     unsigned char       reserve_lp1 :1; /* never has lp1    */
 45     unsigned char       reserve_lp2 :1; /* never has lp2    */
 46     enum reboot_mode    reboot_mode;    /* default restart mode */
 47     unsigned        l2c_aux_val;    /* L2 cache aux value   */
 48     unsigned        l2c_aux_mask;   /* L2 cache aux mask    */
 49     void            (*l2c_write_sec)(unsigned long, unsigned);
 50     const struct smp_operations *smp;   /* SMP operations   */
 51     bool            (*smp_init)(void);
 52     void            (*fixup)(struct tag *, char **);
 53     void            (*dt_fixup)(void);
 54     long long       (*pv_fixup)(void);
 55     void            (*reserve)(void);/* reserve mem blocks  */
 56     void            (*map_io)(void);/* IO mapping function  */
 57     void            (*init_early)(void);
 58     void            (*init_irq)(void);
 59     void            (*init_time)(void);
 60     void            (*init_machine)(void);
 61     void            (*init_late)(void);
 62 #ifdef CONFIG_MULTI_IRQ_HANDLER
 63     void            (*handle_irq)(struct pt_regs *);
 64 #endif
 65     void            (*restart)(enum reboot_mode, const char *);
 66 };
 67

从以上结构体的注释可以看出,该结构体包含了非常多的信息。注意第31行的dt_compat变量,该变量就是用来匹配设备树的compatible属性的。

setup_machine_fdt函数的实现也是架构相关的,arm架构源代码位于arch/arm/kernel/devtree.c,如下:

203 /**      
204  * setup_machine_fdt - Machine setup when an dtb was passed to the kernel
205  * @dt_phys: physical address of dt blob
206  *   
207  * If a dtb was passed to the kernel in r2, then use it to choose the
208  * correct machine_desc and to setup the system.
209  */
210 const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
211 {    
212     const struct machine_desc *mdesc, *mdesc_best = NULL;
213
214 #ifdef CONFIG_ARCH_MULTIPLATFORM
215     DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
216     MACHINE_END
217
218     mdesc_best = &__mach_desc_GENERIC_DT;
219 #endif
220
221     if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
222         return NULL;
223
224     mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
225
226     if (!mdesc) {
227         const char *prop;
228         int size;
229         unsigned long dt_root;
230
231         early_print("\nError: unrecognized/unsupported "
232                 "device tree compatible list:\n[ ");
233
234         dt_root = of_get_flat_dt_root();
235         prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
236         while (size > 0) {
237             early_print("'%s' ", prop);
238             size -= strlen(prop) + 1;
239             prop += strlen(prop) + 1;
240         }
241         early_print("]\n\n");
242
243         dump_machine_table(); /* does not return */
244     }
245
246     /* We really don't want to do this, but sometimes firmware provides buggy data */
247     if (mdesc->dt_fixup)
248         mdesc->dt_fixup();
249
250     early_init_dt_scan_nodes();
251
252     /* Change machine number to match the mdesc we're using */
253     __machine_arch_type = mdesc->nr;
254
255     return mdesc;
256 }

第221行检查fdt指针是否为空并且调用early_init_dt_verify函数,该函数代码位于drivers/of/fdt.c,这个函数算是of模块(还记得么?是open firmware的缩写)的第一个函数,如下:

1060
1061 bool __init early_init_dt_verify(void *params)
1062 {
1063     if (!params)
1064         return false;
1065
1066     /* check device tree validity */
1067     if (fdt_check_header(params))
1068         return false;
1069
1070     /* Setup flat device-tree pointer */
1071     initial_boot_params = params;
1072     of_fdt_crc32 = crc32_be(~0, initial_boot_params,
1073                 fdt_totalsize(initial_boot_params));
1074     return true;
1075 }

early_init_dt_verify首先检查fdt头部的合法性,然后设置fdt全局变量以及计算crc。这个initial_boot_params变量后边在访问设备树的时候还会用到。继续看前边第224行,of_flat_dt_match_machine函数算是of模块的第二个函数吧,在分析这个函数前,我们首先分析这个函数的第二个参数arch_get_next_mach,这是一个函数指针,arm架构的实现位于arch/arm/kernel/devtree.c,如下:

190 static const void * __init arch_get_next_mach(const char *const **match)
191 {
192     static const struct machine_desc *mdesc = __arch_info_begin;
193     const struct machine_desc *m = mdesc;
194
195     if (m >= __arch_info_end)
196         return NULL;
197
198     mdesc++;
199     *match = m->dt_compat;
200     return m;
201 }  

这个函数很简单,注意的是mdesc是静态局部变量,第一次调用指向__arch_info_begin,后边每次调用都mdesc++,如果超过了__arch_info_end就返回NULL。以上代码说明在__arch_info_begin和__arch_info_end两个地址之间存储着多个machine_desc变量(也可能是一个),该函数遍历这些变量,通过match参数返回所有machine_desc结构体的dt_compat变量指针。问题是__arch_info_begin和__arch_info_end地址是怎么来的呢?在arch/arm/kernel/vmlinux.lds.S连接脚本中定义了.arch.info.init段,__arch_info_begin和__arch_info_end地址分别是该段的首尾地址。

188     .init.arch.info : {
189         __arch_info_begin = .;
190         *(.arch.info.init)
191         __arch_info_end = .;
192     }

那么.init.arch.info段的内容怎么来的呢?这就要参考DT_MACHINE_START和MACHINE_END宏了,arm架构的定义在arch/arm/include/asm/mach/arch.h文件,如下所示:

 94 #define DT_MACHINE_START(_name, _namestr)       \
 95 static const struct machine_desc __mach_desc_##_name    \
 96  __used                         \
 97  __attribute__((__section__(".arch.info.init"))) = {    \
 98     .nr     = ~0,               \
 99     .name       = _namestr,
100
101 #endif

从该宏代码看出他定义了一个machine_desc类型的静态局部变量,该变量位于.arch.info.init段中。参考arch/arm/mach-exynos/exynos.c中如下代码,以下代码在.arch.info.init段定义了一个名字为__mach_desc_EXYNOS_DT,类型为machine_desc的静态局部变量,并且该变量的dt_compat字符串矩阵中有"samsung,exynos5420"的字符串。

277 static char const *const exynos_dt_compat[] __initcOnst= {
278     "samsung,exynos3",
279     "samsung,exynos3250",
280     "samsung,exynos4",
281     "samsung,exynos4210",
282     "samsung,exynos4212",
283     "samsung,exynos4412",
284     "samsung,exynos4415",
285     "samsung,exynos5",
286     "samsung,exynos5250",
287     "samsung,exynos5260",
288     "samsung,exynos5420",
289     "samsung,exynos5440",
290     NULL
291 };
 
319 DT_MACHINE_START(EXYNOS_DT, "SAMSUNG EXYNOS (Flattened Device Tree)")
320     /* Maintainer: Thomas Abraham */
321     /* Maintainer: Kukjin Kim */
322     .l2c_aux_val    = 0x3c400001,
323     .l2c_aux_mask   = 0xc20fffff,
324     .smp        = smp_ops(exynos_smp_ops),
325     .map_io     = exynos_init_io,
326     .init_early = exynos_firmware_init,
327     .init_irq   = exynos_init_irq,
328     .init_machine   = exynos_dt_machine_init,
329     .init_late  = exynos_init_late,
330     .dt_compat  = exynos_dt_compat,
331     .reserve    = exynos_reserve,
332     .dt_fixup   = exynos_dt_fixup,
333 MACHINE_END


我们已经知道了get_next_compat指针的具体实现了,现在继续看of_flat_dt_match_machine。从第732行开始的循环就是遍历.arch.info.init段中所有的dt_compat变量,然后通过of_flat_dt_match计算一个分数,并且寻找那个分数最小的。

 713 /**
 714  * of_flat_dt_match_machine - Iterate match tables to find matching machine.
 715  *
 716  * @default_match: A machine specific ptr to return in case of no match.
 717  * @get_next_compat: callback function to return next compatible match table.
 718  *
 719  * Iterate through machine match tables to find the best match for the machine
 720  * compatible string in the FDT.
 721  */
 722 const void * __init of_flat_dt_match_machine(const void *default_match,
 723         const void * (*get_next_compat)(const char * const**))
 724 {
 725     const void *data = NULL;
 726     const void *best_data = default_match;
 727     const char *const *compat;
 728     unsigned long dt_root;
 729     unsigned int best_score = ~1, score = 0;
 730        
 731     dt_root = of_get_flat_dt_root();
 732     while ((data = get_next_compat(&compat))) {
 733         score = of_flat_dt_match(dt_root, compat);
 734         if (score > 0 && score  735             best_data = data;
 736             best_score = score;
 737         }
 738     }
 ....
 759     return best_data;
 760 }
 761

of_flat_dt_match_machine的其余部分代码都是出错处理及打印,现在我们看of_flat_dt_match的实现,该函数仅仅是直接调用of_fdt_match而已,不同的是增加了initial_boot_params参数(还记得我们说过前边说过的这个变量的初始化吧,其实这就是内核中的一个简单封装而已)。

 685 /**
 686  * of_flat_dt_match - Return true if node matches a list of compatible values
 687  */
 688 int __init of_flat_dt_match(unsigned long node, const char *const *compat)
 689 {  
 690     return of_fdt_match(initial_boot_params, node, compat);
 691 }  

of_fdt_match函数从142行开始遍历compat数组的每一个字符串,然后通过of_fdt_is_compatible函数计算匹配度(以最小的数值作为最终的结果)。代码到这个地方已经很好理解了,compat中的数据来自内核的.arch.info.init段,这个段表示内核支持的平台,blob是设备树其实地址,通过node节点指定根节点的compatible属性,然后计算匹配度。还记得我们前边说过的compatible属性包含多个字符串,从前向后范围越来越大,优先匹配前边的,这个地方代码计算分数(score变量)就是这个目的。
 131 /**
 132  * of_fdt_match - Return true if node matches a list of compatible values
 133  */
 134 int of_fdt_match(const void *blob, unsigned long node,
 135                  const char *const *compat)
 136 {
 137     unsigned int tmp, score = 0;
 138
 139     if (!compat)
 140         return 0;
 141
 142     while (*compat) {
 143         tmp = of_fdt_is_compatible(blob, node, *compat);
 144         if (tmp && (score == 0 || (tmp  145             score = tmp;
 146         compat++;
 147     }
 148
 149     return score;
 150 }

继续看of_fdt_is_compatible函数的实现,第97行已经看到找该节点下的"compatible"属性了。

  80 /**
  81  * of_fdt_is_compatible - Return true if given node from the given blob has
  82  * compat in its compatible list
  83  * @blob: A device tree blob
  84  * @node: node to test
  85  * @compat: compatible string to compare with compatible list.
  86  *
  87  * On match, returns a non-zero value with smaller values returned for more
  88  * specific compatible values.
  89  */
  90 int of_fdt_is_compatible(const void *blob,
  91               unsigned long node, const char *compat)
  92 {
  93     const char *cp;
  94     int cplen;
  95     unsigned long l, score = 0;
  96
  97     cp = fdt_getprop(blob, node, "compatible", &cplen);
  98     if (cp == NULL)
  99         return 0;
 100     while (cplen > 0) {
 101         score++;
 102         if (of_compat_cmp(cp, compat, strlen(compat)) == 0)
 103             return score;
 104         l = strlen(cp) + 1;
 105         cp += l;
 106         cplen -= l;
 107     }
 108
 109     return 0;
 110 }

关于根节点的"compatible"属性我们就说到这,一句话总结下就是内核通过"compatible"属性找到对应的平台描述信息,按照范围从小到大尽量匹配范围最小的,如果匹配不到,那么说明内核不支持该平台,系统将在初始化的时候就出错。

根节点还可能包含的属性为#address-cells和#size-cells,规范中说明这两个属性是必须的,实际应用时是可选的,还记得属性那一节说这两个属性如果没有都是有默认值的,#address-cells默认值为2,#size-cells默认值为1。根节点下必须包含的子节点为cpus和memory,后边会说明cpus下边还有每个cpu的子节点,memory节点下边定义的就是memory的起始地址及大小,所以根节点的#address-cells和#size-cells属性实际上说明的就是从cpu角度看系统总线的地址长度和大小。

规范中还写根节点下边必须有一个epapr-version属性用来描述设备树的版本,实际上在linux中根本不用这个属性。


推荐阅读
  • 本书详细介绍了在最新Linux 4.0内核环境下进行Java与Linux设备驱动开发的全面指南。内容涵盖设备驱动的基本概念、开发环境的搭建、操作系统对设备驱动的影响以及具体开发步骤和技巧。通过丰富的实例和深入的技术解析,帮助读者掌握设备驱动开发的核心技术和最佳实践。 ... [详细]
  • 掌握DSP必备的56个核心问题,我已经将其收藏以备不时之需! ... [详细]
  • Python学习:环境配置与安装指南
    Python作为一种跨平台的编程语言,适用于Windows、Linux和macOS等多种操作系统。为了确保本地已成功安装Python,用户可以通过终端或命令行界面输入`python`或`python3`命令进行验证。此外,建议使用虚拟环境管理工具如`venv`或`conda`,以便更好地隔离不同项目依赖,提高开发效率。 ... [详细]
  • MySQL性能优化与调参指南【数据库管理】
    本文详细探讨了MySQL数据库的性能优化与参数调整技巧,旨在帮助数据库管理员和开发人员提升系统的运行效率。内容涵盖索引优化、查询优化、配置参数调整等方面,结合实际案例进行深入分析,提供实用的操作建议。此外,还介绍了常见的性能监控工具和方法,助力读者全面掌握MySQL性能优化的核心技能。 ... [详细]
  • 在 Linux 系统中,`/proc` 目录实现了一种特殊的文件系统,称为 proc 文件系统。与传统的文件系统不同,proc 文件系统主要用于提供内核和进程信息的动态视图,通过文件和目录的形式呈现。这些信息包括系统状态、进程细节以及各种内核参数,为系统管理员和开发者提供了强大的诊断和调试工具。此外,proc 文件系统还支持实时读取和修改某些内核参数,增强了系统的灵活性和可配置性。 ... [详细]
  • 本文深入探讨了 C# 中 `SqlCommand` 和 `SqlDataAdapter` 的核心差异及其应用场景。`SqlCommand` 主要用于执行单一的 SQL 命令,并通过 `DataReader` 获取结果,具有较高的执行效率,但灵活性较低。相比之下,`SqlDataAdapter` 则适用于复杂的数据操作,通过 `DataSet` 提供了更多的数据处理功能,如数据填充、更新和批量操作,更适合需要频繁数据交互的场景。 ... [详细]
  • 在CentOS上部署和配置FreeSWITCH
    在CentOS系统上部署和配置FreeSWITCH的过程涉及多个步骤。本文详细介绍了从源代码安装FreeSWITCH的方法,包括必要的依赖项安装、编译和配置过程。此外,还提供了常见的配置选项和故障排除技巧,帮助用户顺利完成部署并确保系统的稳定运行。 ... [详细]
  • 开发心得:深入探讨Servlet、Dubbo与MyBatis中的责任链模式应用
    开发心得:深入探讨Servlet、Dubbo与MyBatis中的责任链模式应用 ... [详细]
  • HBase客户端Table类中getRpcTimeout方法的应用与编程实例解析 ... [详细]
  • Android 图像色彩处理技术详解
    本文详细探讨了 Android 平台上的图像色彩处理技术,重点介绍了如何通过模仿美图秀秀的交互方式,利用 SeekBar 实现对图片颜色的精细调整。文章展示了具体的布局设计和代码实现,帮助开发者更好地理解和应用图像处理技术。 ... [详细]
  • 在Spring与Ibatis集成的环境中,通过Spring AOP配置事务管理至服务层。当在一个服务方法中引入自定义多线程时,发现事务管理功能失效。若不使用多线程,事务管理则能正常工作。本文深入分析了这一现象背后的潜在风险,并探讨了可能的解决方案,以确保事务一致性和线程安全。 ... [详细]
  • 在稀疏直接法视觉里程计中,通过优化特征点并采用基于光度误差最小化的灰度图像线性插值技术,提高了定位精度。该方法通过对空间点的非齐次和齐次表示进行处理,利用RGB-D传感器获取的3D坐标信息,在两帧图像之间实现精确匹配,有效减少了光度误差,提升了系统的鲁棒性和稳定性。 ... [详细]
  • 本文探讨了在Android应用中实现动态滚动文本显示控件的优化方法。通过详细分析焦点管理机制,特别是通过设置返回值为`true`来确保焦点不会被其他控件抢占,从而提升滚动文本的流畅性和用户体验。具体实现中,对`MarqueeText.java`进行了代码层面的优化,增强了控件的稳定性和兼容性。 ... [详细]
  • 本文提供了 RabbitMQ 3.7 的快速上手指南,详细介绍了环境搭建、生产者和消费者的配置与使用。通过官方教程的指引,读者可以轻松完成初步测试和实践,快速掌握 RabbitMQ 的核心功能和基本操作。 ... [详细]
  • SpringBoot启动脚本详解:BAT文件应用与基础入门指南(SpringBoot系列第1篇)
    如果你还在为SSM框架的复杂搭建过程和繁琐的配置文件而烦恼,那么SpringBoot将是你的一大福音。作为SpringBoot系列的第一篇文章,本文详细介绍了如何使用BAT文件来启动SpringBoot应用,并提供了基础入门指南,帮助开发者快速上手,简化开发流程。 ... [详细]
author-avatar
军长长军765
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有