linux設備驅動程序--設備樹多級子節點的轉換
在上一章:設備樹處理之——device_node轉換成platform_device中,有提到在設備樹的device_node到platform_device轉換中,必須滿足以下條件:
- 一般情況下,只對設備樹中根的一級子節點進行轉換,也就是多級子節點(子節點的子節點)並不處理。但是存在一種特殊情況,就是當某個根子節點的compatible屬性為"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"時,當前節點中的一級子節點將會被轉換成platform_device節點。
- 節點中必須有compatible屬性。
事實上,在設備樹中,通常會存在將描述設備驅動的設備樹節點被放置在多級子節點的情況,比如下面這種情況:
/{
...
i2c@44e0b000 {
compatible = "ti,omap4-i2c";
...
tps@24 {
reg = <0x24>;
compatible = "ti,tps65217";
...
charger {
compatible = "ti,tps65217-charger";
...
};
pwrbutton {
compatible = "ti,tps65217-pwrbutton";
...
};
}
}
...
}
顯然,i2c@44e0b000會被轉換成platform_device,而tps@24、charger、pwrbutton則不會,至少在設備樹初始化階段不會被轉換,仍舊以device_node的形式存在在內存中。
顯而易見,這些設備並非是無意義的設備,那么它們是什么時候生成platform_device的呢?
答案是:由對應根目錄的一級子節點處理。
我們以i2c@44e0b000節點為例,事實上,這個節點對應一個i2c硬件控制器,控制器的起始地址是0x44e0b000,這個節點的作用就是生成一個i2c硬件控制器的platform_device,與同樣被加載到內存中的platform_driver相匹配,在內存中構建一個i2c硬件控制器的描述節點,負責對應i2c控制器的數據收發。
根據設備樹的compatible屬性匹配機制,在內核源代碼中全局搜索,就可以找到與i2c@44e0b000設備節點對應的platform_driver部分:
在i2c-omap.c(不同平台可能文件名不一樣,但是按照上面從設備樹開始找的方法可以找到對應的源文件)中找到了這個compatible的定義:
static const struct of_device_id omap_i2c_of_match[] = {
{
.compatible = "ti,omap4-i2c",
.data = &omap4_pdata,
},
...
}
同時,根據platform driver驅動的規則,需要填充一個struct platform_driver結構體,然后注冊到platform總線中,這樣才能完成platfrom bus的匹配,因此,我們也可以在同文件下找到相應的初始化部分:
static struct platform_driver omap_i2c_driver = {
.probe = omap_i2c_probe,
.remove = omap_i2c_remove,
.driver = {
.name = "omap_i2c",
.pm = OMAP_I2C_PM_OPS,
.of_match_table = of_match_ptr(omap_i2c_of_match),
},
};
static int __init omap_i2c_init_driver(void)
{
return platform_driver_register(&omap_i2c_driver);
}
既然platform總線的driver和device匹配上,就會調用相應的probe函數,根據.probe = omap_i2c_probe,我們再查看omap_i2c_probe函數:
static int omap_i2c_probe(struct platform_device *pdev)
{
... //get resource from dtb node
... //config i2c0 via set corresponding regs
i2c_add_numbered_adapter(adap);
... //deinit part
}
在probe函數中我們找到一個i2c_add_numbered_adapter()函數,再跟蹤代碼到i2c_add_numbered_adapter():
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
... //assert part
return __i2c_add_numbered_adapter(adap);
}
根據名稱可以隱約猜到了,這個函數的作用是添加一個i2c adapter到系統中,接着看:
static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
...
return i2c_register_adapter(adap);
}
i2c_register_adapter(),根據這個名稱可以看出這是根據設備樹描述的硬件i2c控制器而生成的一個i2c_adapter,並注冊到系統中,這個i2c_adapter負責i2c底層數據收發。
繼續跟蹤源碼:
static int i2c_register_adapter(struct i2c_adapter *adap)
{
...
of_i2c_register_devices(adap);
...
}
注意到一個of前綴的函數,看到of就能想到這肯定是設備樹相關的函數。
void of_i2c_register_devices(struct i2c_adapter *adap)
{
...
for_each_available_child_of_node(bus, node) {
if (of_node_test_and_set_flag(node, OF_POPULATED))
continue;
client = of_i2c_register_device(adap, node);
if (IS_ERR(client)) {
dev_warn(&adap->dev,
"Failed to create I2C device for %pOF\n",
node);
of_node_clear_flag(node, OF_POPULATED);
}
}
...
}
這個函數的作用是輪詢每個子節點,並調用of_i2c_register_device(),返回i2c_client結構體,值得注意的是,在i2c總線中,driver部分為struct i2c_driver,而device部分為struct i2c_client.
所以可以看出,of_i2c_register_device()這個函數的作用就是解析設備樹中當前i2c中的子節點,並將其轉換成相應的struct i2c_client描述結構。
不妨來驗證一下我們的猜想:
static struct i2c_client *of_i2c_register_device(struct i2c_adapter *adap,struct device_node *node)
{
...
struct i2c_board_info info = {};
of_modalias_node(node, info.type, sizeof(info.type);
of_get_property(node, "reg", &len);
info.addr = addr;
info.of_node = of_node_get(node);
info.archdata = &dev_ad;
if (of_property_read_bool(node, "host-notify"))
info.flags |= I2C_CLIENT_HOST_NOTIFY;
if (of_get_property(node, "wakeup-source", NULL))
info.flags |= I2C_CLIENT_WAKE;
result = i2c_new_device(adap, &info);
...
}
struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
...
struct i2c_client *client;
client = kzalloc(sizeof *client, GFP_KERNEL);
client->adapter = adap;
client->dev.platform_data = info->platform_data;
if (info->archdata)
client->dev.archdata = *info->archdata;
client->flags = info->flags;
client->addr = info->addr;
client->irq = info->irq;
client->dev.parent = &client->adapter->dev;
client->dev.bus = &i2c_bus_type;
client->dev.type = &i2c_client_type;
client->dev.of_node = info->of_node;
client->dev.fwnode = info->fwnode;
if (info->properties) {
status = device_add_properties(&client->dev, info->properties);
if (status) {
dev_err(&adap->dev,
"Failed to add properties to client %s: %d\n",
client->name, status);
goto out_err;
}
}
device_register(&client->dev);
return client;
...
}
從device_node到i2c_client的轉換主要是在這兩個函數中了,在of_i2c_register_device()函數中,從device_node節點中獲取各種屬性的值記錄在info結構體中,然后將info傳遞給i2c_new_device(),i2c_new_device()生成一個對應的i2c_client結構並返回。
到這里,不難猜測為什么在內核初始化時只將一級子目錄節點(compatible屬性中含有"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"的向下遞歸一級)轉換成platform_device,因為在linux中,將一級子節點視為bus,而多級子節點則由具體的bus去處理。
同時,對於bus而言,有不同的總線處理方式和不同的driver、device的命名,自然不能將所有節點全部轉換成platform_device.
本文僅僅以i2c為例講解設備樹中多級子節點的轉換,朋友們也可以按照這種從設備樹出發的代碼跟蹤方式查看其它bus子節點的轉換。
好了,關於linux設備樹多級子節點的轉換的討論就到此為止啦,如果朋友們對於這個有什么疑問或者發現有文章中有什么錯誤,歡迎留言
原創博客,轉載請注明出處!
祝各位早日實現項目叢中過,bug不沾身.