前面幾章介紹了關鍵數據結構與物理硬件的對應關係,以及在軟件系統中的拓撲結構;這些都是靜態的關係。下面幾章我們把關鍵數據結構的實例化過程進行講解,這是動態流程關係。
其中會涉及設備模型驅動的一些固化的流程,這裏也會做一些簡單的介紹。
一、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分析