内核3.x版本之后设备树(device tree)机制

来源:互联网 发布:淘宝 厂家投诉 编辑:程序博客网 时间:2024/05/16 15:50


转自:http://blog.csdn.net/wdsfup/article/details/50068305


一、DTB相关结构

本节讲下.dts编译生成的dtb文件,其布局结构。

DTB由三部分组成:头(Header)、结构块(device-tree structure)、字符串块(string block)。下面将详细介绍这三部分的内容。

㈠Header

在\kernel\include\linux\of_fdt.h文件中有相关定义


device-tree structure


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

在结构块中,以宏OF_DT_BEGIN_NODE标志一个节点的开始,以宏OF_DT_END_NODE标识一个节点的结束,整个结构块以宏OF_DT_END (0x00000009)结束。在\kernel\include\linux\of_fdt.h中有相关定义,我们把这些宏称之为token。

(1)FDT_BEGIN_NODE (0x00000001)。该token描述了一个node的开始位置,紧挨着该token的就是node name(包括unit address)

(2)FDT_END_NODE (0x00000002)。该token描述了一个node的结束位置。

(3)FDT_PROP (0x00000003)。该token描述了一个property的开始位置,该token之后是两个u32的数据,分别是length和name offset。length表示该property value data的size。name offset表示该属性字符串在device tree strings block的偏移值。length和name offset之后就是长度为length具体的属性值数据。

(4)FDT_NOP (0x00000004)。

(5)FDT_END (0x00000009)。该token标识了一个DTB的结束位置。

一个节点的结构如下:

(1)节点开始标志:一般为OF_DT_BEGIN_NODE(0x00000001)。

(2)节点路径或者节点的单元名(version<3以节点路径表示,version>=0x10以节点单元名表示)

(3)填充字段(对齐到四字节)

(4)节点属性。每个属性以宏OF_DT_PROP(0x00000003)开始,后面依次为属性值的字节长度(4字节)、属性名称在字符串块中的偏移量(4字节)、属性值和填充(对齐到四字节)。

(5)如果存在子节点,则定义子节点。

(6)节点结束标志OF_DT_END_NODE(0x00000002)。

㈢字符串块

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

㈣ memory reserve map

这个区域包括了若干的reserve memory描述符。每个reserve memory描述符是由address和size组成。其中address和size都是用U64来描述。

有些系统,我们也许会保留一些memory有特殊用途(例如DTB或者initrd image),或者在有些DSP+ARM的SOC platform上,有些memory被保留用于ARM和DSP进行信息交互。这些保留内存不会进入内存管理系统。

二、解析DTB的函数及相关数据结构

㈠machine_desc结构

内核将机器信息记录为machine_desc结构体(该定义在/arch/arm/include/asm/mach/arch.h),并保存在_arch_info_begin到_arch_info_end之间(_arch_info_begin,_arch_info_end为虚拟地址,是编译内核时指定的,此时mmu还未进行初始化。它其实通过汇编完成地址偏移操作)

machine_desc结构体用宏MACHINE_START进行定义,一般在/arch/arm/子目录,与板级相关的文件中进行成员函数及变量的赋值。由linker将machine_desc聚集在.arch.info.init节区形成列表。

bootloader引导内核时,ARM寄存器r2会将.dtb的首地址传给内核,内核根据该地址,解析.dtb中根节点的compatible属性,将该属性与内核中预先定义machine_desc结构体的dt_compat成员做匹配,得到最匹配的一个machine_desc。

在代码中,内核通过在start_kernel->setup_arch中调用setup_machine_fdt来实现上述功能,该函数的具体实现可参见/arch/arm/kernel/devtree.c。 

㈡设备节点结构体

1.

记录节点信息的结构体。.dtb经过解析之后将以device_node列表的形式存储节点信息。

㈢属性结构体

device_node结构体中的成员结构体,用于描述节点属性信息。

㈣ uboot下的相关结构体

首先我们看下uboot用于记录os、initrd、fdt信息的数据结构bootm_headers,其定义在/include/image.h中,这边截取了其中与dtb相关的一小部分。

fit_hdr_fdt指向DTB设备树镜像的头。

lmb为uboot下的一种内存管理机制,全称为logical memory blocks。用于管理镜像的内存。lmb所记录的内存信息最终会传递给kernel。这里对lmb不做展开描述。在/include/lmb.h和/lib/lmb.c中有对lmb的接口和定义的具体描述。有兴趣的读者可以看下,所包含的代码量不多。

三、DTB加载及解析过程

先从uboot里的do_bootm出发,根据之前描述,DTB在内存中的地址通过bootm命令进行传递。在bootm中,它会根据所传进来的DTB地址,对DTB所在内存做一系列操作,为内核解析DTB提供保证。上图为对应的函数调用关系图。

在do_bootm中,主要调用函数为do_bootm_states,第四个参数为bootm所要处理的阶段和状态。 

在do_bootm_states中,bootm_start会对lmb进行初始化操作,lmb所管理的物理内存块有三种方式获取。起始地址,优先级从上往下:

1. 环境变量“bootm_low”

2. 宏CONFIG_SYS_SDRAM_BASE(在tegra124中为0x80000000)

3. gd->bd->bi_dram[0].start

大小:

1. 环境变量“bootm_size”

2. gd->bd->bi_dram[0].size

经过初始化之后,这块内存就归lmb所管辖。接着,调用bootm_find_os进行kernel镜像的相关操作,这里不具体阐述。

还记得之前讲过bootm的三个参数么,第一个参数内核地址已经被bootm_find_os处理,而接下来的两个参数会在bootm_find_other中执行操作。

首先,bootm_find_other根据第二个参数找到ramdisk的地址,得到ramdisk的镜像;然后根据第三个参数得到DTB镜像,同检查kernel和ramdisk镜像一样,检查DTB镜像也会进行一系列的校验工作,如果校验错误,将无法正常启动内核。另外,uboot在确认DTB镜像无误之后,会将该地址保存在环境变量“fdtaddr”中。

接着,uboot会把DTB镜像reload一次,使得DTB镜像所在的物理内存归lmb所管理:①boot_fdt_add_mem_rsv_regions会将原先的内存DTB镜像所在的内存置为reserve,保证该段内存不会被其他非法使用,保证接下来的reload数据是正确的;②boot_relocate_fdt会在bootmap区域中申请一块未被使用的内存,接着将DTB镜像内容复制到这块区域(即归lmb所管理的区域)

注:若环境变量中,指定“fdt_high”参数,则会根据该值,调用lmb_alloc_base函数来分配DTB镜像reload的地址空间。若分配失败,则会停止bootm操作。因而,不建议设置fdt_high参数。

接下来,do_bootm会根据内核的类型调用对应的启动函数。与linux对应的是do_bootm_linux。

① boot_prep_linux

为启动后的kernel准备参数

② boot_jump_linux

以上是boot_jump_linux的片段代码,可以看出:若使用DTB,则原先用来存储ATAG的寄存器R2,将会用来存储.dtb镜像地址。

boot_jump_linux最后将调用kernel_entry,将.dtb镜像地址传给内核。


下面我们来看下内核的处理部分:

在arch/arm/kernel/head.S中,有这样一段:

_vet_atags定义在/arch/arm/kernel/head-common.S中,它主要对DTB镜像做了一个简单的校验。

真正解析处理dbt的开始部分,是setup_arch->setup_machine_fdt。这部分的处理在第五部分的machine_mdesc中有提及。

如图,是setup_machine_fdt中的解析过程。

解析chosen节点将对boot_command_line进行初始化。

解析根节点的{size,address}将对dt_root_size_cells,dt_root_addr_cells进行初始化。为之后解析memory等其他节点提供依据。

解析memory节点,将会把节点中描述的内存,加入memory的bank。为之后的内存初始化提供条件。


解析设备树在函数unflatten_device_tree中完成,它将.dtb解析成device_node结构(第五部分有其定义),并构成单项链表,以供OF的API接口使用。

下面主要结合代码分析:/drivers/of/fdt.c


















总的归纳为:

① kernel入口处获取到uboot传过来的.dtb镜像的基地址

② 通过early_init_dt_scan()函数来获取kernel初始化时需要的bootargs和cmd_line等系统引导参数。

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

④ 内核调用OF的API接口,获取of_allnodes链表信息来初始化内核其他子系统、设备等。


四、OF的API接口

OF的接口函数在/drivers/of/目录下,有of_i2c.c、of_mdio.c、of_mtd.c、Adress.c等等

这里将列出几个常用的API接口。


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)

0 0