linux設備驅動程序-設備樹(3)-設備樹多級子節點的轉換

linux設備驅動程序--設備樹多級子節點的轉換

在上一章:設備樹處理之——device_node轉換成platform_device中,有提到在設備樹的device_node到platform_device轉換中,必須知足如下條件:html

  • 通常狀況下,只對設備樹中根的一級子節點進行轉換,也就是多級子節點(子節點的子節點)並不處理。可是存在一種特殊狀況,就是當某個根子節點的compatible屬性爲"simple-bus"、"simple-mfd"、"isa"、"arm,amba-bus"時,當前節點中的一級子節點將會被轉換成platform_device節點。
  • 節點中必須有compatible屬性。

事實上,在設備樹中,一般會存在將描述設備驅動的設備樹節點被放置在多級子節點的狀況,好比下面這種狀況:node

/{
    ...
    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@2四、charger、pwrbutton則不會,至少在設備樹初始化階段不會被轉換,仍舊以device_node的形式存在在內存中。linux

顯而易見,這些設備並不是是無心義的設備,那麼它們是何時生成platform_device的呢?函數

答案是:由對應根目錄的一級子節點處理。code

咱們以i2c@44e0b000節點爲例,事實上,這個節點對應一個i2c硬件控制器,控制器的起始地址是0x44e0b000,這個節點的做用就是生成一個i2c硬件控制器的platform_device,與一樣被加載到內存中的platform_driver相匹配,在內存中構建一個i2c硬件控制器的描述節點,負責對應i2c控制器的數據收發。orm

根據設備樹的compatible屬性匹配機制,在內核源代碼中全局搜索,就能夠找到與i2c@44e0b000設備節點對應的platform_driver部分:htm

在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的匹配,所以,咱們也能夠在同文件下找到相應的初始化部分:blog

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不沾身.

相關文章
相關標籤/搜索