I2C驅動體系結構五:bus和設備的創建/註冊過程

前面幾章介紹了關鍵數據結構與物理硬件的對應關係,以及在軟件系統中的拓撲結構;這些都是靜態的關係。下面幾章我們把關鍵數據結構的實例化過程進行講解,這是動態流程關係。

其中會涉及設備模型驅動的一些固化的流程,這裏也會做一些簡單的介紹。

一、struct bus_type i2c_bus_type的系統註冊

bus_type類型的註冊過程都是調用bus_register接口(bus_register可以認爲是設備模型驅動框架中總線模型的對外接口):

bus_reigster的詳細流程可以參考這篇文章:《linux設備模型分析之bus_register()》,十分詳細,以下簡單說明一下:

i2c-core.c
    postcore_initcall(i2c_init)
        bus_register(&i2c_bus_type)
            priv->drivers_autoprobe = 1;    //設置該標誌, 當有driver註冊時,會自動匹配devices上的設備並用probe初始化,
                                                            //當有device註冊時,也同樣找到driver並會初始化
            kset_register(&priv->subsys);    //註冊kset,創建目錄結構,以及層次關係  生成/sys/bus/i2c目錄
            bus_create_file(bus, &bus_attr_uevent);                     // 生成/sys/bus/i2c/uevent文件
            kset_create_and_add("devices", NULL,&priv->subsys.kobj);    //生成/sys/bus/i2c/devices目錄
            kset_create_and_add("drivers", NULL,&priv->subsys.kobj);    //生成/sys/bus/i2c/drivers目錄
            add_probe_files(bus);            //生成/sys/bus/i2c/drivers_probe和/sys/bus/i2c/drivers_autoprobe文件
            bus_add_attrs(bus);             //i2c_bus_type未用

通過bus_register向系統註冊i2c_bus_type後,實際做的事情包括:

完成bus_type_private的初始化.創建 註冊的這條總線需要的目錄文件.
       在這條總線目錄下創建/device  /driver 目錄
       初始化這條總線上的設備鏈表:struct klist klist_devices;
       初始化這條總線上的驅動鏈表:struct klist klist_drivers;

總線目錄結構如下:

二、struct i2c_adapter的創建和註冊過程

Linux採用的是設備模型驅動組織和管理設備,其中最關鍵的是註冊設備/驅動過程,其中關鍵的實體數據結構是struct device和struct device_driver;關鍵的管理數據結構是kset、kobj等。我們將實體數據結構對象的創建和設備模型驅動分開看待。某些設備類的數據結構的創建過程可以由資源文件源代碼(低版本內核<=3.0)、dts描述文件、acpi描述。

設備結構對象描述創建過程(類struct device結構)------> 設備模型驅動註冊過程

1、採用設備資源源代碼的形式靜態生成設備結構體(platform_device)

在低版本的內核版本中,採用的就是這種方法,把大量platform_device對象定義在源代碼中。參考源代碼(基於linux 2.6)

有大量的場景(如i2c adapter),platform_device對應platorm_driver,在platorm_driver的probe函數中,創建了真正想要創建的數據結構(如sruct i2c_adapter)

arch/arm/mach-s3c2410/mach-smdk2410.c:系統啓動初始化流程,註冊i2c adapter對應的platform_device

arch/arm/plat-samsung/dev-i2c0.c:靜態定義了i2c_adapter相關的platform_device

//arch/arm/plat-samsung/dev-i2c0.c

static struct resource s3c_i2c_resource[] = {
	[0] = {
		.start = S3C_PA_IIC,
		.end   = S3C_PA_IIC + SZ_4K - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = IRQ_IIC,
		.end   = IRQ_IIC,
		.flags = IORESOURCE_IRQ,
	},
};

struct platform_device s3c_device_i2c0 = {
	.name		  = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
	.id		  = 0,
#else
	.id		  = -1,
#endif
	.num_resources	  = ARRAY_SIZE(s3c_i2c_resource),
	.resource	  = s3c_i2c_resource,
};

(圖一:資源源代碼靜態初始化platform_device變量)

 

arch/arm/mach-s3c2410/mach-smdk2410.c: 註冊過程

static struct platform_device *smdk2416_devices[] __initdata = {
	&s3c_device_fb,
	&s3c_device_wdt,
	&s3c_device_ohci,
	&s3c_device_i2c0,        //見圖一
	&s3c_device_hsmmc0,
	&s3c_device_hsmmc1,
};

static void __init smdk2416_machine_init(void)
{
	s3c_i2c0_set_platdata(NULL);
	s3c_fb_set_platdata(&smdk2416_fb_platdata);

	s3c_sdhci0_set_platdata(&smdk2416_hsmmc0_pdata);
	s3c_sdhci1_set_platdata(&smdk2416_hsmmc1_pdata);

	gpio_request(S3C2410_GPB(4), "USBHost Power");
	gpio_direction_output(S3C2410_GPB(4), 1);

	gpio_request(S3C2410_GPB(3), "Display Power");
	gpio_direction_output(S3C2410_GPB(3), 1);

	gpio_request(S3C2410_GPB(1), "Display Reset");
	gpio_direction_output(S3C2410_GPB(1), 1);

	platform_add_devices(smdk2416_devices, ARRAY_SIZE(smdk2416_devices));// 註冊platform_device設備
	smdk_machine_init();
}

在smdk2416_machine_init函數中調用platform_add_devices(實際最終調用platform_device_register--->platform_device_add--->device_add 向內核註冊設備)

int platform_device_register(struct platform_device *pdev)

    ---- device_initialize(&pdev->dev);        // 初始化platform_device::dev 基本數據

    ---- platform_device_add(pdev);            // 向內核註冊dev

            ---- pdev->dev.bus = &platform_bus_type; 

            ---- insert_resource(......)     // 調整resource樹

            ---- device_add(&pdev->dev);    // 向內核註冊platform_device對應的struct device結構

                                                                 // 生成對應的sys目錄文件

                    ---- device_create_file(dev, &uevent_attr);    // 創建/sys/devices/platform/xxx/uevent文件

                    ---- if (MAJOR(dev->devt))     // 是否有設備號,platform_device很少用

                             ---- device_create_file(dev, &devt_attr);  // 創建/sys/devices/platform/xxx/dev文件,標記設備號屬性(Major:Minor),用於udev獲取設備號,在/dev/目錄下創建設備節點(platform_device設備較少使用)

                            ---- device_create_sys_dev_entry(dev);  // 創建/sys/dev/char(block)/platform/Major:Min軟鏈接,指向/sys/devices/platform/xxx/vvv目錄

                            ---- devtmpfs_create_node(dev);    // 創建/dev/xxx設備節點(Major:Minor)

                    ---- device_add_class_symlinks(dev);   

                    ---- ............

                    ---- bus_probe_device

以上流程可以參考

https://www.cnblogs.com/armlinux/archive/2011/01/18/2396860.html

https://www.cnblogs.com/downey-blog/p/10486568.html

2、採用DTS設備樹的方式生成platform_device

對於單個platform_device來說,資源文件源代碼的形式看起來十分簡單和直接,但是從一個電路板的角度來說,使用資源文件源代碼的形式十分不容易管理,而且linux支持無數的電路板,如果以資源文件源代碼的形式管理各個設備,幾乎可以說大量的垃圾代碼充斥其中,DTS設備樹文件作爲替代方案,有如下優點:

a、DTS設備樹文件可以描述硬件設計;

b、DTS設備樹文件提供了統一的單板設計描述,輸入到DTS解析器,以一致方式生成大量的platform_device;

每個1級節點都會生成一個platform_device

BIOS獲取dtb文件,將dtb資源傳遞到內核

arch/arm/mach-exynos/exynos.c

exynos_dt_machine_init--->of_platform_populate--->of_platform_bus_create--->of_platform_device_create_pdata--->of_device_add--->device_add

 

再說一說platform_data,struct device::platform_data,在設備資源源代碼的形式中,一般就直接在arch/arm/xxx.c中直接賦值如:

在使用dts的形態中,dts中沒有這些信息,一般是在platform_device對應的platform_driver的probe函數中,動態生成,如在drivers/i2c/busses/i2c-exynos5.c中

exynos5_i2c_probe--->platform_set_drvdata

3、創建註冊struct i2c_adapter和i2c_client

通過上面1或2的方式,創建了platform_device,但是還沒有體現出如何創建i2c_adapter,其創建和初始化過程是platform_device對應的platform_driver的probe函數中呈現,如 drivers/i2c/busses/i2c-exynos5.c

module_platform_driver(exynos5_i2c_driver);

exynos5_i2c_probe

        ---- struct exynos5_i2c *i2c = devm_kzalloc(......); // struct exynos5_i2c 中包含struct i2c_adapter

        ---- i2c->adap.dev.of_node = pdev->dev.of_node;

        ---- i2c->adap.dev.parent = &pdev->dev;

        ---- i2c->adap.algo_data = i2c;

        ---- i2c_add_adapter(&i2c->adap)

                 ---- i2c_register_adapter(adapter)

                        ---- device_register(&adap->dev);           // 註冊adapter,將adapter掛載到i2c_bus_type上

                        ---- of_i2c_register_devices(adap);

                                ---- of_i2c_register_devices(adap);  // 對於在dts中,有i2c節點的子節點記錄(即i2c slave設備節點),該函數能夠創建對應的i2c_client,並向系統註冊

                                       ---- i2c_new_device--->device_register(); // 註冊i2c_client,將i2c_client掛載到i2c_bus_type上,client->adapter=adap(每個client都對應一個adapter)

                                ---- acpi_i2c_register_devices(adap); // 基於acpi形式的i2c_client註冊

                        ---- bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);

        ---- platform_set_drvdata(pdev, i2c);

probe函數中:

a、註冊了adapter

b、對於dts中包含i2c節點的子節點(i2c slave設備),註冊了i2c_client

c、__process_new_adapter從某些資料上看似乎在此階段並沒有使用(因爲i2c_driver還沒有註冊上來)

 

4、動態創建i2c_client

前面是在i2c_adapter對應的platform_driver probe時,能夠通過dts記錄靜態創建和註冊i2c_client,此處講解通過i2c_driver的註冊動態識別、創建、註冊i2c_client的過程。

i2c_driver實際是與代表i2c slave的struct i2c_client結構體是對應的,我們在源代碼中搜索 module_i2c_driver 關鍵字,可以看到大量的結果,說明kernel中支持了很多i2c slave

#define module_i2c_driver(__i2c_driver) \
    module_driver(__i2c_driver, i2c_add_driver, \
                     i2c_del_driver)

#define i2c_add_driver(driver) \
        i2c_register_driver(THIS_MODULE, driver)

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

    ---- driver->driver.bus = &i2c_bus_type;    // 設置driver掛載的位置

    ---- INIT_LIST_HEAD(&driver->clients);

    ---- driver_register(&driver->driver);         // 向系統註冊device_driver

    ---- i2c_for_each_dev(driver, __process_new_driver);    // 對於i2c_bus_type上掛載的所有i2c_adapter,使用當前i2c_driver,調用__process_new_driver

        ---- __process_new_driver --- i2c_do_add_adapter(struct i2c_driver *driver,struct i2c_adapter *adapter)

            ---- i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver) // 對於i2c_driver中的每一個address,調用i2c_detect_address函數

                ---- struct i2c_client *temp_client = kzalloc(...);temp_client->adapter = adapter; 

                ---- temp_client->addr = address_list[i]; // 將i2c_driver傳遞的address_list逐個使用i2c_detect_address

                ---- i2c_detect_address(temp_client, driver);

                    ---- i2c_check_addr_validity(addr); //檢查addr的有效性(純軟件行爲)

                    ---- i2c_check_addr_busy(adapter, addr); //檢查該地址是否與已經掛載在該adapter的i2c_client一致,如果一致,表示已經有i2c_client掛載到該adapter下了,實際這個函數蠻有趣,它似乎揭示了一種總線的佈局方式---adapter可以以級聯的形式擴展。該函數也是一個純軟件的行爲。

                    ---- driver->detect(temp_client, &info);  // 這個回調函數就是在某個i2c client驅動源文件中聲明定義i2c_driver變量時已經賦值好的,如drivers/hwmon/max6639.c,其具體實現是依賴i2c-core中的硬件讀寫接口

                    ---- i2c_new_device ---> device_register; // 創建i2c_client,並註冊到內核

再說明一下,通過這種動態創建/註冊i2c_client的方式,不是依靠傳統的直接調用match/probe匹配,而是在i2c_driver加載過程中生成i2c_client,動態調用device_register,再通過match/probe。

 

前面幾章主要是通過設備驅動模型的角度來看待i2c系統結構中各種關鍵數據結構的創建過程,後面會從i2c本身特性的角度來繼續講解。

 

遺留問題:dts節點中的clocks、pinctrl分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章