DT_MACHINE_START 板級信息初始化匹配調用機制實現

本文轉自:https://www.cnblogs.com/targethero/p/5086085.html

一個最簡單的設備樹必須包含根節點,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("\nError: unrecognized/unsupported "
232                 "device tree compatible list:\n[ ");
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("]\n\n");
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 <[email protected]> */
321     /* Maintainer: Kukjin Kim <[email protected]> */
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中根本不用這個屬性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章