1、前言html
Device Tree總共有三篇,分別是:node
一、爲什麼要引入Device Tree,這個機制是用來解決什麼問題的?(請參考引入Device Tree的緣由)linux
二、Device Tree的基礎概念(請參考DT基礎概念)c++
三、ARM linux中和Device Tree相關的代碼分析(這是本文的主題)算法
本文主要內容是:以Device Tree相關的數據流分析爲索引,對ARM linux kernel的代碼進行解析。主要的數據流包括:數組
一、初始化流程。也就是掃描dtb並將其轉換成Device Tree Structure。數據結構
二、傳遞運行時參數傳遞以及platform的識別流程分析tcp
三、如何將Device Tree Structure併入linux kernel的設備驅動模型。函數
注:本文中的linux kernel使用的是3.14版本。佈局
2、如何經過Device Tree完成運行時參數傳遞以及platform的識別功能?
一、彙編部分的代碼分析
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代碼進行處理。
二、和device tree相關的setup_arch代碼分析
具體的c代碼都是在setup_arch中處理,這個函數是一個總的入口點。具體代碼以下(刪除了部分無關代碼):
void __init setup_arch(char **cmdline_p) { const struct machine_desc *mdesc; …… mdesc = setup_machine_fdt(__atags_pointer); if (!mdesc) mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type); machine_desc = mdesc; machine_name = mdesc->name; …… }
對於如何肯定HW platform這個問題,舊的方法是靜態定義若干的machine描述符(struct machine_desc ),在啓動過程當中,經過machine type ID做爲索引,在這些靜態定義的machine描述符中掃描,找到那個ID匹配的描述符。在新的內核中,首先使用setup_machine_fdt來setup machine描述符,若是返回NULL,才使用傳統的方法setup_machine_tags來setup machine描述符。傳統的方法須要給出__machine_arch_type(bootloader經過r1寄存器傳遞給kernel的)和tag list的地址(用來進行tag parse)。__machine_arch_type用來尋找machine描述符;tag list用於運行時參數的傳遞。隨着內核的不斷髮展,相信有一天linux kernel會徹底拋棄tag list的機制。
三、匹配platform(machine描述符)
setup_machine_fdt函數的功能就是根據Device Tree的信息,找到最適合的machine描述符。具體代碼以下:
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys) { const struct machine_desc *mdesc, *mdesc_best = NULL; if (!dt_phys || !early_init_dt_scan(phys_to_virt(dt_phys))) return NULL; mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); if (!mdesc) { 出錯處理 } /* Change machine number to match the mdesc we're using */ __machine_arch_type = mdesc->nr; return mdesc; }
early_init_dt_scan函數有兩個功能,一個是爲後續的DTB scan進行準備工做,另一個是運行時參數傳遞。具體請參考下面一個section的描述。
of_flat_dt_match_machine是在machine描述符的列表中scan,找到最合適的那個machine描述符。咱們首先看如何組成machine描述符的列表。和傳統的方法相似,也是靜態定義的。DT_MACHINE_START和MACHINE_END用來定義一個machine描述符。編譯的時候,compiler會把這些machine descriptor放到一個特殊的段中(.arch.info.init),造成machine描述符的列表。machine描述符用下面的數據結構來標識(刪除了不相關的member):
struct machine_desc { unsigned int nr; /* architecture number */ const char *const *dt_compat; /* array of device tree 'compatible' strings */ …… };
nr成員就是過去使用的machine type ID。內核machine描述符的table有若干個entry,每一個都有本身的ID。bootloader傳遞了machine type ID,指明使用哪個machine描述符。目前匹配machine描述符使用compatible strings,也就是dt_compat成員,這是一個string list,定義了這個machine所支持的列表。在掃描machine描述符列表的時候須要不斷的獲取下一個machine描述符的compatible字符串的信息,具體的代碼以下:
static const void * __init arch_get_next_mach(const char *const **match) { static const struct machine_desc *mdesc = __arch_info_begin; const struct machine_desc *m = mdesc; if (m >= __arch_info_end) return NULL; mdesc++; *match = m->dt_compat; return m; }
__arch_info_begin指向machine描述符列表第一個entry。經過mdesc++不斷的移動machine描述符指針(Note:mdesc是static的)。match返回了該machine描述符的compatible string list。具體匹配的算法卻是很簡單,就是比較字符串而已,一個是root node的compatible字符串列表,一個是machine描述符的compatible字符串列表,得分最低的(最匹配的)就是咱們最終選定的machine type。
四、運行時參數傳遞
運行時參數是在掃描DTB的chosen node時候完成的,具體的動做就是獲取chosen node的bootargs、initrd等屬性的value,並將其保存在全局變量(boot_command_line,initrd_start、initrd_end)中。使用tag list方法是相似的,經過分析tag list,獲取相關信息,保存在一樣的全局變量中。具體代碼位於early_init_dt_scan函數中:
bool __init early_init_dt_scan(void *params) { if (!params) return false; /* 全局變量initial_boot_params指向了DTB的header*/ initial_boot_params = params; /* 檢查DTB的magic,確認是一個有效的DTB */ if (be32_to_cpu(initial_boot_params->magic) != OF_DT_HEADER) { initial_boot_params = NULL; return false; } /* 掃描 /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); return true; }
設定meminfo(該全局變量肯定了物理內存的佈局)有若干種途徑:
一、經過tag list(tag是ATAG_MEM)傳遞memory bank的信息。
二、經過command line(能夠用tag list,也能夠經過DTB)傳遞memory bank的信息。
三、經過DTB的memory node傳遞memory bank的信息。
目前固然是推薦使用Device Tree的方式來傳遞物理內存佈局信息。
3、初始化流程
在系統初始化的過程當中,咱們須要將DTB轉換成節點是device_node的樹狀結構,以便後續方便操做。具體的代碼位於setup_arch->unflatten_device_tree中。
void __init unflatten_device_tree(void) { __unflatten_device_tree(initial_boot_params, &of_allnodes, early_init_dt_alloc_memory_arch); /* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */ of_alias_scan(early_init_dt_alloc_memory_arch); }
咱們用struct device_node 來抽象設備樹中的一個節點,具體解釋以下:
struct device_node { const char *name;----------------------device node name const char *type;-----------------------對應device_type的屬性 phandle phandle;-----------------------對應該節點的phandle屬性 const char *full_name; ----------------從「/」開始的,表示該node的full path struct property *properties;-------------該節點的屬性列表 struct property *deadprops; ----------若是須要刪除某些屬性,kernel並不是真的刪除,而是掛入到deadprops的列表 struct device_node *parent;------parent、child以及sibling將全部的device node鏈接起來 struct device_node *child; struct device_node *sibling; struct device_node *next; --------經過該指針能夠獲取相同類型的下一個node struct device_node *allnext;-------經過該指針能夠獲取node global list下一個node struct proc_dir_entry *pde;--------開放到userspace的proc接口信息 struct kref kref;-------------該node的reference count unsigned long _flags; void *data; };
unflatten_device_tree函數的主要功能就是掃描DTB,將device node被組織成:
一、global list。全局變量struct device_node *of_allnodes就是指向設備樹的global list
二、tree。
這些功能主要是在__unflatten_device_tree函數中實現,具體代碼以下(去掉一些可有可無的代碼):
static void __unflatten_device_tree(struct boot_param_header *blob,---須要掃描的DTB struct device_node **mynodes,---------global list指針 void * (*dt_alloc)(u64 size, u64 align))------內存分配函數 { unsigned long size; void *start, *mem; struct device_node **allnextp = mynodes; 此處刪除了health check代碼,例如檢查DTB header的magic,確認blob的確指向一個DTB。 /* scan過程分紅兩輪,第一輪主要是肯定device-tree structure的長度,保存在size變量中 */ start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct); size = (unsigned long)unflatten_dt_node(blob, 0, &start, NULL, NULL, 0); size = ALIGN(size, 4); /* 初始化的時候,並非掃描到一個node或者property就分配相應的內存,實際上內核是一次性的分配了一大片內存,這些內存包括了全部的struct device_node、node name、struct property所須要的內存。*/ mem = dt_alloc(size + 4, __alignof__(struct device_node)); memset(mem, 0, size); *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef); //用來檢驗後面unflattening是否溢出 /* 這是第二輪的scan,第一次scan是爲了獲得保存全部node和property所須要的內存size,第二次就是實打實的要構建device node tree了 */ start = ((void *)blob) + be32_to_cpu(blob->off_dt_struct); unflatten_dt_node(blob, mem, &start, NULL, &allnextp, 0); 此處略去校驗溢出和校驗OF_DT_END。 }
具體的scan是在unflatten_dt_node函數中,若是已經清楚地瞭解DTB的結構,其實代碼很簡單,這裏就再也不細述了。
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。致使Device Tree的引入ARM體系結構的代碼其中一個最重要的緣由的太多的靜態定義的表格。例如:通常代碼中會定義一個static struct platform_device *xxx_devices的靜態數組,在初始化的時候調用platform_add_devices。這些靜態定義的platform_device每每又須要靜態定義各類resource,這致使靜態表格進一步增大。若是ARM linux中再也不定義這些表格,那麼必定須要一個轉換的過程,也就是說,系統應該會根據Device tree來動態的增長系統中的platform_device。固然,這個過程並不是只是發生在platform bus上(具體能夠參考「Platform Device」的設備),也可能發生在其餘的非即插即用的bus上,例如AMBA總線、PCI總線。一言以蔽之,若是要併入linux kernel的設備驅動模型,那麼就須要根據device_node的樹狀結構(root是of_allnodes)將一個個的device node掛入到相應的總線device鏈表中。只要作到這一點,總線機制就會安排device和driver的約會。
固然,也不是全部的device node都會掛入bus上的設備鏈表,好比cpus node,memory node,choose node等。
一、cpus node的處理
這部分的處理能夠參考setup_arch->arm_dt_init_cpu_maps中的代碼,具體的代碼以下:
void __init arm_dt_init_cpu_maps(void) { scan device node global list,尋找full path是「/cpus」的那個device node。cpus這個device node只是一個容器,其中包括了各個cpu node的定義以及全部cpu node共享的property。 cpus = of_find_node_by_path("/cpus"); for_each_child_of_node(cpus, cpu) { 遍歷cpus的全部的child node u32 hwid; if (of_node_cmp(cpu->type, "cpu")) 咱們只關心那些device_type是cpu的node continue; if (of_property_read_u32(cpu, "reg", &hwid)) { 讀取reg屬性的值並賦值給hwid return; } reg的屬性值的8 MSBs必須設置爲0,這是ARM CPU binding定義的。 if (hwid & ~MPIDR_HWID_BITMASK) return; 不容許重複的CPU id,那是一個災難性的設定 for (j = 0; j < cpuidx; j++) if (WARN(tmp_map[j] == hwid, "Duplicate /cpu reg " "properties in the DT\n")) return; 數組tmp_map保存了系統中全部CPU的MPIDR值(CPU ID值),具體的index的編碼規則是: tmp_map[0]保存了booting CPU的id值,其他的CPU的ID值保存在1~NR_CPUS的位置。 if (hwid == mpidr) { i = 0; bootcpu_valid = true; } else { i = cpuidx++; } tmp_map[i] = hwid; } 根據DTB中的信息設定cpu logical map數組。 for (i = 0; i < cpuidx; i++) { set_cpu_possible(i, true); cpu_logical_map(i) = tmp_map[i]; } }
要理解這部分的內容,須要理解ARM CUPs binding的概念,能夠參考linux/Documentation/devicetree/bindings/arm目錄下的CPU.txt文件的描述。
二、memory的處理
這部分的處理能夠參考setup_arch->setup_machine_fdt->early_init_dt_scan->early_init_dt_scan_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_type屬性值 __be32 *reg, *endp; unsigned long l; 在初始化的時候,咱們會對每個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, ®); size = dt_mem_next_cell(dt_root_size_cells, ®); early_init_dt_add_memory_arch(base, size); 將具體的memory block信息加入到內核中。 } return 0; }
三、interrupt controller的處理
初始化是經過start_kernel->init_IRQ->machine_desc->init_irq()實現的。咱們用S3C2416爲例來描述interrupt controller的處理過程。下面是machine描述符的定義。
DT_MACHINE_START(S3C2416_DT, "Samsung S3C2416 (Flattened Device Tree)") …… .init_irq = irqchip_init, …… MACHINE_END
在driver/irqchip/irq-s3c24xx.c文件中定義了兩個interrupt controller,以下:
IRQCHIP_DECLARE(s3c2416_irq, "samsung,s3c2416-irq", s3c2416_init_intc_of); IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of);
固然,系統中能夠定義更多的irqchip,不過具體用哪個是根據DTB中的interrupt controller node中的compatible屬性肯定的。在driver/irqchip/irqchip.c文件中定義了irqchip_init函數,以下:
void __init irqchip_init(void) { of_irq_init(__irqchip_begin); }
__irqchip_begin就是全部的irqchip的一個列表,of_irq_init函數是遍歷Device Tree,找到匹配的irqchip。具體的代碼以下:
void __init of_irq_init(const struct of_device_id *matches) { struct device_node *np, *parent = NULL; struct intc_desc *desc, *temp_desc; struct list_head intc_desc_list, intc_parent_list; INIT_LIST_HEAD(&intc_desc_list); INIT_LIST_HEAD(&intc_parent_list); 遍歷全部的node,尋找定義了interrupt-controller屬性的node,若是定義了interrupt-controller屬性則說明該node就是一箇中斷控制器。 for_each_matching_node(np, matches) { if (!of_find_property(np, "interrupt-controller", NULL) || !of_device_is_available(np)) continue; 分配內存並掛入鏈表,固然還有根據interrupt-parent創建controller之間的父子關係。對於interrupt controller,它也多是一個樹狀的結構。 desc = kzalloc(sizeof(*desc), GFP_KERNEL); if (WARN_ON(!desc)) goto err; 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);-----匹配並初始化 if (WARN(!match->data,----------match->data是初始化函數 "of_irq_init: no init function for %s\n", match->compatible)) { kfree(desc); continue; } irq_init_cb = (of_irq_init_cb_t)match->data; ret = irq_init_cb(desc->dev, desc->interrupt_parent);-----執行初始化函數 if (ret) { kfree(desc); continue; } 處理完的節點放入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_or_null(&intc_parent_list, typeof(*desc), list); if (!desc) { pr_err("of_irq_init: children remain, but no parents\n"); break; } list_del(&desc->list); parent = desc->dev; kfree(desc); } list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) { list_del(&desc->list); kfree(desc); } err: list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) { list_del(&desc->list); kfree(desc); } }
只有該node中有interrupt-controller這個屬性定義,那麼linux kernel就會分配一個interrupt controller的描述符(struct intc_desc)並掛入隊列。經過interrupt-parent屬性,能夠肯定各個interrupt controller的層次關係。在scan了全部的Device Tree中的interrupt controller的定義以後,系統開始匹配過程。一旦匹配到了interrupt chip列表中的項次後,就會調用相應的初始化函數。若是CPU是S3C2416的話,匹配到的是irqchip的初始化函數是s3c2416_init_intc_of。
OK,咱們已經經過compatible屬性找到了適合的interrupt controller,那麼如何解析reg屬性呢?咱們知道,對於s3c2416的interrupt controller而言,其#interrupt-cells的屬性值是4,定義爲。每一個域的解釋以下:
(1)ctrl_num表示使用哪種類型的interrupt controller,其值的解釋以下:
- 0 ... main controller
- 1 ... sub controller
- 2 ... second main controller
(2)parent_irq。對於sub controller,parent_irq標識了其在main controller的bit position。
(3)ctrl_irq標識了在controller中的bit位置。
(4)type標識了該中斷的trigger type,例如:上升沿觸發仍是電平觸發。
爲了更順暢的描述後續的代碼,我須要簡單的介紹2416的中斷控制器,其block diagram以下:
53個Samsung2416的中斷源被分紅兩種類型,一種是須要sub寄存器進行控制的,例如DMA,系統中的8個DMA中斷是經過兩級識別的,先在SRCPND寄存器中獲得是DMA中斷的信息,具體是哪個channel的DMA中斷須要繼續查詢SUBSRC寄存器。那些不須要sub寄存器進行控制的,例如timer,5個timer的中斷能夠直接從SRCPND中獲得。
中斷MASK寄存器能夠控制產生的中斷是否要報告給CPU,當一箇中斷被mask的時候,雖然SRCPND寄存器中,硬件會set該bit,可是不會影響到INTPND寄存器,從而不會向CPU報告該中斷。對於SUBMASK寄存器,若是該bit被set,也就是該sub中斷被mask了,那麼即使產生了對應的sub中斷,也不會修改SRCPND寄存器的內容,只是修改SUBSRCPND中寄存器的內容。
不過隨着硬件的演化,更多的HW block加入到SOC中,這使得中斷源不夠用了,所以中斷寄存器又被分紅兩個group,一個是group 1(開始地址是0X4A000000,也就是main controller了),另一個是group2(開始地址是0X4A000040,叫作second main controller)。group 1中的sub寄存器的起始地址是0X4A000018(也就是sub controller)。
瞭解了上面的內容後,下面的定義就比較好理解了:
static struct s3c24xx_irq_of_ctrl s3c2416_ctrl[] = { { .name = "intc", -----------main controller .offset = 0, }, { .name = "subintc", ---------sub controller .offset = 0x18, .parent = &s3c_intc[0], }, { .name = "intc2", ----------second main controller .offset = 0x40, } };
對於s3c2416而言,irqchip的初始化函數是s3c2416_init_intc_of,s3c2416_ctrl做爲參數傳遞給了s3c_init_intc_of,大部分的處理都是在s3c_init_intc_of函數中完成的,因爲這個函數和中斷子系統很是相關,這裏就不詳述了,後續會有一份專門的文檔描述之。
四、GPIO controller的處理
暫不描述,後續會有一份專門的文檔描述GPIO sub system。
五、machine初始化
machine初始化的代碼能夠沿着start_kernel->rest_init->kernel_init->kernel_init_freeable->do_basic_setup->do_initcalls路徑尋找。在do_initcalls函數中,kernel會依次執行各個initcall函數,在這個過程當中,會調用customize_machine,具體以下:
static int __init customize_machine(void) { if (machine_desc->init_machine) machine_desc->init_machine(); else of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL); return 0; } arch_initcall(customize_machine);
在這個函數中,通常會調用machine描述符中的init_machine callback函數來把各類Device Tree中定義的platform device設備節點加入到系統(即platform bus的全部的子節點,對於device tree中其餘的設備節點,須要在各自bus controller初始化的時候自行處理)。若是machine描述符中沒有定義init_machine函數,那麼直接調用of_platform_populate把全部的platform device加入到kernel中。對於s3c2416,其machine描述符中的init_machine callback函數就是s3c2416_dt_machine_init,代碼以下:
static void __init s3c2416_dt_machine_init(void) { of_platform_populate(NULL, --------傳入NULL參數表示從root node開始scan of_default_bus_match_table, s3c2416_auxdata_lookup, NULL); s3c_pm_init(); --------power management相關的初始化 }
因而可知,最終生成platform device的代碼來自of_platform_populate函數。該函數的邏輯比較簡單,遍歷device node global list中全部的node,並調用of_platform_bus_create處理,of_platform_bus_create函數代碼以下:
static int of_platform_bus_create(struct device_node *bus,-------------要建立的那個device node const struct of_device_id *matches,-------要匹配的list const struct of_dev_auxdata *lookup,------附屬數據 struct device *parent, bool strict)---------------parent指向父節點。strict是否要求徹底匹配 { const struct of_dev_auxdata *auxdata; struct device_node *child; struct platform_device *dev; const char *bus_id = NULL; void *platform_data = NULL; int rc = 0; 刪除確保device node有compatible屬性的代碼。 auxdata = of_dev_lookup(lookup, bus); 在傳入的lookup table尋找和該device node匹配的附加數據 if (auxdata) { bus_id = auxdata->name;-----------------若是找到,那麼就用附加數據中的靜態定義的內容 platform_data = auxdata->platform_data; } ARM公司提供了CPU core,除此以外,它設計了AMBA的總線來鏈接SOC內的各個block。符合這個總線標準的SOC上的外設叫作ARM Primecell Peripherals。若是一個device node的compatible屬性值是arm,primecell的話,能夠調用of_amba_device_create來向amba總線上增長一個amba device。 if (of_device_is_compatible(bus, "arm,primecell")) { of_amba_device_create(bus, bus_id, platform_data, parent); return 0; } 若是不是ARM Primecell Peripherals,那麼咱們就須要向platform bus上增長一個platform device了 dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); if (!dev || !of_match_node(matches, bus)) return 0; 一個device node多是一個橋設備,所以要重複調用of_platform_bus_create來把全部的device node處理掉。 for_each_child_of_node(bus, child) { pr_debug(" create child: %s\n", child->full_name); rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); if (rc) { of_node_put(child); break; } } return rc; }
具體增長platform device的代碼在of_platform_device_create_pdata中,代碼以下:
static struct platform_device *of_platform_device_create_pdata( struct device_node *np, const char *bus_id, void *platform_data, struct device *parent) { struct platform_device *dev; if (!of_device_is_available(np))---------check status屬性,確保是enable或者OK的。 return NULL; of_device_alloc除了分配struct platform_device的內存,還分配了該platform device須要的resource的內存(參考struct platform_device 中的resource成員)。固然,這就須要解析該device node的interrupt資源以及memory address資源。 dev = of_device_alloc(np, bus_id, parent); if (!dev) return NULL; 設定platform_device 中的其餘成員 dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); if (!dev->dev.dma_mask) dev->dev.dma_mask = &dev->dev.coherent_dma_mask; dev->dev.bus = &platform_bus_type; dev->dev.platform_data = platform_data; if (of_device_add(dev) != 0) {------------------把這個platform device加入統一設備模型系統中 platform_device_put(dev); return NULL; } return dev; }