(三)從解析DTS到建立device_從device_node到併入裝置驅動模型(結合原始碼)
從device_node到併入裝置驅動模型
此篇部落格有很多參考其他文章的內容,由於參考內容繁雜,不一一標註角標了,在末尾會貼上所有參考部落格的link,如有侵權,請聯絡本人處理,謝謝。
深入,並且廣泛
-沉默犀牛
上一篇文章已經詳細的分析了兩個問題:
1.如何根據Device Tree的資訊,找到最適合的machine_desc
2.如何將DTB轉換成節點是device_node的樹狀結構
那麼為什麼要做這兩件事情呢?
我們現在要做的事情是把DTS中描述的節點(status = okay的)註冊到kernel中,DTS的資訊我們的kernel沒法直接識別啊,就得通過上述的2把DTS中描述的每一個節點,轉換成device_node結構體,我們可以認為DTS中寫的每一個節點在這裡都被解析為一個device_node。(該結構體中有parent、child、sibling成員,通過這些成員把device_node給連線成了樹狀結構)好,現在我們有了完全代表了DTS資訊,又能被kernel識別的device_node,現在就要把這些device_node一個個的建立起來(比如有的device_node要註冊到platform總線上,有的要註冊到i2c匯流排…),這個建立的過程,就需要我們上述1中的machine_desc了!在定義machine_desc結構體的時候,會定義一個回撥函式:xxxx_init(大部分情況)就是這個xxxx_init把device_node都註冊起來。
再補充上面一段話的三個點
1.xxxx_init函式只是註冊了platform總線上的device_node,那其他的呢?比如i2c上的device呢?在註冊i2c匯流排時,呼叫qup_i2c_probe(),這個介面會新增i2c介面卡,介面卡新增完成後會呼叫of_i2c_register_devices()介面來i2c匯流排節點的子節點,然後呼叫i2c_new_device(),生成i2c裝置。
2.我在定義回撥函式後面寫了個大部分情況,也就代表有的時候不會定義這個回撥函式,那麼kernel怎麼註冊device_node呢?事實上kernel會檢測machine_desc有沒有init函式,如果有,則呼叫;如果沒有,則呼叫of提供的介面直接註冊所有platform的device,這裡就有一個問題了:這個machine_desc如果沒有定義init函式,那我還要它幹嘛呢?我去看(根據DTS選擇最適配的machine_desc)那一段程式碼時發現,如果沒有找到合適的machine_desc的話,有一行註釋:does not return! 這似乎意味著,找不到合適的machine_desc也沒啥關係,kernel還是會繼續執行下去,platform上的device還是可以註冊上。如果這一點的理解有誤,請留言糾正,大恩不言謝!
3.DTS中的節點都轉換成了device_node,但是DTS中描述的節點並不都是device啊,比如cpus node,memory node,choose node等。對於這些節點的處理,我會在下一篇文章中說明。
經過前面的介紹和解釋,我們明確了這一篇文章的主題:
那些platform匯流排下的device_node如何註冊到platform總線上的?
執行流程
進入kernel的入口後,會按照如下的呼叫流程start_kernel
->rest_init
->kernel_init
->kernel_init_freeable
->
do_basic_setup
do_initcalls
,do_initcalls
函式中,在do_initcalls函式中,kernel會依次執行各個initcall函式。
程式碼驗證
在上述的呼叫流程中,會呼叫到呼叫customize_machine
,也正是這個函式,檢測了machine_desc是否有xxxx_init函式,程式碼如下:
static int __init customize_machine(void)
{
if (machine_desc->init_machine) //如果有init_machine()函式,則執行
machine_desc->init_machine();
else //否則直接呼叫of提供的介面來註冊device
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
return 0;
}
arch_initcall(customize_machine);
以高通msm8953為例,看看它有沒有定義init_machine函式:
DT_MACHINE_START(MSM8953_DT,
"Qualcomm Technologies, Inc. MSM8953 (Flattened Device Tree)")
.init_machine = msm8953_init, //這裡定義了init函式
.dt_compat = msm8953_dt_match, //這個引數就是之前匹配machine_desc用的
//這個引數將於DTS中的compatible屬性值來比較
MACHINE_END
再看看這個函式的內容:
static void __init msm8953_init(void)
{
board_dt_populate(NULL);
}
void __init board_dt_populate(struct of_dev_auxdata *adata)
{
//傳入NULL引數表示從root node開始scan
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL);
/* Explicitly parent the /soc devices to the root node to preserve
* the kernel ABI (sysfs structure, etc) until userspace is updated
*/
//從soc節點開始scan
of_platform_populate(of_find_node_by_path("/soc"),
of_default_bus_match_table, adata, NULL);
}
看來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;
//之前傳入的NULL,就是從/節點下開始,即根節點
root = root ? of_node_get(root) : of_find_node_by_path("/");
if (!root)
return -EINVAL;
pr_debug("%s()\n", __func__);
pr_debug(" starting at: %s\n", root->full_name);
for_each_child_of_node(root, child) { //這裡面是一個for迴圈,如果root節點下面有child,就執行一遍
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;
}
看來of_platform_bus_create()
這個函式是個重要函式,仔細分析一下:
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;
//確保device node有compatible屬性的程式碼
if (strict && (!of_get_property(bus, "compatible", NULL))) {
pr_debug("%s() - skipping %s, no compatible prop\n",
__func__, bus->full_name);
return 0;
}
if (of_node_check_flag(bus, OF_POPULATED_BUS)) {
pr_debug("%s() - skipping %s, already populated\n",
__func__, bus->full_name);
return 0;
}
//在傳入的lookup table尋找和該device node匹配的附加資料
auxdata = of_dev_lookup(lookup, bus);
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")) {
/*
* Don't return an error here to keep compatibility with older
* device tree files.
*/
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;
}
}
of_node_set_flag(bus, OF_POPULATED_BUS);
return rc;
}
具體增加platform device的程式碼在of_platform_device_create_pdata()
中,程式碼如下:
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;
if (!of_device_is_available(np) ||
of_node_test_and_set_flag(np, OF_POPULATED)) //check status屬性,和是否已經註冊過
return NULL;
// of_device_alloc除了分配struct platform_device的記憶體,還分配了該platform device需要的resource的記憶體
//這裡就根據struct device_node建立了struct platform_device
dev = of_device_alloc(np, bus_id, parent);
if (!dev)
goto err_clear_flag;
//設定platform_device 中的其他成員
dev->dev.bus = &platform_bus_type;
dev->dev.platform_data = platform_data;
of_dma_configure(&dev->dev, dev->dev.of_node);
of_msi_configure(&dev->dev, dev->dev.of_node);
of_reserved_mem_device_init_by_idx(&dev->dev, dev->dev.of_node, 0);
if (of_device_add(dev) != 0) { //把這個platform device加入統一裝置模型系統中
of_dma_deconfigure(&dev->dev);
platform_device_put(dev);
goto err_clear_flag;
}
return dev;
err_clear_flag:
of_node_clear_flag(np, OF_POPULATED);
return NULL;
}
到這裡我們就很清楚device_node是如何最終成為platform_device了。