(DT系列三)系統啓動時, dts 是怎麼被加載的

轉自:http://blog.csdn.net/lichengtongxiazai/article/details/38941913

一,主要問題:
系統在啓動的時候,是怎麼加載 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是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呢?由於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列表中的項次後,就會調用相應的初始化函數。

發佈了34 篇原創文章 · 獲贊 5 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章