設備樹處理之——device_node轉換成platform_device
以下討論基於linux4.14,arm平台
platform device
設備樹的產生就是為了替代driver中過多的platform_device部分的靜態定義,將硬件資源抽象出來,由系統統一解析,這樣就可以避免各驅動中對硬件資源大量的重復定義,這樣一來,幾乎可以肯定的是,設備樹中的節點最終目標是轉換成platform device結構,在驅動開發時就只需要添加相應的platform driver部分進行匹配即可。
在上一節中講到設備樹dtb文件中的各個節點轉換成device_node的過程,每個設備樹子節點都將轉換成一個對應的device_node節點,那么:
是不是每個由設備樹節點轉換而來的device_node結構體都將轉換成對應的?
首先,對於所有的device_node,如果要轉換成platform_device,必須滿足以下條件:
-
一般情況下,只對設備樹中根的子節點進行轉換,也就是子節點的子節點並不處理。但是存在一種特殊情況,就是當某個根子節點的compatible屬性為"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"時,當前節點中的子節點將會被轉換成platform_device節點。
-
節點中必須有compatible屬性。
如果是device_node轉換成platform device,這個轉換過程又是怎么樣的呢?
在老版本的內核中,platform_device部分是靜態定義的,其實最主要的部分就是resources部分,這一部分描述了當前驅動需要的硬件資源,一般是IO,中斷等資源。
在設備樹中,這一類資源通常通過reg屬性來描述,中斷則通過interrupts來描述,所以,設備樹中的reg和interrupts資源將會被轉換成platform_device內的struct resources資源。
那么,設備樹中其他屬性是怎么轉換的呢?答案是:不需要轉換,在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的轉換.
大體流程講完了,接下來從源代碼中進行求證。
platform_device轉換的開始
事實上,如果從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()函數中的實現,我們需要逐一講解其中的函數實現:
of_device_alloc()
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中。
of_device_add
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不沾身.