您的位置:首页 > 运维架构 > Linux

(DT系列三)系统启动时, dts 是怎么被加载的

2016-11-09 10:08 721 查看
一,主要问题:

系统在启动的时候,是怎么加载 dts的;

Lk,kernel中都应调查。

 

 

二:参考文字

dts加载流程如下图所示:

 

 

 

 

启动过程中,bootloader(默认是bootable/bootloader/lk)会根据机器硬件信息选择合适的devicetree装入内存,把地址等相关信息传给kernel。kernel中,会根据传入的信息创建设备。

1,先从little kernel开始:

1.1 总体来说

Lk/arch/arm/crt0.S文件中语句:blkmain

调用的是lk/kernel/main.c文件中的函数:kmain()

 

 

kmain()

  |bootstrap2()

    |arch_init()

    |platform_init()

    |target_init()

    |apps_init()//call init() of APPs defined using APP_START macro

       |aboot_init()

          |boot_linux_from_mmc()

             |//1,Device tree的第一种方法

                |dev_tree_get_entry_info()

                   |__dev_tree_get_entry_info()

                |memmove();

             |//2,Device tree的第二种方法

                |dev_tree_appended()

             |boot_linux()

                |update_device_tree()

                |entry(0, machtype, tags_phys);//pass control to kernel

 

1.2 详细介绍

Aboot.c(bootable\bootloader\lk\app\aboot)

 

 

APP_START(aboot)

.init = aboot_init,

APP_END

在下面aboot_init()---> boot_linux_from_mmc()中,调用dev_tree_get_entry_info(),里面会根据硬件(chipset和platform的id,系统实际跑时的信息在系统boot的更早阶段由N侧设置并传来,而DT中的信息由根节点的"qcom,msm-id"属性定义)来选择合适的DT,后面会把该DT装入内存,把地址等信息传给kernel(通过CPU寄存器)。

void boot_linux(void*kernel, unsigned *tags,

const char *cmdline,unsigned machtype,

void *ramdisk,unsigned ramdisk_size)

{

#if DEVICE_TREE

 

 

//更新Device Tree

ret =update_device_tree((void *)tags, final_cmdline, ramdisk, ramdisk_size);

}

 

 

/* Top levelfunction that updates the device tree. */

intupdate_device_tree(void *fdt, const char *cmdline,

  void*ramdisk, uint32_t ramdisk_size)

{

int ret = 0;

uint32_t offset;

 

 

/* Check the devicetree header */

//核查其magic数是否正确:version和size

ret =fdt_check_header(fdt);

 

 

 

 

/* Add padding tomake space for new nodes and properties. */

//Move or resize dtbbuffer

ret =fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + DTB_PAD_SIZE);

 

 

/* Get offset of thememory node */

ret =fdt_path_offset(fdt, "/memory");

 

 

offset = ret;

 

 

ret =target_dev_tree_mem(fdt, offset);

 

 

/* Get offset of thechosen node */

ret =fdt_path_offset(fdt, "/chosen");

 

 

offset = ret;

/* Adding thecmdline to the chosen node */

ret =fdt_setprop_string(fdt, offset, (const char*)"bootargs", (constvoid*)cmdline);

 

 

/* Adding theinitrd-start to the chosen node */

ret =fdt_setprop_u32(fdt, offset, "linux,initrd-start",(uint32_t)ramdisk);

if (ret)

 

 

/* Adding theinitrd-end to the chosen node */

ret =fdt_setprop_u32(fdt, offset, "linux,initrd-end", ((uint32_t)ramdisk +ramdisk_size));

 

 

fdt_pack(fdt);

 

 

return ret;

}

 

 

2,Kernel中的处理

 

 

主要的数据流包括: 

(1)初始化流程,即扫描dtb并将其转换成DeviceTree Structure。 

(2)传递运行时参数传递以及platform的识别 

(3)将Device TreeStructure并入linux kernel的设备驱动模型。 

 

 

1,汇编部分的代码分析 

linux/arch/arm/kernel/head.S文件定义了bootloader和kernel的参数传递要求:

 

 

MMU = off, D-cache =off, I-cache = dont care, r0 = 0, r1 = machine nr, r2 = atags or dtbpointer. 

 

 

目前的kernel支持旧的taglist的方式,同时也支持device tree的方式。r2可能是device tree binaryfile的指针(bootloader要传递给内核之前要copy到memory中),也可以是taglist的指针。在ARM的汇编部分的启动代码中(主要是head.S和head-common.S),machine typeID和指向DTB或者atags的指针被保存在变量__machine_arch_type和__atags_pointer中,这么做是为了后续C代码进行处理。

start_kernel()

  |setup_arch()

    |setup_machine_fdt()//selectmachine description according to DT info

 

2,获得machine描述符

//根据DeviceTree的信息,找到最适合的machine描述符。

struct machine_desc* __init setup_machine_fdt(unsigned int dt_phys)

{

/* 扫描 /chosennode,保存运行时参数(bootargs)到boot_command_line,此外,还处理initrd相关的property,并保存在initrd_start和initrd_end这两个全局变量中*/

of_scan_flat_dt(early_init_dt_scan_chosen,boot_command_line);

/* 扫描根节点,获取{size,address}-cells信息,并保存在dt_root_size_cells和dt_root_addr_cells全局变量中 */

of_scan_flat_dt(early_init_dt_scan_root,NULL);

/* 扫描DTB中的memorynode,并把相关信息保存在meminfo中,全局变量meminfo保存了系统内存相关的信息。*/

of_scan_flat_dt(early_init_dt_scan_memory,NULL);

 

 

/* Change machinenumber to match the mdesc we're using */

__machine_arch_type= mdesc_best->nr;

 

 

return mdesc_best;

}

运行时参数是在扫描DTB的chosennode时候完成的,具体的动作就是获取chosennode的bootargs、initrd等属性的value,并将其保存在全局变量(boot_command_line,initrd_start、initrd_end)中。

 

 

3,将DTB转换成device node的结构的节点

在系统初始化的过程中,我们需要将DTB转换成节点是device_node的树状结构,以便后续方便操作。具体的代码位于setup_arch->unflatten_device_tree中。

void __initunflatten_device_tree(void)

{

__unflatten_device_tree(initial_boot_params,&allnodes,

early_init_dt_alloc_memory_arch);

 

 

/* Get pointer to"/chosen" and "/aliasas" nodes for use everywhere */

of_alias_scan(early_init_dt_alloc_memory_arch);

}

unflatten_device_tree函数的主要功能就是扫描DTB,将devicenode被组织成:

(1)globallist。全局变量struct device_node *of_allnodes就是指向设备树的global list

(2)tree。

static void__unflatten_device_tree(struct boot_param_header *blob,

    structdevice_node **mynodes,

    void *(*dt_alloc)(u64 size, u64 align))

{

  //此处删除了healthcheck代码,例如检查DTB header的magic,确认blob的确指向一个DTB。

 /* scan过程分成两轮,第一轮主要是确定device-tree structure的长度,保存在size变量中 */

start = ((unsignedlong)blob) +

be32_to_cpu(blob->off_dt_struct);

size =unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);

size = (size | 3) +1;

 

 

/*初始化的时候,并不是扫描到一个node或者property就分配相应的内存,实际上内核是一次性的分配了一大片内存,这些内存包括了所有的structdevice_node、node name、struct property所需要的内存。*/

mem = (unsignedlong)

dt_alloc(size + 4,__alignof__(struct device_node));

((__be32 *)mem)[size/ 4] = cpu_to_be32(0xdeadbeef);

 

 

/*这是第二轮的scan,第一次scan是为了得到保存所有node和property所需要的内存size,第二次就是实打实的要构建device nodetree了 */

start = ((unsignedlong)blob) +

be32_to_cpu(blob->off_dt_struct);

unflatten_dt_node(blob,mem, &start, NULL, &allnextp, 0);

//此处略去校验溢出和校验OF_DT_END。

}

 

4,并入linux kernel的设备驱动模型

在linuxkernel引入统一设备模型之后,bus、driver和device形成了设备模型中的铁三角。在驱动初始化的时候会将代表该driver的一个数据结构(一般是xxx_driver)挂入bus上的driver链表。device挂入链表分成两种情况,一种是即插即用类型的bus,在插入一个设备后,总线可以检测到这个行为并动态分配一个device数据结构(一般是xxx_device,例如usb_device),之后,将该数据结构挂入bus上的device链表。bus上挂满了driver和device,那么如何让device遇到“对”的那个driver呢?就是bus的match函数。

系统应该会根据Devicetree来动态的增加系统中的platform_device(这个过程并非只发生在platformbus上,也可能发生在其他的非即插即用的bus上,例如AMBA总线、PCI总线)。 如果要并入linuxkernel的设备驱动模型,那么就需要根据device_node的树状结构(root是of_allnodes)将一个个的devicenode挂入到相应的总线device链表中。只要做到这一点,总线机制就会安排device和driver的约会。当然,也不是所有的device node都会挂入bus上的设备链表,比如cpusnode,memory
node,choose node等。

 

4.1 没有挂入bus的devicenode

(1) cpus node的处理

暂无,只有choosenode的相关处理。

(2) memory的处理

int __initearly_init_dt_scan_memory(unsigned long node, const char *uname,

    intdepth, void *data)

{

char *type =of_get_flat_dt_prop(node, "device_type", NULL);

/*在初始化的时候,我们会对每一个devicenode都要调用该call back函数,因此,我们要过滤掉那些和memory block定义无关的node。和memoryblock定义有的节点有两种,一种是node name是memory@形态的,另外一种是node中定义了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的起始地址和length的信息。有两种属性和该信息有关,一个是linux,usable-memory,不过最新的方式还是使用reg属性。*/

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;

endp = reg + (l /sizeof(__be32));

/*reg属性的值是address,size数组,那么如何来取出一个个的address/size呢?由于memorynode一定是root node的child,因此dt_root_addr_cells(root node的#address-cells属性值)和dt_root_size_cells(root node的#size-cells属性值)之和就是address,size数组的entrysize。*/

while ((endp - reg)>= (dt_root_addr_cells + dt_root_size_cells)) {

u64 base, size;

base =dt_mem_next_cell(dt_root_addr_cells, ®);

size =dt_mem_next_cell(dt_root_size_cells, ®);

if (size == 0)

continue;

//将具体的memoryblock信息加入到内核中。

early_init_dt_add_memory_arch(base,size);

}

return 0;

}

(3) interruptcontroller的处理

初始化是通过start_kernel->init_IRQ->machine_desc->init_irq()实现的。我们用QualcommMSM 8974为例来描述interrupt controller的处理过程。下面是machine描述符的定义:/arch/arm/mach-msm/board-8974.c

DT_MACHINE_START(MSM8974_DT,"Qualcomm MSM 8974 (Flattened Device Tree)")

.init_irq =msm_dt_init_irq,

.dt_compat =msm8974_dt_match,

...

MACHINE_END

源码文件:/arch/arm/mach-msm/board-dt.c

void __initmsm_dt_init_irq(void)

{

struct device_node*node;

 

 

of_irq_init(irq_match);

node =of_find_matching_node(NULL, mpm_match);

}

of_irq_init函数:遍历DeviceTree,找到匹配的irqchip。具体的代码如下:

void __initof_irq_init(const struct of_device_id *matches)

{

/*遍历所有的node,寻找定义了interrupt-controller属性的node,如果定义了interrupt-controller属性则说明该node就是一个中断控制器。*/

for_each_matching_node(np,matches) {

if(!of_find_property(np, "interrupt-controller", NULL))

continue;

/*分配内存并挂入链表,当然还有根据interrupt-parent建立controller之间的父子关系。对于interruptcontroller,它也可能是一个树状的结构。*/

desc =kzalloc(sizeof(*desc), GFP_KERNEL);

 

 

desc->dev = np;

desc->interrupt_parent= of_irq_find_parent(np);

if(desc->interrupt_parent == np)

desc->interrupt_parent= NULL;

list_add_tail(&desc->list,&intc_desc_list);

}

 

 

/*正因为interruptcontroller被组织成树状的结构,因此初始化的顺序就需要控制,应该从根节点开始,依次递进到下一个level的interrupt controller。*/

while(!list_empty(&intc_desc_list)) {

/*intc_desc_list链表中的节点会被一个个的处理,每处理完一个节点就会将该节点删除,当所有的节点被删除,整个处理过程也就是结束了。*/

list_for_each_entry_safe(desc,temp_desc, &intc_desc_list, list) {

const structof_device_id *match;

int ret;

of_irq_init_cb_tirq_init_cb;

/*最开始的时候parent变量是NULL,确保第一个被处理的是rootinterrupt controller。在处理完root node之后,parent变量被设定为root interruptcontroller,因此,第二个循环中处理的是所有parent是root interrupt controller的child interruptcontroller。也就是level 1(如果root是level 0的话)的节点。*/

if(desc->interrupt_parent != parent)

continue;

 

 

list_del(&desc->list);//从链表中删除

match= of_match_node(matches, desc->dev);//匹配并初始化

//match->data是初始化函数

if(WARN(!match->data,

  "of_irq_init: no init function for %s\n",

  match->compatible)) {

kfree(desc);

continue;

}

irq_init_cb= match->data;//执行初始化函数

ret =irq_init_cb(desc->dev, desc->interrupt_parent);

/*处理完的节点放入intc_parent_list链表,后面会用到*/

list_add_tail(&desc->list,&intc_parent_list);

}

 

 

/* 对于level0,只有一个root interrupt controller,对于level 1,可能有若干个interruptcontroller,因此要遍历这些parent interrupt controller,以便处理下一个level的child node。 */

desc =list_first_entry(&intc_parent_list, typeof(*desc), list);

list_del(&desc->list);

parent =desc->dev;

kfree(desc);

}

}

只有该node中有interrupt-controller这个属性定义,那么linuxkernel就会分配一个interrupt controller的描述符(structintc_desc)并挂入队列。通过interrupt-parent属性,可以确定各个interruptcontroller的层次关系。在scan了所有的Device Tree中的interruptcontroller的定义之后,系统开始匹配过程。一旦匹配到了interrupt chip列表中的项次后,就会调用相应的初始化函数。

 

源文档 <http://blog.csdn.net/lichengtongxiazai/article/details/38941913
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux devicetree dts