Linux 內核:設備樹(3)把device_node轉換成platfrom_device
背景
在上一節中講到設備樹dtb文件中的各個節點轉換成device_node
的過程(《dtb轉換成device_node 》),每個設備樹子節點都將轉換成一個對應的device_node節點。
設備樹dts文件最終在linux內核中會轉化成platform_device:dts
-> dtb
->device_node
-> platform_device
那么,接下來,我們就來看看linux內核如何把device_node轉換成platfrom_device。
原文(有刪改):https://www.cnblogs.com/downey-blog/p/10486568.html
基於arm平台,linux4.14
設備樹對於驅動
設備樹的產生就是為了替代driver中過多的platform_device部分的靜態定義,將硬件資源抽象出來,由系統統一解析,這樣就可以避免各驅動中對硬件資源大量的重復定義。
這樣一來,幾乎可以肯定的是,設備樹中的節點最終目標是轉換成platform device結構,在驅動開發時就只需要添加相應的platform driver部分進行匹配即可。
device_node轉換為platform_device是有條件的
首先,對於所有的device_node,如果要轉換成platform_device,除了節點中必須有compatible
屬性以外,必須滿足以下條件:
-
一般情況下,只對設備樹中根的第1級節點(
/xx
)注冊成platform device
,也就是對它們的子節點(/xx/*
)並不處理。 -
如果一個結點的
compatile
屬性含有這些特殊的值("simple-bus","simple-mfd","isa","arm,amba-bus")之一,並且自己成功注冊成了platform_device,, 那么它的子結點(需含compatile屬性)也可以轉換為platform_device
(當成總線看待)。 -
根節點(
/
)是例外的,生成platfrom_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來訪問設備樹中的信息。
platform_device轉換的開始
of_platform_default_populate_init
函數的執行入口是,在系統啟動的早期進行的of_platform_default_populate_init
:
// drivers/of/platform.c
static int __init of_platform_default_populate_init(void)
{
struct device_node *node;
if (!of_have_populated_dt())
return -ENODEV;
/*
* Handle ramoops explicitly, since it is inside /reserved-memory,
* which lacks a "compatible" property.
*/
node = of_find_node_by_path("/reserved-memory");
if (node) {
node = of_find_compatible_node(node, NULL, "ramoops");
if (node)
of_platform_device_create(node, NULL, NULL);
}
/* Populate everything else. */
of_platform_default_populate(NULL, NULL, NULL);
return 0;
}
arch_initcall_sync(of_platform_default_populate_init);
在函數of_platform_default_populate_init()
中,調用了of_platform_default_populate(NULL, NULL, NULL);
,傳入三個空指針:
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_default_bus_match_table
of_default_bus_match_table
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 */
};
如果節點的屬性值為 "simple-bus","simple-mfd","isa","arm,amba-bus "之一的話,那么它子節點就可以轉化成platform_device。
of_platform_populate
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
struct device_node *child;
int rc = 0;
// 從設備樹中獲取根節點的device_node結構體
root = root ? of_node_get(root) : of_find_node_by_path("/");
if (!root)
return -EINVAL;
pr_debug("%s()\n", __func__);
pr_debug(" starting at: %pOF\n", root);
//遍歷所有的子節點
for_each_child_of_node(root, child) {
// 然后對每個根目錄下的一級子節點 創建 bus
// 例如, /r1 , /r2,而不是 /r1/s1
rc = of_platform_bus_create(child, matches, lookup, parent, true);
if (rc) {
of_node_put(child);
break;
}
}
of_node_set_flag(root, OF_POPULATED_BUS);
of_node_put(root);
return rc;
}
EXPORT_SYMBOL_GPL(of_platform_populate);
在調用of_platform_populate()
時傳入了的matches
參數是of_default_bus_match_table[]
;
這個table是一個靜態數組,這個靜態數組中定義了一系列的compatible屬性:"simple-bus"
、"simple-mfd"
、"isa"
、"arm,amba-bus"
。
按照我們上文中的描述,當某個根節點下的一級子節點的compatible屬性為這些屬性其中之一時,它的一級子節點也將由device_node轉換成platform_device.
到底是不是這樣呢?接着往下看。
of_platform_bus_create
/**
* of_platform_bus_create() - Create a device for a node and its children.
* @bus: device node of the bus to instantiate
* @matches: match table for bus nodes
* @lookup: auxdata table for matching id and platform_data with device nodes
* @parent: parent for new device, or NULL for top level.
* @strict: require compatible property
*
* Creates a platform_device for the provided device_node, and optionally
* recursively create devices for all the child nodes.
*/
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)
{
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;
// ...
// 創建of_platform_device、賦予私有數據
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
// 判斷當前節點的compatible屬性是否包含上文中compatible靜態數組中的元素
// 如果不包含,函數返回0,即,不處理子節點。
if (!dev || !of_match_node(matches, bus))
return 0;
for_each_child_of_node(bus, child) {
pr_debug(" create child: %pOF\n", child);
// 創建 of_platform_bus
/*
如果當前compatible屬性中包含靜態數組中的元素,
即當前節點的compatible屬性有"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"其中一項,
把子節點當作對應的總線來對待,遞歸地對當前節點調用`of_platform_bus_create()`
即,將符合條件的子節點轉換為platform_device結構。
*/
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
if (rc) {
of_node_put(child);
break;
}
}
of_node_set_flag(bus, OF_POPULATED_BUS);
return rc;
}
of_platform_device_create_pdata
這個函數實現了:創建of_platform_device、賦予私有數據
此時的參數platform_data為NULL。
/**
* of_platform_device_create_pdata - Alloc, initialize and register an of_device
* @np: pointer to node to create device for
* @bus_id: name to assign device
* @platform_data: pointer to populate platform_data pointer with
* @parent: Linux device model parent device.
*
* Returns pointer to created platform device, or NULL if a device was not
* registered. Unavailable devices will not get registered.
*/
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;
// ...
// 創建實例,將傳入的device_node鏈接到成員:dev.of_node中
dev = of_device_alloc(np, bus_id, parent);
if (!dev)
goto err_clear_flag;
// 登記到 platform 中
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
of_msi_configure(&dev->dev, dev->dev.of_node);
// 添加當前生成的platform_device。
if (of_device_add(dev) != 0) {
platform_device_put(dev);
goto err_clear_flag;
}
return dev;
err_clear_flag:
of_node_clear_flag(np, OF_POPULATED);
return NULL;
}
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中。
最終,我們能夠通過在自己的驅動中,使用
struct device_node *node = pdev->dev.of_node;
獲取到設備樹節點中的數據。
of_device_add
int of_device_add(struct platform_device *ofdev){
// ...
return device_add(&ofdev->dev);
}
將當前platform_device
中的struct device成員注冊到系統device中,並為其在用戶空間創建相應的訪問節點。
這一步會調用platform_match
,因此最終也會執行設備樹的match,以及probe。
總結
總的來說,將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()