帶你瞭解什麼是核心匯流排架構

roc_guo發表於2022-05-10

當裝置或者驅動新增到連結串列時,會觸發匯流排的match函式。那麼,您有沒有深入去研究過核心匯流排呢?在本文中,我們來深入探討一下核心中的匯流排,主要涉及到以下問題:

帶你瞭解什麼是核心匯流排架構帶你瞭解什麼是核心匯流排架構

businessman having questions and concrete wall

1.核心中是如何部署匯流排的。
2.裝置和驅動是如何掛載到匯流排上的。
3.裝置和其對應的驅動是如何透過匯流排進行匹配的。

1.匯流排部署

我們從函式start_kernel來分析匯流排的部署,實際上在函式start_kernel呼叫之前,會有彙編程式碼來處理啟動引數,啟動模式,建立核心空間頁表,準備好堆疊等。由於這些同匯流排部署關係不大,暫且就認為start_kernel就是核心的main函式。start_kernel內部會呼叫rest_init,rest_init函式內部建立核心執行緒kernel_init,而kernel_init中有如下的函式呼叫過程:

kernel_init-->do_basic_setup->driver_init—>buses_init和platform_bus_init

此處的buses_init和platform_bus_init就是匯流排的部署函式,也是本小節的重點,且buses_init必須在platform_bus_init前面呼叫。因為Platform匯流排是掛載在bus匯流排下的,接下來我們詳細分析下這兩個過程。

buses_init

核心中所有的物件如bus,都是一個kobject,而把相同型別的kobject集合到一起就組成了一個kset,而函式buses_init內部就是透過bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL)來註冊bus匯流排對應的kset,其最後bus_kset如下圖1所示:

帶你瞭解什麼是核心匯流排架構帶你瞭解什麼是核心匯流排架構

圖 1 bus_kset結構

至此算是準備好了bus_kset,我們繼續往下看一下其他型別的匯流排是如何和bus進行關聯的。

platform_bus_init

該函式主要完成兩個功能,其函式如下:

struct bus_type platform_bus_type = {
    .name       = "platform",
    .dev_attrs  = platform_dev_attrs,
    .match      = platform_match,
    .uevent     = platform_uevent,
    .pm     = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
int __init platform_bus_init(void)
{
    int error;
    early_platform_cleanup();
    error = device_register(&platform_bus);
    if (error)
        return error;
    error =  bus_register(&platform_bus_type);
    if (error)
        device_unregister(&platform_bus);
    return error;
}

device_register是用來註冊一個device,並新增到系統中,最後會在/sys/devices/目錄下建立 platform目錄對應的裝置物件,其路徑是/sys/devices/platform/。

bus_register是將Platform bus匯流排註冊進系統,其實內部就是建立了對應的kset和kobject等,且主要完成以下三項工作:

初始化必須的結構體,struct subsys_private 和對應的kobkect。
同bus匯流排建立關係,kobject.parent 設定為上一步已經建立好的bus_kset.kobj, kobject.kset設定為bus_kset,把對應的kobject.ktype設定為bus_ktype。
把對應的kobjet新增到對應的kset的連結串列中,對匯流排來說,就是新增到bus_kset中的連結串列中。
下面是bus_register函式的實現(刪除了建立失敗退出時free記憶體等的操作),且我在程式碼中增加了註釋,方便大家查閱:

/**
 * bus_register - register a bus with the system.
 * @bus: bus.
 *
 * Once we have that, we registered the bus with the kobject
 * infrastructure, then register the children subsystems it has:
 * the devices and drivers that belong to the bus.
 */
int bus_register(struct bus_type *bus)
{
    int retval;
    //step:建立並分配,初始化struct subsys_private結構體指標
    struct subsys_private *priv;
    priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;
    priv->bus = bus;
    bus->p = priv;
    BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
    retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
    if (retval)
        goto out;
//step2:同上面建立的bus_kset進行關聯
//kset_register時,會設定對應priv->subsys。Kobject->parent = bus_kset.kobj
    priv->subsys.kobj.kset = bus_kset;
    priv->subsys.kobj.ktype = &bus_ktype;
    priv->drivers_autoprobe = 1;
    retval = kset_register(&priv->subsys); //一會新增到bus_kset連結串列中
    if (retval)
        goto out;
    //step3:建立對應的屬性檔案
    retval = bus_create_file(bus, &bus_attr_uevent);
    if (retval)
        goto bus_uevent_fail;
    priv->devices_kset = kset_create_and_add("devices", NULL,
                         &priv->subsys.kobj);
    if (!priv->devices_kset) {
        retval = -ENOMEM;
        goto bus_devices_fail;
    }
    priv->drivers_kset = kset_create_and_add("drivers", NULL,
                         &priv->subsys.kobj);
    if (!priv->drivers_kset) {
        retval = -ENOMEM;
        goto bus_drivers_fail;
    }
    //step4:初始化兩個比較重要的連結串列,後面內容中會提到這兩個連結串列
    klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
    klist_init(&priv->klist_drivers, NULL, NULL);
    
    //step5:新增探針檔案
    retval = add_probe_files(bus);
    if (retval)
        goto bus_probe_files_fail;
    retval = bus_add_attrs(bus);
    if (retval)
        goto bus_attrs_fail;
    pr_debug("bus: '%s': registered\n", bus->name);
    return 0;
    ……
    return retval;
}
EXPORT_SYMBOL_GPL(bus_register);

對於其他匯流排(如IIC等),也是透過bus_register進行註冊的,比如bus_register(&i2c_bus_type)和bus_register(&mmc_bus_type)等,其原理同上面一樣,在此就不挨個介紹了。

透過上面的分析,我們知道了bus匯流排,且其他匯流排掛載在bus匯流排下,等匯流排部署完成後,不同裝置會掛載在對應的匯流排下面。對於SPI,IIC等裝置,他們都可以掛載在對應的匯流排下同CPU進行資料互動。但在嵌入式系統中,有些裝置是不屬於這些常見的匯流排,因此引入了虛擬的Platform匯流排,本小節正是透過虛擬的Platform匯流排來說明匯流排部署的。

2.裝置和驅動的掛載

我們依然採用Platform匯流排來說明裝置和驅動的掛載問題。

裝置掛載

對於Platform匯流排來說,可以透過函式platform_device_register來掛載(有的地方稱之為註冊)裝置,也可以透過裝置樹來掛載,在核心啟動時,會進行裝置樹的解析,本文中不涉及裝置樹,主要介紹platform_device_register的方式。

該函式原型如下:

int platform_device_register(struct platform_device *pdev)
{
    device_initialize(&pdev->dev);
    return platform_device_add(pdev);
}

函式在執行的過程中,有如下呼叫關係:

platform_device_add---->設定struct platform_device中的匯流排型別及其他引數--->device_add--->bus_add_device---->klist_add_tail

這個呼叫過程省略了一些屬性和節點等的處理,我關注的重點在函式klist_add_tail,該函式是把當前裝置新增到platform_bus中的一個連結串列中,這個連結串列在Platform匯流排部署時就初始化完成了,其初始化函式就是函式bus_register中的step4,可以翻閱上一個小節來檢視。

驅動掛載

對於Platform匯流排來說,可以透過函式platform_driver_register來掛載(有的地方稱之為註冊)裝置,其函式原型如下:

int platform_driver_register(struct platform_driver *drv)
{
    drv->driver.bus = &platform_bus_type;
    if (drv->probe)
        drv->driver.probe = platform_drv_probe;
    if (drv->remove)
        drv->driver.remove = platform_drv_remove;
    if (drv->shutdown)
        drv->driver.shutdown = platform_drv_shutdown;
    return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(platform_driver_register);

函式在執行的過程中,有如下呼叫關係:

driver_register---> 設定對應的引數等---->driver_find---> bus_add_driver----> klist_add_tail

相對於裝置掛載,多了一個函式driver_find的呼叫,該函式主要目的就是判斷驅動是否已經掛載上了,其餘處理方式同裝置掛載相同。最為重要的依然是klist_add_tail,把該驅動新增到了platform_bus中的一個連結串列中。

其他型別的匯流排裝置和驅動相同,也會存在兩個連結串列,裝置和驅動均掛載到相應的連結串列中。

3.裝置和驅動的匹配

從第2小節中,我們知道Platform匯流排下有兩個連結串列,我採用下面的圖來具體化這兩個連結串列,圖左邊的裝置連結串列,圖中僅呈現3個裝置,實際上會有很多,圖右邊為驅動連結串列。不管是左邊的裝置還是右邊的驅動,均有name欄位(通常情況下是compatible),這是個非常重要的欄位,後面我們會用到它。

帶你瞭解什麼是核心匯流排架構帶你瞭解什麼是核心匯流排架構

圖 2Platform匯流排的兩個連結串列

針對匹配問題,我依然採用Platform匯流排來闡述,我們已經知道在進行驅動掛載時,會呼叫函式bus_add_driver,該函式核心實際上還會呼叫一個函式driver_attach(針對設定drivers_autoprobe的情況),下面是函式driver_attach的呼叫情況:

driver_attach
--->bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)
---> klist_iter_init_node(&bus->p->klist_devices, &i, (start ? &start->p->knode_bus : NULL));
   while ((dev = next_device(&i)) && !error)
      error = fn(dev, data);
   --->driver_match_device
     ---> drv->bus->match
       ---> platform_match

從上面程式碼過程可以看出,當掛載驅動時,會遍歷圖2中左邊的連結串列,最後呼叫Platform匯流排的match函式platform_match (match函式是在struct bus_type platform_bus_type中設定的,在匯流排部署時階段呼叫platform_bus_init就設定好了)來進行裝置和驅動的匹配。每個匯流排都會有自己的match函式,且match函式里面會透過多種方式匹配,如常見的compitable,name或者id_table,只要有一個能匹配上,則認為驅動和裝置匹配成功。

總結

本文主要採用Platform來說明了核心中匯流排部署,裝置和驅動掛載,及裝置和驅動的匹配問題,實際上其他匯流排也是採用相同的方式,在我的描述過程中,重點在於匯流排,忽略了一些sysfs節點,引用計數,kobject,kset等,但這些在核心架構中也是比較重要的環節,希望大家在瞭解匯流排架構後,也能有時間去深入檢視核心匯流排的各個處理細節。

特別說明:不同的核心,可能使用到的函式,或者函式的實現同文章中介紹的存在出入,但其原理及架構相同,可以作為參考。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2893297/,如需轉載,請註明出處,否則將追究法律責任。

相關文章