原文網址:http://www.cnblogs.com/biglucky/p/4057495.htmlhtml
Linux kernel 是怎麼將 devicetree中的內容生成plateform_device
1,實現場景(以Versatile Express V2M爲例說明其過程)
以arch/arm/mach-vexpress/v2m.c 爲例,在該文件中的v2m_dt_init函數的做用就是利用 dt(device tree)結構初始化 platform device。
static void __init v2m_dt_init(void)
{
of_platform_populate(NULL, of_default_bus_match_table,
v2m_dt_auxdata_lookup, NULL);
…...
}
of_platform_populate 實如今 drivers/of/platform.c,是 OF 的標準函數。調用of_platform_populate把全部的platform device加入到kernel中。
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
root = root ? of_node_get(root) : of_find_node_by_path("/");
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true);
}
…...
}
在of_platform_populate()中若是 root 爲 NULL,則將 root 賦值爲根節點,這個根節點是用of_find_node_by_path()取到的。
struct device_node *of_find_node_by_path(const char *path)
{
struct device_node *np = allnodes;
read_lock(&devtree_lock);
for (; np; np = np->allnext) {
if (np->full_name && (of_node_cmp(np->full_name, path) == 0)
&& of_node_get(np))
break;
}
read_unlock(&devtree_lock);
return np;
}
在這個函數中有一個很關鍵的全局變量:allnodes,它的定義是在 drivers/of/base.c 裏面:struct device_node *allnodes;
這應該所就是那個所謂的「device tree data」了。它應該指向了 device tree 的根節點。問題又來了,這個 allnodes 又是咋來的呢?咱們知道 device tree 是由 DTC(Device Tree Compiler)編譯成二進制文件DTB(Ddevice Tree Blob)的,而後在系統上電以後由 bootloader 加載到內存中去,這個時候尚未device tree,而在內存中只有一個所謂的 DTB,這只是一個以某個內存地址開始的一堆原始的 dt 數據,沒有樹結構。kernel 的任務須要把這些數據轉換成一個樹結構而後再把這棵樹的根節點的地址賦值給allnodes 就好了。這個過程必定是很是重要,由於沒有這個 device tree 那全部的設備就沒辦法初始化,因此這個 dt 樹的造成必定在 kernel 剛剛啓動的時候就完成了。
既然如此,咱們來看看 kernel 初始化的代碼(init/main.c)。
2,鋪墊(初始化device tree)
Kernel/init/main.c
asmlinkage void __init start_kernel(void)
{
setup_arch(&command_line);
}
這個 setup_arch 就是各個架構本身的設置函數,哪一個參與了編譯就調用哪一個,在本文中應當是arch/arm/kernel/setup.c 中的setup_arch()。
Kernel/arch/arm/setup.c
void __init setup_arch(char **cmdline_p)
{
mdesc = setup_machine_fdt(__atags_pointer);
unflatten_device_tree();
}
這個時候 DTB 只是加載到內存中的 .dtb 文件而已,這個文件中不只包含數據結構,還包含了一些文件頭等信息,kernel 須要從這些信息中獲取到數據結構相關的信息,而後再生成設備樹。
struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
struct boot_param_header *devtree;
devtree = phys_to_virt(dt_phys);
initial_boot_params = devtree;
}
phys_to_virt 字面上的意思是物理地址轉換成虛擬地址,那就是說__atags_pointer是一個物理地址,即__atags_pointer 的確是一個指針,再看變量 devtree它指向了一個struct boot_param_header 結構體。隨後 kernel 把這個指針賦給了全局變量initial_boot_params。也就是說之後 kernel 會是用這個指針指向的數據去初始化 device tree。
struct boot_param_header {
__be32 magic; /* magic word OF_DT_HEADER */
__be32 totalsize; /* total size of DT block */
__be32 off_dt_struct; /* offset to structure */
__be32 off_dt_strings; /* offset to strings */
__be32 off_mem_rsvmap; /* offset to memory reserve map */
__be32 version; /* format version */
__be32 last_comp_version; /* last compatible version */
/* version 2 fields below */
__be32 boot_cpuid_phys; /* Physical CPU id we're booting on */
/* version 3 fields below */
__be32 dt_strings_size; /* size of the DT strings block */
/* version 17 fields below */
__be32 dt_struct_size; /* size of the DT structure block */
};
看這個結構體,很像以前所說的文件頭,有魔數、大小、數據結構偏移量、版本等等,kernel 就應該經過這個結構獲取數據,並最終生成設備樹。如今回到setup_arch,果真在隨後的代碼中有這麼一個函數:將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);
}
能夠看到,allnodes 就是在這裏賦值的,device tree 也是在這裏正式開始創建的。
//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; ----------若是須要,刪除某些屬性,並掛入到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被組織成:
(1)global list。全局變量struct device_node *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。
}
到此爲止,device tree 的初始化就算完成了,在之後的啓動過程當中,kernel 就會依據這個 dt 來初始化各個設備。
3,具體建立platform device的過程
接着第一部分的描述:重點剖析 of_platform_bus_create()函數
of_platform_populate 實如今 drivers/of/platform.c,是 OF 的標準函數。
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
root = root ? of_node_get(root) : of_find_node_by_path("/");
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true);
}
…...
}
第一部分和第二部分總共完成了of_find_node_by_path("/")。這裏開始分析函數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;
/* Make sure it has a compatible property */
if (strict && (!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s() - skipping %s, no compatible prop\n",
__func__, bus->full_name);
return 0;
}
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中,代碼以下:
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的內存。固然,這就須要解析該device node的interrupt資源以及memory address資源。*/
dev = of_device_alloc(np, bus_id, parent);
//設定platform_device 中的其餘成員
dev->dev.coherent_dma_mask = DMA_BIT_MASK(sizeof(dma_addr_t) * 8);
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
/* We do not fill the DMA ops for platform devices by default.
* This is currently the responsibility of the platform code
* to do such, possibly using a device notifier
*/
if (of_device_add(dev) != 0) {
platform_device_put(dev); //把這個platform device加入統一設備模型系統中
return NULL;
}
return dev;
}
至此,Linux kernel已經徹底把Device Tree中的內容生成了相對應的platform device。node