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

一,主要问题:
系统在启动的时候,是怎么加载 dts的;
Lk,kernel中都应调查。

二:参考文字
dts加载流程如下图所示:


启动过程中,bootloader(默认是bootable/bootloader/lk)会根据机器硬件信息选择合适的devicetree装入内存,把地址等相关信息传给kernel。kernel中,会根据传入的信息创建设备。
1,先从little kernel开始:
1.1 总体来说
Lk/arch/arm/crt0.S文件中语句:bl kmain
调用的是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 level function that updates the device tree. */
int update_device_tree(void *fdt, const char *cmdline,
  void *ramdisk, uint32_t ramdisk_size)
{
int ret = 0;
uint32_t offset;

/* Check the device tree header */
//核查其magic数是否正确:version和size
ret = fdt_check_header(fdt);

/* Add padding to make space for new nodes and properties. */
//Move or resize dtb buffer
ret = fdt_open_into(fdt, fdt, fdt_totalsize(fdt) + DTB_PAD_SIZE);

/* Get offset of the memory node */
ret = fdt_path_offset(fdt, "/memory");

offset = ret;

ret = target_dev_tree_mem(fdt, offset);

/* Get offset of the chosen node */
ret = fdt_path_offset(fdt, "/chosen");

offset = ret;
/* Adding the cmdline to the chosen node */
ret = fdt_setprop_string(fdt, offset, (const char*)"bootargs", (const void*)cmdline);

/* Adding the initrd-start to the chosen node */
ret = fdt_setprop_u32(fdt, offset, "linux,initrd-start", (uint32_t)ramdisk);
if (ret)

/* Adding the initrd-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并将其转换成Device Tree Structure。 
(2)传递运行时参数传递以及platform的识别 
(3)将Device Tree Structure并入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 dtb pointer.

目前的kernel支持旧的tag list的方式,同时也支持device tree的方式。r2可能是device tree binary file的指针(bootloader要传递给内核之前要copy到memory中),也可以是tag list的指针。在ARM的汇编部分的启动代码中(主要是head.S和head-common.S),machine type ID和指向DTB或者atags的指针被保存在变量__machine_arch_type和__atags_pointer中,这么做是为了后续C代码进行处理。
start_kernel()
  |setup_arch()
     |setup_machine_fdt()//select machine description according to DT info
2,获得machine描述符
//根据Device Tree的信息,找到最适合的machine描述符。
struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
/* 扫描 /chosen node,保存运行时参数(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中的memory node,并把相关信息保存在meminfo中,全局变量meminfo保存了系统内存相关的信息。*/
of_scan_flat_dt(early_init_dt_scan_memory, NULL);

/* Change machine number to match the mdesc we‘re using */
__machine_arch_type = mdesc_best->nr;

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

3,将DTB转换成device node的结构的节点
在系统初始化的过程中,我们需要将DTB转换成节点是device_node的树状结构,以便后续方便操作。具体的代码位于setup_arch->unflatten_device_tree中。
void __init unflatten_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,将device node被组织成:
(1)global list。全局变量struct device_node *of_allnodes就是指向设备树的global list
(2)tree。
static void __unflatten_device_tree(struct boot_param_header *blob,
    struct device_node **mynodes,
    void * (*dt_alloc)(u64 size, u64 align))
{
  //此处删除了health check代码,例如检查DTB header的magic,确认blob的确指向一个DTB。
  /* scan过程分成两轮,第一轮主要是确定device-tree structure的长度,保存在size变量中 */
start = ((unsigned long)blob) +
be32_to_cpu(blob->off_dt_struct);
size = unflatten_dt_node(blob, 0, &start, NULL, NULL, 0);
size = (size | 3) + 1;

/* 初始化的时候,并不是扫描到一个node或者property就分配相应的内存,实际上内核是一次性的分配了一大片内存,这些内存包括了所有的struct device_node、node name、struct property所需要的内存。*/
mem = (unsigned long)
dt_alloc(size + 4, __alignof__(struct device_node));
((__be32 *)mem)[size / 4] = cpu_to_be32(0xdeadbeef);

/* 这是第二轮的scan,第一次scan是为了得到保存所有node和property所需要的内存size,第二次就是实打实的要构建device node tree了 */
start = ((unsigned long)blob) +
be32_to_cpu(blob->off_dt_struct);
unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0);
//此处略去校验溢出和校验OF_DT_END。
}
4,并入linux kernel的设备驱动模型
在linux kernel引入统一设备模型之后,bus、driver和device形成了设备模型中的铁三角。在驱动初始化的时候会将代表该driver的一个数据结构(一般是xxx_driver)挂入bus上的driver链表。device挂入链表分成两种情况,一种是即插即用类型的bus,在插入一个设备后,总线可以检测到这个行为并动态分配一个device数据结构(一般是xxx_device,例如usb_device),之后,将该数据结构挂入bus上的device链表。bus上挂满了driver和device,那么如何让device遇到“对”的那个driver呢?就是bus的match函数。
系统应该会根据Device tree来动态的增加系统中的platform_device(这个过程并非只发生在platform bus上,也可能发生在其他的非即插即用的bus上,例如AMBA总线、PCI总线)。 如果要并入linux kernel的设备驱动模型,那么就需要根据device_node的树状结构(root是of_allnodes)将一个个的device node挂入到相应的总线device链表中。只要做到这一点,总线机制就会安排device和driver的约会。当然,也不是所有的device node都会挂入bus上的设备链表,比如cpus node,memory node,choose node等。
4.1 没有挂入bus的device node
(1) cpus node的处理
暂无,只有choose node的相关处理。
(2) memory的处理
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
    int depth, void *data)
{
char *type = of_get_flat_dt_prop(node, "device_type", NULL);
/*在初始化的时候,我们会对每一个device node都要调用该call back函数,因此,我们要过滤掉那些和memory block定义无关的node。和memory block定义有的节点有两种,一种是node name是[email protected]形态的,另外一种是node中定义了device_type属性并且其值是memory。*/
if (type == NULL) {
if (depth != 1 || strcmp(uname, "[email protected]") != 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呢?由于memory node一定是root node的child,因此dt_root_addr_cells(root node的#address-cells属性值)和dt_root_size_cells(root node的#size-cells属性值)之和就是address,size数组的entry size。*/
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
u64 base, size;
base = dt_mem_next_cell(dt_root_addr_cells, &reg);
size = dt_mem_next_cell(dt_root_size_cells, &reg);
if (size == 0)
continue;
//将具体的memory block信息加入到内核中。
early_init_dt_add_memory_arch(base, size);
}
return 0;
}
(3) interrupt controller的处理
初始化是通过start_kernel->init_IRQ->machine_desc->init_irq()实现的。我们用Qualcomm MSM 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 __init msm_dt_init_irq(void)
{
struct device_node *node;

of_irq_init(irq_match);
node = of_find_matching_node(NULL, mpm_match);
}
of_irq_init函数:遍历Device Tree,找到匹配的irqchip。具体的代码如下:
void __init of_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之间的父子关系。对于interrupt controller,它也可能是一个树状的结构。*/
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);
}

/*正因为interrupt controller被组织成树状的结构,因此初始化的顺序就需要控制,应该从根节点开始,依次递进到下一个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 struct of_device_id *match;
int ret;
of_irq_init_cb_t irq_init_cb;
/*最开始的时候parent变量是NULL,确保第一个被处理的是root interrupt controller。在处理完root node之后,parent变量被设定为root interrupt controller,因此,第二个循环中处理的是所有parent是root interrupt controller的child interrupt controller。也就是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);
}

/* 对于level 0,只有一个root interrupt controller,对于level 1,可能有若干个interrupt controller,因此要遍历这些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这个属性定义,那么linux kernel就会分配一个interrupt controller的描述符(struct intc_desc)并挂入队列。通过interrupt-parent属性,可以确定各个interrupt controller的层次关系。在scan了所有的Device Tree中的interrupt controller的定义之后,系统开始匹配过程。一旦匹配到了interrupt chip列表中的项次后,就会调用相应的初始化函数。

时间: 2024-10-24 16:00:18

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

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

原文网址:http://www.cnblogs.com/biglucky/p/4057481.html 一,主要问题:系统在启动的时候,是怎么加载 dts的:Lk,kernel中都应调查. 二:参考文字dts加载流程如下图所示: 启动过程中,bootloader(默认是bootable/bootloader/lk)会根据机器硬件信息选择合适的devicetree装入内存,把地址等相关信息传给kernel.kernel中,会根据传入的信息创建设备.1,先从little kernel开始:1.1 总

VSTO学习笔记(三) 开发Office 2010 64位COM加载项

原文:VSTO学习笔记(三) 开发Office 2010 64位COM加载项 一.加载项简介 Office提供了多种用于扩展Office应用程序功能的模式,常见的有: 1.Office 自动化程序(Automation Executables) 2.Office加载项(COM or Excel Add-In) 3.Office文档代码或模板(Code Behind an Office Document or Template) 4.Office 智能标签(Smart Tags) 本次我们将学习使

[Android学习系列16]Android把php输出的json加载到listview

首先写个php脚本输出json,注意,还要输出回车,方便android的bufferreader读取一行 <?php class Book { public $bookid; public $bookname; public $bookinfo; function __construct($id,$name,$info ){ $this->bookid = $id; $this->bookname = $name; $this->bookinfo = $info; } } $boo

[Android学习系列15]下拉刷新列表实现动态加载

使用google官方的SwipeRefreshLayout 参考: http://blog.csdn.net/easyer2012/article/details/22857807 http://stormzhang.github.io/android/2014/03/29/android-swiperefreshlayout/ http://www.eoeandroid.com/thread-330439-1-1.html http://www.oschina.net/translate/sw

JavaWeb 服务启动时,在后台老板启动加载一个线程

avaWeb 服务启动时,在后台启动加载一个线程JavaWeb 服务启动时,在后台启动加载一个线程. 目前,我所掌握的一共有两种方法,第一种是监听(Listener),第二种是配置随项目启动而启动的Servlet. 下面对这两种方法做一简单的介绍,(Mark一下,防止以后急用又忘记了): 监听(Listener) 首先,我们创建一个监听的类,继承ServletContextListener,如下: 源码复制打印    package com.wxp.thread;    import javax

vs2012打开低版本项目时 出现vs2012警告未能加载包“visual c++ package 解决办法

vs2012 打开 vs2010 项目时 提示的 错误信息. 解决办法 是下载一个 vs2012的 一个补丁包 http://www.microsoft.com/en-us/download/details.aspx?id=36020 初次安装成功后,调试 无法启用,关闭,重新打开项目 即可解决! ======ok. [在此谢谢网上提供解决方案的朋友们,谢谢你们!] vs2012打开低版本项目时 出现vs2012警告未能加载包"visual c++ package 解决办法

Android批量图片加载经典系列——afinal框架实现图片的异步缓存加载

一.问题描述 在之前的系列文章中,我们使用了Volley和Xutil框架实现图片的缓存加载(查看系列文章:http://www.cnblogs.com/jerehedu/p/4607599.html#pltpjz),接下来我们再介绍一下afinal 框架的使用. Afinal 是一个android的http框架.sqlite orm 和 ioc 框架.使其更加简单易用,Afinal的宗旨是简洁,快速.约定配置的方式之后,尽量一行代码完成所有事情,代码入侵性小,在三者中比较推荐.在这里我们主要使用

《Entity Framework 6 Recipes》中文翻译系列 (43) ------ 第八章 POCO之使用POCO加载实体

翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 8-2  使用POCO加载关联实体 问题 你想使用POCO预先加载关联实体. 解决方案 假设你有如图8-3所示的模型. 图8-3. 一个包含实体Venue.Event和Competitor的模型 实体使用POCO类,我们想预先加载关联实体(导航属性).并使用上下文对象中的Include()方法来实现.代码清单8-4演示了使用Include()方法来实现我们的要求. 代码清单8-4. 使用In

Android批量图片加载经典系列——使用LruCache、AsyncTask缓存并异步加载图片

一.问题描述 使用LruCache.AsyncTask实现批量图片的加载并达到下列技术要求 1.从缓存中读取图片,若不在缓存中,则开启异步线程(AsyncTask)加载图片,并放入缓存中 2.及时移除无效的异步线程;保证异步加载图片时不会乱序 3.只对当前屏幕可见部分进行缓存.异步加载图片 4.优化性能杜绝OOM 二.案例介绍 案例实现照片墙效果 三.主要技术 LruCache 内存缓存技术,在Android中 专门用来做图片缓存处理的组件,主要使用步骤 (1) 设置缓存图片的内存大小,如设置为