如下討論基於linux4.14,arm平臺html
設備樹的產生就是爲了替代driver中過多的platform_device部分的靜態定義,將硬件資源抽象出來,由系通通一解析,這樣就能夠避免各驅動中對硬件資源大量的重複定義,這樣一來,幾乎能夠確定的是,設備樹中的節點最終目標是轉換成platform device結構,在驅動開發時就只須要添加相應的platform driver部分進行匹配便可。node
在上一節中講到設備樹dtb文件中的各個節點轉換成device_node的過程,每一個設備樹子節點都將轉換成一個對應的device_node節點,那麼:linux
是否是每一個由設備樹節點轉換而來的device_node結構體都將轉換成對應的?數組
首先,對於全部的device_node,若是要轉換成platform_device,必須知足如下條件:app
通常狀況下,只對設備樹中根的子節點進行轉換,也就是子節點的子節點並不處理。可是存在一種特殊狀況,就是當某個根子節點的compatible屬性爲"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"時,當前節點中的子節點將會被轉換成platform_device節點。函數
節點中必須有compatible屬性。post
若是是device_node轉換成platform device,這個轉換過程又是怎麼樣的呢?debug
在老版本的內核中,platform_device部分是靜態定義的,其實最主要的部分就是resources部分,這一部分描述了當前驅動須要的硬件資源,通常是IO,中斷等資源。指針
在設備樹中,這一類資源一般經過reg屬性來描述,中斷則經過interrupts來描述,因此,設備樹中的reg和interrupts資源將會被轉換成platform_device內的struct resources資源。code
那麼,設備樹中其餘屬性是怎麼轉換的呢?答案是:不須要轉換,在platform_device中有一個成員struct device dev,這個dev中又有一個指針成員struct device_node *of_node,linux的作法就是將這個of_node指針直接指向由設備樹轉換而來的device_node結構。
例如,有這麼一個struct platform_device* of_test.咱們能夠直接經過of_test->dev.of_node來訪問設備樹中的信息.
設備樹節點到device_node的轉換參考一篇博客:設備樹dtb到device_node的轉換.
大致流程講完了,接下來從源代碼中進行求證。
事實上,若是從C語言的開始函數start_kernel進行追溯,是找不到platform device這一部分轉換的源頭的,事實上,這個轉換過程的函數是of_platform_default_populate_init(),它被調用的方式是這樣一個聲明:
arch_initcall_sync(of_platform_default_populate_init);
在linux中,同系列的調用聲明還有:
#define pure_initcall(fn) __define_initcall(fn, 0) #define core_initcall(fn) __define_initcall(fn, 1) #define core_initcall_sync(fn) __define_initcall(fn, 1s) #define postcore_initcall(fn) __define_initcall(fn, 2) #define postcore_initcall_sync(fn) __define_initcall(fn, 2s) #define arch_initcall(fn) __define_initcall(fn, 3) #define arch_initcall_sync(fn) __define_initcall(fn, 3s) #define subsys_initcall(fn) __define_initcall(fn, 4) #define subsys_initcall_sync(fn) __define_initcall(fn, 4s) #define fs_initcall(fn) __define_initcall(fn, 5) #define fs_initcall_sync(fn) __define_initcall(fn, 5s) #define rootfs_initcall(fn) __define_initcall(fn, rootfs) #define device_initcall(fn) __define_initcall(fn, 6) #define device_initcall_sync(fn) __define_initcall(fn, 6s) #define late_initcall(fn) __define_initcall(fn, 7) #define late_initcall_sync(fn) __define_initcall(fn, 7s)
這些宏最終都是調用__define_initcall(fn, n),這個數字表明系統啓動時被調用的優先級,數字越小,優先級越低,用這一系列宏聲明一個新的函數就是將這個函數指針放入內存中一個指定的段內。
#define __define_initcall(fn, id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" #id ".init"))) = fn;
即放入到".initcalln.init"中,n表明優先級,當系統啓動時,會依次調用這些段中的函數。
(詳細瞭解linux的initcall機制能夠參考個人另外一篇博客:linux的initcall機制)
下面咱們就進入到of_platform_default_populate_init()中,查看它的執行過程:
static int __init of_platform_default_populate_init(void) { ... of_platform_default_populate(NULL, NULL, NULL); ... }
在函數of_platform_default_populate_init()中,調用了of_platform_default_populate(NULL, NULL, NULL);,傳入三個空指針:
const struct of_device_id of_default_bus_match_table[] = { { .compatible = "simple-bus", }, { .compatible = "simple-mfd", }, { .compatible = "isa", }, #ifdef CONFIG_ARM_AMBA { .compatible = "arm,amba-bus", }, #endif /* CONFIG_ARM_AMBA */ {} /* Empty terminated list */ }; int of_platform_default_populate(struct device_node *root,const struct of_dev_auxdata *lookup,struct device *parent) { return of_platform_populate(root, of_default_bus_match_table, lookup, parent); }
of_platform_default_populate()調用了of_platform_populate()。
須要注意的是,在調用of_platform_populate()時傳入了參數of_default_bus_match_table[],這個table是一個靜態數組,這個靜態數組中定義了一系列的compatible屬性:"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"。
按照咱們上文中的描述,當某個根節點下的一級子節點的compatible屬性爲這些屬性其中之一時,它的一級子節點也將由device_node轉換成platform_device.
究竟是不是這樣呢?接着往下看:
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); if (rc) { of_node_put(child); break; } } ... }
首先,從設備樹中獲取根節點的device_node結構體,而後對每一個根目錄下的一級子節點調用of_platform_bus_create(),從命名上來看,這部分解析的目的是創建各個bus的platform_device結構,須要注意的是對於of_platform_bus_create(child, matches, lookup, parent, true),matchs參數是上文中提到的compatible靜態數組,而lookup和parent依舊爲NULL。
接着跟蹤代碼:
static int of_platform_bus_create(struct device_node *bus,const struct of_device_id *matches,const struct of_dev_auxdata *lookup,struct device *parent, bool strict) { dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); if (!dev || !of_match_node(matches, bus)) return 0; for_each_child_of_node(bus, child) { pr_debug(" create child: %pOF\n", child); rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); if (rc) { of_node_put(child); break; } } ... }
對於節點的轉換,是由of_platform_device_create_pdata(bus, bus_id, platform_data, parent)函數來實現的。
緊接着,在第二行的函數調用中,判斷of_match_node(matches,bus)函數的返回值,這個matchs就是compatible的靜態數組,這個函數的做用就是判斷當前節點的compatible屬性是否包含上文中compatible靜態數組中的元素,若是不包含,函數返回。
若是當前compatible屬性中包含靜態數組中的元素,即當前節點的compatible屬性有"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"其中一項,遞歸地對當前節點調用of_platform_bus_create(),即將符合條件的子節點轉換爲platform_device結構。
關於節點轉換的細節部分咱們接着跟蹤of_platform_device_create_pdata(bus, bus_id, platform_data, parent)函數,此時的參數platform_data爲NULL。
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; dev = of_device_alloc(np, bus_id, parent); dev->dev.bus = &platform_bus_type; dev->dev.platform_data = platform_data; if (of_device_add(dev) != 0) { platform_device_put(dev); goto err_clear_flag; } }
struct platform_device終於現出了真身,在這個函數調用中,顯示申請並初始化一個platform_device結構體,將傳入的device_node連接到成員:dev.fo_node中
賦值bus成員和platform_data成員,platform_data成員爲NULL。
再使用of_device_add()將當前生成的platform_device添加到系統中。
對於of_platform_device_create_pdata()函數中的實現,咱們須要逐一講解其中的函數實現:
struct platform_device *of_device_alloc(struct device_node *np,const char *bus_id,struct device *parent) { //統計reg屬性的數量 while (of_address_to_resource(np, num_reg, &temp_res) == 0) num_reg++; //統計中斷irq屬性的數量 num_irq = of_irq_count(np); //根據num_irq和num_reg的數量申請相應的struct resource內存空間。 if (num_irq || num_reg) { res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL); if (!res) { platform_device_put(dev); return NULL; } //設置platform_device中的num_resources成員 dev->num_resources = num_reg + num_irq; //設置platform_device中的resource成員 dev->resource = res; //將device_node中的reg屬性轉換成platform_device中的struct resource成員。 for (i = 0; i < num_reg; i++, res++) { rc = of_address_to_resource(np, i, res); WARN_ON(rc); } //將device_node中的irq屬性轉換成platform_device中的struct resource成員。 if (of_irq_to_resource_table(np, res, num_irq) != num_irq) pr_debug("not all legacy IRQ resources mapped for %s\n", np->name); } //將platform_device的dev.of_node成員指針指向device_node。 dev->dev.of_node = of_node_get(np); //將platform_device的dev.fwnode成員指針指向device_node的fwnode成員。 dev->dev.fwnode = &np->fwnode; //設備parent爲platform_bus dev->dev.parent = parent ? : &platform_bus; }
首先,函數先統計設備樹中reg屬性和中斷irq屬性的個數,而後分別爲它們申請內存空間,鏈入到platform_device中的struct resources成員中。除了設備樹中"reg"和"interrupt"屬性以外,還有可選的"reg-names"和"interrupt-names"這些io中斷資源相關的設備樹節點屬性也在這裏被轉換。
將相應的設備樹節點生成的device_node節點鏈入到platform_device的dev.of_node中。
int of_device_add(struct platform_device *ofdev){ ... return device_add(&ofdev->dev); }
將當前platform_device中的struct device成員註冊到系統device中,併爲其在用戶空間建立相應的訪問節點。
總的來講,將device_node轉換爲platform_device的過程仍是比較簡單的。
整個轉換過程的函數調用關係是這樣的:
of_platform_default_populate_init() | of_platform_default_populate(); | of_platform_populate(); | of_platform_bus_create() _____________________|_________________ | | of_platform_device_create_pdata() of_platform_bus_create() _________________|____________________ | | of_device_alloc() of_device_add()
好了,關於linux設備樹中device_node到platform_device的轉換過程的討論就到此爲止啦,若是朋友們對於這個有什麼疑問或者發現有文章中有什麼錯誤,歡迎留言
原創博客,轉載請註明出處!
祝各位早日實現項目叢中過,bug不沾身.