三、Exynos4412 核心移植(六)—— 裝置樹解析
http://blog.csdn.net/zqixiao_09/article/details/50822753
一、描述
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是一種描述硬體的資料結構,它起源於 OpenFirmware (OF)。在Linux 2.6中,ARM架構的板極硬體細節過多地被硬編碼在arch/arm/plat-xxx和arch/arm/mach-xxx,採用Device Tree後,許多硬體的細節可以直接透過它傳遞給Linux,而不再需要在kernel中進行大量的冗餘編碼。
Device Tree由一系列被命名的結點(node)和屬性(property)組成,而結點本身可包含子結點。所謂屬性,其實就是成對出現的name和value。在Device Tree中,可描述的資訊包括(原先這些資訊大多被hard code到kernel中):
- CPU的數量和類別
- 記憶體基地址和大小
- 匯流排和橋
- 外設連線
- 中斷控制器和中斷使用情況
- GPIO控制器和GPIO使用情況
- Clock控制器和Clock使用情況
它基本上就是畫一棵電路板上CPU、匯流排、裝置組成的樹,Bootloader會將這棵樹傳遞給核心,然後核心可以識別這棵樹,並根據它展開出Linux核心中的platform_device、i2c_client、spi_device等裝置,而這些裝置用到的記憶體、IRQ等資源,也被傳遞給了核心,核心會將這些資源繫結給展開的相應的裝置。
通常由.dts檔案以文字方式對系統裝置樹進行描述,經過Device Tree Compiler(dtc)
二、相關結構體
1、U-Boot需要將裝置樹在記憶體中的儲存地址傳給核心。該樹主要由三大部分組成:頭(Header)、結構塊(Structure block)、字串塊(Strings block)。
裝置樹在記憶體中的儲存佈局圖:
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 */
- constchar *name; /* architecture name */
- unsigned long atag_offset; /* tagged list (relative) */
- constchar *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, constchar *);
- };
1.6 裝置節點結構體
- struct device_node {
- constchar *name; //裝置name
- constchar *type; //裝置型別
- phandle phandle;
- constchar *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)
- constchar *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就是各個架構自己的設定函式,