//Based on Linux v3.14 source code
Linux设备树机制(Device Tree)
一、描述
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是一种描述硬件的数据结构,由一系列被命名的结点(node)和属性(property)组成,而结点本身可包含子结点。所谓属性,其实就是成对出现的name和value。在Device Tree中,可描述的信息包括(原先这些信息大多被hard code到kernel中):CPU的数量和类别,内存基地址和大小,总线和桥,外设连接,中断控制器和中断使用情况,GPIO控制器和GPIO使用情况,Clock控制器和Clock使用情况。
通常由.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)。设备树在内存中的存储布局图。
------------------------------
base -> | struct boot_param_header |
------------------------------
| (alignment gap) (*) |
------------------------------
| memory reserve map |
------------------------------
| (alignment gap) |
------------------------------
| |
| device-tree structure |
| |
------------------------------
| (alignment gap) |
------------------------------
| |
| device-tree strings |
| |
-----> ------------------------------
|
|
--- (base + totalsize)
1.1 头(header)
头主要描述设备树的一些基本信息,例如设备树大小,结构块偏移地址,字符串块偏移地址等。偏移地址是相对于设备树头的起始地址计算的。
struct boot_param_header {
__be32 magic; //设备树魔数,固定为0xd00dfeed
__be32 totalsize; //整个设备树的大小
__be32 off_dt_struct; //保存结构块在整个设备树中的偏移
__be32 off_dt_strings; //保存的字符串块在设备树中的偏移
__be32 off_mem_rsvmap; //保留内存区,该区保留了不能被内核动态分配的内存空间
__be32 version; //设备树版本
__be32 last_comp_version; //向下兼容版本号
__be32 boot_cpuid_phys; //为在多核处理器中用于启动的主cpu的物理id
__be32 dt_strings_size; //字符串块大小
__be32 dt_struct_size; //结构块大小
};
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来获取。
struct machine_desc {
unsigned int nr; /* architecture number */
const char *name; /* architecture name */
unsigned long atag_offset; /* tagged list (relative) */
const char *const *dt_compat; /* array of device tree* 'compatible' strings */
unsigned int nr_irqs; /* number of IRQs */
#ifdef CONFIG_ZONE_DMA
phys_addr_t dma_zone_size; /* size of DMA-able area */
#endif
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned char reserve_lp0 :1; /* never has lp0 */
unsigned char reserve_lp1 :1; /* never has lp1 */
unsigned char reserve_lp2 :1; /* never has lp2 */
enum reboot_mode reboot_mode; /* default restart mode */
struct smp_operations *smp; /* SMP operations */
bool (*smp_init)(void);
void (*fixup)(struct tag *, char **,struct meminfo *);
void (*init_meminfo)(void);
void (*reserve)(void);/* reserve mem blocks */
void (*map_io)(void);/* IO mapping function */
void (*init_early)(void);
void (*init_irq)(void);
void (*init_time)(void);
void (*init_machine)(void);
void (*init_late)(void);
#ifdef CONFIG_MULTI_IRQ_HANDLER
void (*handle_irq)(struct pt_regs *);
#endif
void (*restart)(enum reboot_mode, const char *);
};
1.6 设备节点结构体
struct device_node {
const char *name; //设备name
const char *type; //设备类型
phandle phandle;
const char *full_name; //设备全称,包括父设备名
struct property *properties; //设备属性链表
struct property *deadprops; //removed properties
struct device_node *parent; //指向父节点
struct device_node *child; //指向子节点
struct device_node *sibling; //指向兄弟节点
struct device_node *next; //相同设备类型的下一个节点
struct device_node *allnext; //next in list of all nodes
struct proc_dir_entry *pde; //该节点对应的proc
struct kref kref;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
1.7 属性结构体
struct property {
char *name; //属性名
int length; //属性值长度
void *value; //属性值
struct property *next; //指向下一个属性
unsigned long _flags; //标志
unsigned int unique_id;
};
三、设备树初始化及解析
分析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链表信息来初始化内核其他子系统、设备等。
//kernel 初始化的代码(init/main.c)
asmlinkage void __init start_kernel(void)
{
...
//这个setup_arch就是各个架构自己的设置函数,哪个参与了编译就调用哪个,arm架构应当是arch/arm/kernel/setup.c中的 setup_arch。
setup_arch(&command_line);
...
}
void __init setup_arch(char **cmdline_p)
{
const struct machine_desc *mdesc;
setup_processor();
//setup_machine_fdt函数获取内核前期初始化所需的bootargs,cmd_line等系统引导参数
mdesc = setup_machine_fdt(__atags_pointer);//__atags_pointer是bootloader传递参数的物理地址
if (!mdesc)
mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
machine_desc = mdesc;
machine_name = mdesc->name;
if (mdesc->reboot_mode != REBOOT_HARD)
reboot_mode = mdesc->reboot_mode;
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;
parse_early_param();
sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL);
early_paging_init(mdesc, lookup_processor_type(read_cpuid_id()));
setup_dma_zone(mdesc);
sanity_check_meminfo();
arm_memblock_init(&meminfo, mdesc);
paging_init(mdesc);
request_standard_resources(mdesc);
if (mdesc->restart)
arm_pm_restart = mdesc->restart;
//解析设备树
unflatten_device_tree();
......
}
(一)函数获取内核前期初始化所需的bootargs,cmd_line等系统引导参数
1. setup_machine_fdt()函数获取内核前期初始化所需的bootargs,cmd_line等系统引导参数。
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
const struct machine_desc *mdesc, *mdesc_best = NULL;
#ifdef CONFIG_ARCH_MULTIPLATFORM
DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
MACHINE_END
mdesc_best = &__mach_desc_GENERIC_DT;
#endif
//bootloader传递参数的物理地址不为空,并将物理地址转化为虚拟地址,
//通过函数early_init_dt_scan从设备树中读出bootargs,cmd_line等系统引导参数。
if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys)))
return NULL;
//根据设备树中根节点属性"compatible"的属性描述,找到系统中定义的最匹配的machine_desc结构,该结构控制系统体系架构相关部分的初始化
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
if (!mdesc) {
const char *prop;
long size;
unsigned long dt_root;
early_print(" Error: unrecognized/unsupported ""device tree compatible list: [ ");
//找到设备树的根节点,dt_root指向根节点的属性地址处
dt_root = of_get_flat_dt_root();
//读出根节点的"compatible"属性的属性值
prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
//将根节点的"compatible"属性的属性值打印出来
while (size > 0) {
early_print("'%s' ", prop);
size -= strlen(prop) + 1;
prop += strlen(prop) + 1;
}
early_print("] ");
dump_machine_table(); /* does not return */
}
//Change machine number to match the mdesc we're using
__machine_arch_type = mdesc->nr;
return mdesc;
}
struct boot_param_header *initial_boot_params;
bool __init early_init_dt_scan(void *params)
{
if (!params)
return false;
//参数params是bootloader传递参数的物理地址转化为的虚拟地址,保存设备树起始地址
initial_boot_params = params;
//验证设备树的magic
if (be32_to_cpu(initial_boot_params->magic) != OF_DT_HEADER) {
initial_boot_params = NULL;
return false;
}
//从设备树中读取chosen节点的信息,包括命令行boot_command_line,initrd location及size
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
//得到根节点的{size,address}-cells信息
of_scan_flat_dt(early_init_dt_scan_root, NULL);
//读出设备树的系统内存设置
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
return true;
}
int __init of_scan_flat_dt(int (*it)(unsigned long node,const char *uname, int depth,void *data),void *data)
{
//找到设备树中结构块的地址
unsigned long p = ((unsigned long)initial_boot_params) + be32_to_cpu(initial_boot_params->off_dt_struct);
int rc = 0;
int depth = -1;
do {
//获得节点起始标志,即OF_DT_BEGIN_NODE,OF_DT_PROP等
u32 tag = be32_to_cpup((__be32 *)p);
const char *pathp;
p += 4;//跳过节点起始标志
//如果是OF_DT_END_NODE标志,表示该节点结束,继续下一结点
if (tag == OF_DT_END_NODE) {
depth--;
continue;
}
//OF_DT_NOP标志代表空节点
if (tag == OF_DT_NOP)
continue;
//OF_DT_END标志整个结构块结束
if (tag == OF_DT_END)
break;
//OF_DT_PROP标示属性,Property:属性值的字节长度、属性名称在字符串块中的偏移量、属性值和填充。
if (tag == OF_DT_PROP) {
//属性值的字节大小
u32 sz = be32_to_cpup((__be32 *)p);
p += 8;//跳过属性值的字节长度、属性名称在字符串块中的偏移量
if (be32_to_cpu(initial_boot_params->version) < 0x10)
p = ALIGN(p, sz >= 8 ? 8 : 4);
//跳过该属性值的大小
p += sz;
//地址对齐
p = ALIGN(p, 4);
//表示一个属性节点遍历完成,因为这里并不是寻找属性节点,而是找OF_DT_BEGIN_NODE开始的节点
continue;
}
//若都不是以上节点类型,也不是节点开始标示(OF_DT_BEGIN_NODE),则出错返回
if (tag != OF_DT_BEGIN_NODE) {
pr_err("Invalid tag %x in flat device tree! ", tag);
return -EINVAL;
}
//执行到这里,标示tag=OF_DT_BEGIN_NODE,表示一个节点的开始,探索深度加1
depth++;
//节点路径或者节点名
pathp = (char *)p;
//节点地址四字节对齐,然后p指向节点属性地址
p = ALIGN(p + strlen(pathp) + 1, 4);
//如果是节点路径,则返回路径名的最后一段,假如为/root/my_root,则返回my_root,即获得节点名
if (*pathp == '/')
pathp = kbasename(pathp);
//调用相应的节点处理函数,p指向节点属性地址
rc = it(p, pathp, depth, data);
if (rc != 0)
break;
} while (1);
return rc;
}
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>; //节点属性
//};
int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,int depth, void *data)
{
unsigned long l;
char *p;
pr_debug("search "chosen", depth: %d, uname: %s ", depth, uname);
//depth深度要为1,表示在根节点下(一般根节点/的depth为0)
//data表示系统启动命令行boot_command_line要分配空间
//检查节点名是否为chosen节点
if (depth != 1 || !data || (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
return 0;
//从设备树的chosen节点中读出initrd的起始、结束地址
early_init_dt_check_for_initrd(node);
//设备树的chosen节点中读取bootargs属性的属性值,并拷贝给boot_command_line
p = of_get_flat_dt_prop(node, "bootargs", &l);
if (p != NULL && l > 0)
strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE));
pr_debug("Command line is: %s ", (char*)data);
return 1;
}
static void __init early_init_dt_check_for_initrd(unsigned long node)
{
u64 start, end;
unsigned long len;
__be32 *prop;
pr_debug("Looking for initrd properties... ");
//返回该chosen节点中属性名为"linux,initrd-start"的地址
prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);
if (!prop)
return;
//从该地址读出initrd-start的地址
start = of_read_number(prop, len/4);
//返回该chosen节点中属性名为"linux,initrd-end"的地址
prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
if (!prop)
return;
//从该地址读出initrd-end的地址
end = of_read_number(prop, len/4);
//将读出的地址赋值给全局变量initrd_start和initrd_end,用于跟文件系统的挂载
initrd_start = (unsigned long)__va(start);
initrd_end = (unsigned long)__va(end);
initrd_below_start_ok = 1;
pr_debug("initrd_start=0x%llx initrd_end=0x%llx ",(unsigned long long)start, (unsigned long long)end);
}
void *__init of_get_flat_dt_prop(unsigned long node, const char *name,unsigned long *size)
{
return of_fdt_get_property(initial_boot_params, node, name, size);
}
void *of_fdt_get_property(struct boot_param_header *blob,unsigned long node, const char *name,unsigned long *size)
{
//p指向该chosen节点的节点属性地址
unsigned long p = node;
//因为一个节点中可能包含多个属性,所以这里遍历chosen节点中的所有属性,找到属性名为name的属性
do {
//取得该节点属性的起始标志OF_DT_PROP
u32 tag = be32_to_cpup((__be32 *)p);
u32 sz, noff;
const char *nstr;
p += 4;//跳过节点属性的起始标志
//空节点则继续
if (tag == OF_DT_NOP)
continue;
//非属性标志则返回NULL
if (tag != OF_DT_PROP)
return NULL;
//运行到这里表示为属性OF_DT_PROP
//取得该节点属性的的属性值size
sz = be32_to_cpup((__be32 *)p);
//取得该属性名的在字符串块中的偏移值
noff = be32_to_cpup((__be32 *)(p + 4));
p += 8;
//跳过对齐填充字段
if (be32_to_cpu(blob->version) < 0x10)
p = ALIGN(p, sz >= 8 ? 8 : 4);
//在字符串块取出该属性的名称
nstr = of_fdt_get_string(blob, noff);
if (nstr == NULL) {
pr_warning("Can't find property index name ! ");
return NULL;
}
//若名称一致,表示找到我们要找的属性
if (strcmp(name, nstr) == 0) {
if (size)
*size = sz;//返回该属性的属性值size
//返回该属性值所在的地址
return (void *)p;
}
//否则继续下一个属性
p += sz;
p = ALIGN(p, 4);
} while (1);
}
char *of_fdt_get_string(struct boot_param_header *blob, u32 offset)
{
//从设备树的字符串块的offset处读出name
return ((char *)blob) + be32_to_cpu(blob->off_dt_strings) + offset;
}
static inline u64 of_read_number(const __be32 *cell, int size)
{
u64 r = 0;
//读出属性值,属性值大小为size
while (size--)
r = (r << 32) | be32_to_cpu(*(cell++));
return r;
}
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"属性
int __init early_init_dt_scan_root(unsigned long node, const char *uname,int depth, void *data)
{
__be32 *prop;
//根节点的探索深度depth一定为0,否则不是根节点"/",node为根节点的属性地址
if (depth != 0)
return 0;
dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;
//返回根节点节点属性名为"#size-cells"的地址
prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
if (prop)
dt_root_size_cells = be32_to_cpup(prop);//从该属性获得root size
pr_debug("dt_root_size_cells = %x ", dt_root_size_cells);
//返回根节点节点属性名为"#address-cells"的地址
prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
if (prop)
dt_root_addr_cells = be32_to_cpup(prop);//从该属性获得root address
pr_debug("dt_root_addr_cells = %x ", dt_root_addr_cells);
return 1;
}
1.3 memory节点
//memory节点用于描述目标板上物理内存范围,一般称作/memory节点,可以有一个或多个。当有多个节点时,需要后跟单元地址予以区分;只有一个单元地址时,可以不写单元地址,默认为0。此节点包含板上物理内存的属性,一般要指定device_type(固定为"memory")和reg属性。其中reg的属性值以<起始地址 空间大小>的形式给出,如下示例中目标板内存起始地址为0x80000000,大小为0x20000000字节。
//memory {
// device_type = "memory";
// reg = <0x80000000 0x20000000>;
//};
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,int depth, void *data)
{
//获取该节点中属性"device_type"的属性值
char *type = of_get_flat_dt_prop(node, "device_type", NULL);
__be32 *reg, *endp;
unsigned long l;
//检查"device_type"的属性值,确定该节点是否为memory节点
if (type == NULL) {
if (depth != 1 || strcmp(uname, "memory@0") != 0)
return 0;
} else if (strcmp(type, "memory") != 0)
return 0;
//从该memory节点中获取属性"linux,usable-memory"的属性值,及属性值大小l
reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
if (reg == NULL)
reg = of_get_flat_dt_prop(node, "reg", &l);
if (reg == NULL)
return 0;
//reg为属性值的起始地址,endp为结束地址
endp = reg + (l / sizeof(__be32));
pr_debug("memory scan node %s, reg size %ld, data: %x %x %x %x, ",
uname, l, reg[0], reg[1], reg[2], reg[3]);
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
u64 base, size;
//读出物理内存的起始地址以及size
base = dt_mem_next_cell(dt_root_addr_cells, ®);
size = dt_mem_next_cell(dt_root_size_cells, ®);
if (size == 0)
continue;
pr_debug(" - %llx , %llx ", (unsigned long long)base,(unsigned long long)size);
//添加物理内存到memblock中进行管理,这里不再展开
early_init_dt_add_memory_arch(base, size);
}
return 0;
}
2. 通过比较根节点属性compatible值指明和目标板为同一系列的兼容的开发板名称
//compatible制定系统的名称。它包含","格式的字符串。准确地确定器件型号是非常重要的,并且我们需要包含厂商的名字来避免名字空间冲突。因为操作系统会使用compatible这个值来决定怎样在这个机器上运行,所以在这个属性中放入正确的值是非常重要的。
const void * __init of_flat_dt_match_machine(const void *default_match,
const void * (*get_next_compat)(const char * const**))
{
const void *data = NULL;
const void *best_data = default_match;
const char *const *compat;
unsigned long dt_root;
unsigned int best_score = ~1, score = 0;
//读出设备树的根节点,dt_root指向根节点的属性地址处
dt_root = of_get_flat_dt_root();
//调用arch_get_next_mach,遍历该系统中的所有machine_desc结构,返回给data,并且返回该结构的compatible
while ((data = get_next_compat(&compat))) {
//将系统中的所有machine_desc结构的compatible字符串与设备树根节点的compatible属性进行match
score = of_flat_dt_match(dt_root, compat);
//返回与根节点属性compatible属性值最匹配的machine_desc结构
if (score > 0 && score < best_score) {
best_data = data;
best_score = score;
}
}
if (!best_data) {
const char *prop;
long size;
pr_err(" unrecognized device tree list: [ ");
prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
if (prop) {
while (size > 0) {
printk("'%s' ", prop);
size -= strlen(prop) + 1;
prop += strlen(prop) + 1;
}
}
printk("] ");
return NULL;
}
pr_info("Machine model: %s ", of_flat_dt_get_machine_name());
return best_data;
}
//查找设备树的根节点
unsigned long __init of_get_flat_dt_root(void)
{
//找到设备树的设备块起始地址
unsigned long p = ((unsigned long)initial_boot_params) +
be32_to_cpu(initial_boot_params->off_dt_struct);
//跳过空节点
while (be32_to_cpup((__be32 *)p) == OF_DT_NOP)
p += 4;
BUG_ON(be32_to_cpup((__be32 *)p) != OF_DT_BEGIN_NODE);
p += 4;
//第一个节点就是根节点,p指向根节点的属性地址处
return ALIGN(p + strlen((char *)p) + 1, 4);
}
//arch/arm/kernel/devtree.c
__arch_info_begin 和 __arch_info_end是在 arch/arm/kernel/vmlinux.lds.S中:
00034: __arch_info_begin = .;
00035: *(.arch.info.init)
00036: __arch_info_end = .;
这里是声明了两个变量:__arch_info_begin 和 __arch_info_end,其中等号后面的"."是location counter。在__arch_info_begin 的位置上,放置所有文件中的 ".arch.info.init" 段的内容,然后紧接着是 __arch_info_end 的位置.".arch.info.init" 段中定义了设备的machine_desc结构。
//这里就是取出一个machine_desc结构
static const void * __init arch_get_next_mach(const char *const **match)
{
static const struct machine_desc *mdesc = __arch_info_begin;
const struct machine_desc *m = mdesc;
if (m >= __arch_info_end)
return NULL;
mdesc++;//指针后移,确保下次取出下一个machine_desc结构
*match = m->dt_compat;
return m;//返回当前的machine_desc结构
}
//与设备树根节点进行match
int __init of_flat_dt_match(unsigned long node, const char *const *compat)
{
//initial_boot_params指向设备树起始地址
//node指向根节点的属性地址
//compat为系统中machine_desc结构的compatible字符串
return of_fdt_match(initial_boot_params, node, compat);
}
int of_fdt_match(struct boot_param_header *blob, unsigned long node,const char *const *compat)
{
unsigned int tmp, score = 0;
if (!compat)
return 0;
//遍历compatible字符串数组
while (*compat) {
//返回compatible的匹配值
tmp = of_fdt_is_compatible(blob, node, *compat);
if (tmp && (score == 0 || (tmp < score)))
score = tmp;//返回最大的匹配值
compat++;//下一个字符串
}
return score;
}
int of_fdt_is_compatible(struct boot_param_header *blob,unsigned long node, const char *compat)
{
const char *cp;
unsigned long cplen, l, score = 0;
//从根节点中读出属性"compatible"的属性值
cp = of_fdt_get_property(blob, node, "compatible", &cplen);
if (cp == NULL)
return 0;
//比较compatible的指定的属性字符串的一致性
while (cplen > 0) {
score++;
if (of_compat_cmp(cp, compat, strlen(compat)) == 0)
return score;
l = strlen(cp) + 1;
cp += l;
cplen -= l;
}
return 0;
}
(二)、解析设备树
//unflatten_device_tree()函数来解析dtb文件,构建一个由device_node结构连接而成的单项链表,并使用全局变量of_allnodes指针来保存这个链表的头指针。内核调用OF提供的API函数获取of_allnodes链表信息来初始化内核其他子系统、设备。
void __init unflatten_device_tree(void)
{
//解析设备树,将所有的设备节点链入全局链表 of_allnodes中
__unflatten_device_tree(initial_boot_params, &of_allnodes,early_init_dt_alloc_memory_arch);
//设置内核输出终端,以及遍历“/aliases”节点下的所有的属性,挂入相应链表
of_alias_scan(early_init_dt_alloc_memory_arch);
}
static void __unflatten_device_tree(struct boot_param_header *blob,
struct device_node **mynodes,
void * (*dt_alloc)(u64 size, u64 align))
{
unsigned long size;
void *start, *mem;
struct device_node **allnextp = mynodes;
pr_debug(" -> unflatten_device_tree() ");
if (!blob) {
pr_debug("No device tree pointer ");
return;
}
pr_debug("Unflattening device tree: ");
pr_debug("magic: %08x ", be32_to_cpu(blob->magic));
pr_debug("size: %08x ", be32_to_cpu(blob->totalsize));
pr_debug("version: %08x ", be32_to_cpu(blob->version));
//检查设备树magic
if (be32_to_cpu(blob->magic) != OF_DT_HEADER) {
pr_err("Invalid device tree blob header ");
return;
}
//找到设备树的设备节点起始地址
start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct);
//第一次调用mem传0,allnextpp传NULL,实际上是为了计算整个设备树所要的空间
size = (unsigned long)unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);
size = ALIGN(size, 4);//4字节对齐
pr_debug(" size is %lx, allocating... ", size);
//调用early_init_dt_alloc_memory_arch函数,为设备树分配内存空间
mem = dt_alloc(size + 4, __alignof__(struct device_node));
memset(mem, 0, size);
//设备树结束处赋值0xdeadbeef,为了后边检查是否有数据溢出
*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
pr_debug(" unflattening %p... ", mem);
//再次获取设备树的设备节点起始地址
start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct);
//mem为设备树分配的内存空间,allnextp指向全局变量of_allnodes,生成整个设备树
unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0);
if (be32_to_cpup(start) != OF_DT_END)
pr_warning("Weird tag at end of tree: %08x ", be32_to_cpup(start));
if (be32_to_cpup(mem + size) != 0xdeadbeef)
pr_warning("End of tree marker overwritten: %08x ",be32_to_cpup(mem + size));
*allnextp = NULL;
pr_debug(" <- unflatten_device_tree() ");
}
static void * unflatten_dt_node(struct boot_param_header *blob,
void *mem,void **p,
struct device_node *dad,
struct device_node ***allnextpp,
unsigned long fpsize)
{
struct device_node *np;
struct property *pp, **prev_pp = NULL;
char *pathp;
u32 tag;
unsigned int l, allocl;
int has_name = 0;
int new_format = 0;
//*p指向设备树的设备块起始地址
tag = be32_to_cpup(*p);
//每个有孩子的设备节点,其tag一定是OF_DT_BEGIN_NODE
if (tag != OF_DT_BEGIN_NODE) {
pr_err("Weird tag at start of node: %x ", tag);
return mem;
}
*p += 4;//地址+4,跳过tag,这样指向节点的名称或者节点路径名
pathp = *p;//获得节点名或者节点路径名
l = allocl = strlen(pathp) + 1;//该节点名称的长度
*p = PTR_ALIGN(*p + l, 4);//地址对齐后,*p指向该节点属性的地址
//如果是节点名则进入,若是节点路径名则(*pathp) == '/'
if ((*pathp) != '/') {
new_format = 1;
if (fpsize == 0) {//fpsize=0
fpsize = 1;
allocl = 2;
l = 1;
*pathp = '