一個最簡單的設備樹必須包含根節點,cpus節點,memory節點。根節點的名字及全路徑都是“/”,至少需要包含model和compatible兩個屬性。model屬性我們在屬性那節已經說過是用來描述產品型號的,類型為字符串,推薦的格式為“manufacturer,model-number”(非強制的)。根節點的model屬性描述的是板子的型號或者芯片平臺的型號,如:
model = "Atmel AT91SAM9G20 family SoC"
model = "Samsung SMDK5420 board based on EXYNOS5420"
從軟件的層面講model屬性僅僅表示一個名字而已,沒有更多的作用。compatible屬性則不同,該屬性決定軟件如何匹配硬件對硬件進行初始化。屬性那一節我們說過compatible屬性的類型是字符串數組,按照范圍從小到大的順序排列,每個字符串表示一種匹配類型。根節點的compatible屬性表示平臺如何匹配,比如‘compatible = "samsung,smdk5420", "samsung,exynos5420", "samsung,exynos5"’,表示軟件應該首先匹配'samsung,smdk5420',這個是一款開發板。如果無法匹配,再試著匹配"samsung,exynos5420",這個是一款芯片平臺。如果還是無法匹配,還可以試著匹配 "samsung,exynos5",這是一個系列的芯片平臺。這里說的匹配是指軟件根據該信息找到對應的代碼,如對應的初始化函數。
根節點表示的是整個板子或者芯片平臺,所以在系統初始化比較早的時候就需要確認是什么平臺,怎樣初始化。對于Linux,是通過在start_kernel函數調用setup_arch函數實現的。不同的架構,setup_arch函數的實現不同,對于arm架構,setup_arch函數源代碼位于arch/arm/kernel/setup.c中。以下是該函數的部分源代碼(代碼來自內核版本4.4-rc7的官方版本,本節后邊所有代碼都來自該版本)。
?935 void __init setup_arch(char **cmdline_p)
?936 {
?937???? const struct machine_desc *mdesc;
?938?
?939???? setup_processor();
?940???? mdesc = setup_machine_fdt(__atags_pointer);
?941???? if (!mdesc)
?942???????? mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);
?943???? machine_desc = mdesc;
?944???? machine_name = mdesc->name;
第940行setup_machine_fdt函數的輸入是設備樹(DTB)首地址,返回的mdesc是描述平臺信息的結構體。還記得我們在概述那節說過啟動程序如uboot把設備樹讀到內存中,然后在啟動內核的同時將設備樹首地址傳給內核,此處__atags_pointer就是啟動程序傳給內核的設備樹地址(此時內存中的設備樹已經是DTB形式)。setup_machine_fdt中的fdt是flat device tree的縮寫,fdt的意思是說設備樹在內存中是在一塊連續地址存儲的,fdt和dtb說的都是同一個東西。setup_machine_tags是在設備樹初始化失敗的時候才調用的,所以不用管他。machine_desc和machine_name都是靜態全局變量,用來保存指針方便后邊引用的。為了更好的理解setup_machine_fdt具體實現了什么功能,我們首先看下machine_desc結構體。不同的架構,該結構體定義差別很大,arm架構源代碼位于arch/arm/include/asm/mach/arch.h,復制如下:
?
?27 struct machine_desc {
?28???? unsigned int??????? nr;???? /* architecture number? */
?29???? const char????? *name;????? /* architecture name??? */
?30???? unsigned long?????? atag_offset;??? /* tagged list (relative) */
?31???? const char *const?? *dt_compat; /* array of device tree
?32????????????????????????? * 'compatible' strings */
?33?
?34???? unsigned int??????? nr_irqs;??? /* number of IRQs */
?35?
?36 #ifdef CONFIG_ZONE_DMA
?37???? phys_addr_t???? dma_zone_size;? /* size of DMA-able area */
?38 #endif
?39?
?40???? unsigned int??????? video_start;??? /* start of video RAM?? */
?41???? unsigned int??????? video_end;? /* end of video RAM */
?42?
?43???? unsigned char?????? reserve_lp0 :1; /* never has lp0??? */
?44???? unsigned char?????? reserve_lp1 :1; /* never has lp1??? */
?45???? unsigned char?????? reserve_lp2 :1; /* never has lp2??? */
?46???? enum reboot_mode??? reboot_mode;??? /* default restart mode */
?47???? unsigned??????? l2c_aux_val;??? /* L2 cache aux value?? */
?48???? unsigned??????? l2c_aux_mask;?? /* L2 cache aux mask??? */
?49???? void??????????? (*l2c_write_sec)(unsigned long, unsigned);
?50???? const struct smp_operations *smp;?? /* SMP operations?? */
?51???? bool??????????? (*smp_init)(void);
?52???? void??????????? (*fixup)(struct tag *, char **);
?53???? void??????????? (*dt_fixup)(void);
?54???? long long?????? (*pv_fixup)(void);
?55???? void??????????? (*reserve)(void);/* reserve mem blocks? */
?56???? void??????????? (*map_io)(void);/* IO mapping function? */
?57???? void??????????? (*init_early)(void);
?58???? void??????????? (*init_irq)(void);
?59???? void??????????? (*init_time)(void);
?60???? void??????????? (*init_machine)(void);
?61???? void??????????? (*init_late)(void);
?62 #ifdef CONFIG_MULTI_IRQ_HANDLER
?63???? void??????????? (*handle_irq)(struct pt_regs *);
?64 #endif
?65???? void??????????? (*restart)(enum reboot_mode, const char *);
?66 };
?67?
從以上結構體的注釋可以看出,該結構體包含了非常多的信息。注意第31行的dt_compat變量,該變量就是用來匹配設備樹的compatible屬性的。
setup_machine_fdt函數的實現也是架構相關的,arm架構源代碼位于arch/arm/kernel/devtree.c,復制代碼如下:
203 /**???? ?
204? * setup_machine_fdt - Machine setup when an dtb was passed to the kernel
205? * @dt_phys: physical address of dt blob
206? *? ?
207? * If a dtb was passed to the kernel in r2, then use it to choose the
208? * correct machine_desc and to setup the system.
209? */?
210 const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
211 {?? ?
212???? const struct machine_desc *mdesc, *mdesc_best = NULL;
213?
214 #ifdef CONFIG_ARCH_MULTIPLATFORM
215???? DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
216???? MACHINE_END
217?
218???? mdesc_best = &__mach_desc_GENERIC_DT;
219 #endif
220?
221???? if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
222???????? return NULL;
223?
224???? mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);
225?
226???? if (!mdesc) {
227???????? const char *prop;
228???????? int size;
229???????? unsigned long dt_root;
230?
231???????? early_print("
Error: unrecognized/unsupported "
232???????????????? "device tree compatible list:
[ ");
233?
234???????? dt_root = of_get_flat_dt_root();
235???????? prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
236???????? while (size > 0) {
237???????????? early_print("'%s' ", prop);
238???????????? size -= strlen(prop) + 1;
239???????????? prop += strlen(prop) + 1;
240???????? }
241???????? early_print("]
");
242?
243???????? dump_machine_table(); /* does not return */
244???? }
245?
246???? /* We really don't want to do this, but sometimes firmware provides buggy data */
247???? if (mdesc->dt_fixup)
248???????? mdesc->dt_fixup();
249?
250???? early_init_dt_scan_nodes();
251?
252???? /* Change machine number to match the mdesc we're using */
253???? __machine_arch_type = mdesc->nr;
254?
255???? return mdesc;
256 }
第221行檢查fdt指針是否為空并且調用early_init_dt_verify函數,該函數代碼位于drivers/of/fdt.c,這個函數算是of模塊(還記得么?是open firmware的縮寫)的第一個函數,復制代碼如下:
1060?
1061 bool __init early_init_dt_verify(void *params)
1062 {
1063???? if (!params)
1064???????? return false;
1065?
1066???? /* check device tree validity */
1067???? if (fdt_check_header(params))
1068???????? return false;
1069?
1070???? /* Setup flat device-tree pointer */
1071???? initial_boot_params = params;
1072???? of_fdt_crc32 = crc32_be(~0, initial_boot_params,
1073???????????????? fdt_totalsize(initial_boot_params));
1074???? return true;
1075 }
early_init_dt_verify首先檢查fdt頭部的合法性,然后設置fdt全局變量以及計算crc。這個initial_boot_params變量后邊在訪問設備樹的時候還會用到。繼續看前邊第224行,of_flat_dt_match_machine函數算是of模塊的第二個函數吧,在分析這個函數前,我們首先分析這個函數的第二個參數arch_get_next_mach,這是一個函數指針,arm架構的實現位于arch/arm/kernel/devtree.c,復制代碼如下:
190 static const void * __init arch_get_next_mach(const char *const **match)
191 {
192???? static const struct machine_desc *mdesc = __arch_info_begin;
193???? const struct machine_desc *m = mdesc;
194?
195???? if (m >= __arch_info_end)
196???????? return NULL;
197?
198???? mdesc++;
199???? *match = m->dt_compat;
200???? return m;
201 } ?
這個函數很簡單,注意的是mdesc是靜態局部變量,第一次調用指向__arch_info_begin,后邊每次調用都mdesc++,如果超過了__arch_info_end就返回NULL。以上代碼說明在__arch_info_begin和__arch_info_end兩個地址之間存儲著多個machine_desc變量(也可能是一個),該函數遍歷這些變量,通過match參數返回所有machine_desc結構體的dt_compat變量指針。問題是__arch_info_begin和__arch_info_end地址是怎么來的呢?在arch/arm/kernel/vmlinux.lds.S連接腳本中定義了.arch.info.init段,__arch_info_begin和__arch_info_end地址分別是該段的首尾地址。
188???? .init.arch.info : {
189???????? __arch_info_begin = .;
190???????? *(.arch.info.init)
191???????? __arch_info_end = .;
192???? }
那么.init.arch.info段的內容怎么來的呢?這就要參考DT_MACHINE_START和MACHINE_END宏了,arm架構的定義在arch/arm/include/asm/mach/arch.h文件,如下所示:
?94 #define DT_MACHINE_START(_name, _namestr)??????
?95 static const struct machine_desc __mach_desc_##_name???
?96? __used????????????????????????
?97? __attribute__((__section__(".arch.info.init"))) = {???
?98???? .nr???? = ~0,??????????????
?99???? .name?????? = _namestr,
100?
101 #endif
從該宏代碼看出他定義了一個machine_desc類型的靜態局部變量,該變量位于.arch.info.init段中。參考arch/arm/mach-exynos/exynos.c中如下代碼,以下代碼在.arch.info.init段定義了一個名字為__mach_desc_EXYNOS_DT,類型為machine_desc的靜態局部變量,并且該變量的dt_compat字符串矩陣中有"samsung,exynos5420"的字符串。
277 static char const *const exynos_dt_compat[] __initconst = {
278???? "samsung,exynos3",
279???? "samsung,exynos3250",
280???? "samsung,exynos4",
281???? "samsung,exynos4210",
282???? "samsung,exynos4212",
283???? "samsung,exynos4412",
284???? "samsung,exynos4415",
285???? "samsung,exynos5",
286???? "samsung,exynos5250",
287???? "samsung,exynos5260",
288???? "samsung,exynos5420",
289???? "samsung,exynos5440",
290???? NULL
291 };
?
319 DT_MACHINE_START(EXYNOS_DT, "SAMSUNG EXYNOS (Flattened Device Tree)")
320???? /* Maintainer: Thomas Abraham */
321???? /* Maintainer: Kukjin Kim */
322???? .l2c_aux_val??? = 0x3c400001,
323???? .l2c_aux_mask?? = 0xc20fffff,
324???? .smp??????? = smp_ops(exynos_smp_ops),
325???? .map_io???? = exynos_init_io,
326???? .init_early = exynos_firmware_init,
327???? .init_irq?? = exynos_init_irq,
328???? .init_machine?? = exynos_dt_machine_init,
329???? .init_late? = exynos_init_late,
330???? .dt_compat? = exynos_dt_compat,
331???? .reserve??? = exynos_reserve,
332???? .dt_fixup?? = exynos_dt_fixup,
333 MACHINE_END
我們已經知道了get_next_compat指針的具體實現了,現在繼續看of_flat_dt_match_machine。從第732行開始的循環就是遍歷.arch.info.init段中所有的dt_compat變量,然后通過of_flat_dt_match計算一個分數,并且尋找那個分數最小的。
?713 /**
?714? * of_flat_dt_match_machine - Iterate match tables to find matching machine.
?715? *
?716? * @default_match: A machine specific ptr to return in case of no match.
?717? * @get_next_compat: callback function to return next compatible match table.
?718? *
?719? * Iterate through machine match tables to find the best match for the machine
?720? * compatible string in the FDT.
?721? */
?722 const void * __init of_flat_dt_match_machine(const void *default_match,
?723???????? const void * (*get_next_compat)(const char * const**))
?724 {
?725???? const void *data = NULL;
?726???? const void *best_data = default_match;
?727???? const char *const *compat;
?728???? unsigned long dt_root;
?729???? unsigned int best_score = ~1, score = 0;
?730?????? ?
?731???? dt_root = of_get_flat_dt_root();
?732???? while ((data = get_next_compat(&compat))) {
?733???????? score = of_flat_dt_match(dt_root, compat);
?734???????? if (score > 0 && score < best_score) {
?735???????????? best_data = data;
?736???????????? best_score = score;
?737???????? }
?738???? }
?....
?759???? return best_data;
?760 }
?761?
of_flat_dt_match_machine的其余部分代碼都是出錯處理及打印,現在我們看of_flat_dt_match的實現,該函數僅僅是直接調用of_fdt_match而已,不同的是增加了initial_boot_params參數(還記得我們說過前邊說過的這個變量的初始化吧,其實這就是內核中的一個簡單封裝而已)。
?685 /**
?686? * of_flat_dt_match - Return true if node matches a list of compatible values
?687? */
?688 int __init of_flat_dt_match(unsigned long node, const char *const *compat)
?689 { ?
?690???? return of_fdt_match(initial_boot_params, node, compat);
?691 } ?
of_fdt_match函數從142行開始遍歷compat數組的每一個字符串,然后通過of_fdt_is_compatible函數計算匹配度(以最小的數值作為最終的結果)。代碼到這個地方已經很好理解了,compat中的數據來自內核的.arch.info.init段,這個段表示內核支持的平臺,blob是設備樹其實地址,通過node節點指定根節點的compatible屬性,然后計算匹配度。還記得我們前邊說過的compatible屬性包含多個字符串,從前向后范圍越來越大,優先匹配前邊的,這個地方代碼計算分數(score變量)就是這個目的。
?131 /**
?132? * of_fdt_match - Return true if node matches a list of compatible values
?133? */
?134 int of_fdt_match(const void *blob, unsigned long node,
?135????????????????? const char *const *compat)
?136 {
?137???? unsigned int tmp, score = 0;
?138?
?139???? if (!compat)
?140???????? return 0;
?141?
?142???? while (*compat) {
?143???????? tmp = of_fdt_is_compatible(blob, node, *compat);
?144???????? if (tmp && (score == 0 || (tmp < score)))
?145???????????? score = tmp;
?146???????? compat++;
?147???? }
?148?
?149???? return score;
?150 }
繼續看of_fdt_is_compatible函數的實現,第97行已經看到找該節點下的"compatible"屬性了。
? 80 /**
? 81? * of_fdt_is_compatible - Return true if given node from the given blob has
? 82? * compat in its compatible list
? 83? * @blob: A device tree blob
? 84? * @node: node to test
? 85? * @compat: compatible string to compare with compatible list.
? 86? *
? 87? * On match, returns a non-zero value with smaller values returned for more
? 88? * specific compatible values.
? 89? */
? 90 int of_fdt_is_compatible(const void *blob,
? 91?????????????? unsigned long node, const char *compat)
? 92 {
? 93???? const char *cp;
? 94???? int cplen;
? 95???? unsigned long l, score = 0;
? 96?
? 97???? cp = fdt_getprop(blob, node, "compatible", &cplen);
? 98???? if (cp == NULL)
? 99???????? return 0;
?100???? while (cplen > 0) {
?101???????? score++;
?102???????? if (of_compat_cmp(cp, compat, strlen(compat)) == 0)
?103???????????? return score;
?104???????? l = strlen(cp) + 1;
?105???????? cp += l;
?106???????? cplen -= l;
?107???? }
?108?
?109???? return 0;
?110 }
關于根節點的"compatible"屬性我們就說到這,一句話總結下就是內核通過"compatible"屬性找到對應的平臺描述信息,按照范圍從小到大盡量匹配范圍最小的,如果匹配不到,那么說明內核不支持該平臺,系統將在初始化的時候就出錯。
根節點還可能包含的屬性為#address-cells和#size-cells,規范中說明這兩個屬性是必須的,實際應用時是可選的,還記得屬性那一節說這兩個屬性如果沒有都是有默認值的,#address-cells默認值為2,#size-cells默認值為1。根節點下必須包含的子節點為cpus和memory,后邊會說明cpus下邊還有每個cpu的子節點,memory節點下邊定義的就是memory的起始地址及大小,所以根節點的#address-cells和#size-cells屬性實際上說明的就是從cpu角度看系統總線的地址長度和大小。
規范中還寫根節點下邊必須有一個epapr-version屬性用來描述設備樹的版本,實際上在linux中根本不用這個屬性。
?
評論
查看更多