歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> (DT系列五)Linux kernel 是怎麼將 devicetree中的內容生成plateform_device

(DT系列五)Linux kernel 是怎麼將 devicetree中的內容生成plateform_device

日期:2017/3/3 12:52:32   编辑:Linux技術

原文網址:http://www.cnblogs.com/biglucky/p/4057495.html

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。

Stay hungry, stay foolish!

Copyright © Linux教程網 All Rights Reserved