設備注冊、驅動注冊與驅動匹配
以背光調整設備為例,記錄設備描述,設備注冊,驅動注冊,驅動與設備匹配的過程。
1 設備描述
在設備樹dts文件中對設備信息進行描述,使用dts將設備與驅動分離,在不同的平台或目標機上,如果硬件設備資源不通,則只需要變更設備樹文件即可,驅動可以保持一致。例如背光設備的dts描述為:
{
lvds_backlight0: lvds_backlight { // 標簽:設備名[@設備地址]
compatible = "pwm-backlight"; // 設備與驅動匹配的關鍵字
pwms = <&lvds0_pwm 0 100000 0>; // pwm設備描述 <引用PWM設備結點 極性 周期>
brightness-levels = < 0 1 2 3 4 5 6 7 8 9 // 背光調整等級
10 11 12 13 14 15 16 17 18 19
20 21 22 23 24 25 26 27 28 29
30 31 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59
60 61 62 63 64 65 66 67 68 69
70 71 72 73 74 75 76 77 78 79
80 81 82 83 84 85 86 87 88 89
90 91 92 93 94 95 96 97 98 99
100>;
default-brightness-level = <80>; // 默認背光等級
status = "okay"; // 設備結點狀態,如果是okay則向kernel注冊設備,否則為disable,則不向kernel注冊設備信息
};
}
以上為設備樹文件描述lvds_backlight設備節點的節點信息。
- compatible:字符串屬性,用於匹配設備與設備驅動。一般形式為
<manufacturer>,<model>,前者一般為芯片供應商,后者為模塊名。 - pwms:pwm設備屬性,其引用了一個pwm設備節點
&lvds0_pwm,節點信息由drivers/pwm/core.c解析成設備。在pwm_bl.c驅動中可以直接get pwm資源及對pwm資源進行操作。 - brightness-levels:背光調整等級,由
pwm_bl.c中的probe進行解析。 - default-brightness-level:默認背光等級,由
pwm_bl.c中的probe進行解析。 - status:設備狀態如果設置成okay,則在解析設備樹文件時會將此設備注冊到對應的總線上,否則不會自動注冊,需要手動注冊。
2 設備注冊
- dts文件在編譯時,編譯成dtb文件,啟動kernel時,將dtb的地址傳遞給kernel,對dtb文件進行解析。開始解析的函數為
setup.c文件中的setup_arch()函數中的unflatten_device_tree()對設備樹文件進行解析,解析結果放在of_root(of/base.c)的一個鏈表中。 - start_kernel()-->rest_init()-->kernel_init()-->kernel_init_freeable()-->driver_init()-->driver_init()-->of_core_init(),將設備樹中配置的節點暴露到用戶空間的
/sys/firmware/devicetree/base目錄。其中kset kobj等概念還未完全建立,后續補充 - 多數設備均注冊到
platform bus上,如何總線進行注冊目前還沒有理清楚,在系統目錄/sys/bus/platform/devices中列出系統已經注冊的設備。
經過kernel的一系列設置arch,初始化操作,kernel將bl_pwm設備注冊到platform上,可在文件系統中查看到以下文件:/sys/bus/platform/devices/lvds_backlight,當status屬性設置成"disable"時,設備便不會注冊,/sys/bus/platform/devices/目錄下也不會有相關的設備信息。
3 驅動注冊
由於設備注冊到platform bus上,因此驅動也注冊到platform bus上,platform bus對設備與驅動進行匹配。將驅動注冊到platform bus的方法需要使用platform提供的api,具體注冊方法如下:
// match table
static const struct of_device_id pwm_backlight_of_match[] = {
{ .compatible = "pwm-backlight" }, // 匹配設備中的compatible屬性
{ }
};
MODULE_DEVICE_TABLE(of, pwm_backlight_of_match);
...
// platform driver 結構定義
static struct platform_driver pwm_backlight_driver = {
.driver = {
.name = "lvds_backlight", // 驅動名稱
.pm = &pwm_backlight_pm_ops,
.of_match_table = of_match_ptr(pwm_backlight_of_match), // 匹配設備的配置表,同一個驅動,可以匹配不同的設備
},
.probe = pwm_backlight_probe, // 探針函數,當設備與驅動匹配時調用
.remove = pwm_backlight_remove, // 設備離線時調用
.shutdown = pwm_backlight_shutdown, // shutdown時調用
};
module_platform_driver(pwm_backlight_driver); // 宏,展開后調用init 和 exit函數。注冊驅動
pwm_backlight_driver和pwm_backlight_of_match均為配置表,設置驅動名,回調函數等,確保能被kernel調用,注冊驅動的是platform提供的宏module_platform_driver,具體如下:
// platform_device.h
/* module_platform_driver() - Helper macro for drivers that don't do
* anything special in module init/exit. This eliminates a lot of
* boilerplate. Each module may only use this macro once, and
* calling it replaces module_init() and module_exit()
*/
#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
// device.h
/**
* module_driver() - Helper macro for drivers that don't do anything
* special in module init/exit. This eliminates a lot of boilerplate.
* Each module may only use this macro once, and calling it replaces
* module_init() and module_exit().
*
* @__driver: driver name
* @__register: register function for this driver type
* @__unregister: unregister function for this driver type
* @...: Additional arguments to be passed to __register and __unregister.
*
* Use this macro to construct bus specific macros for registering
* drivers, and do not use it on its own.
*/
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);
因此在驅動中只要包含module_platform_driver(pwm_backlight_driver);代碼,kernel就會適當時候將此驅動注冊到platform bus上。注冊成功后,可以在/sys/bus/platform/drivers路徑下查看已經注冊的驅動。
4 驅動與設備匹配
由於設備與驅動均注冊到platform bus上,因此設備的匹配由platform bus完成,具體匹配函數為platform.c中的platform_match(struct device *dev, struct device_driver *drv)函數,其參數為設備結構指針和驅動結構指針,函數實現如下:
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv)) // 使用match表匹配compatible屬性
return 1;
/* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;// 匹配ID
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0); // 使用驅動名與設備名進行匹配
由匹配函數可知,當有驅動或設備注冊后,依次使用match tbl acpi id tbl dev->name drv->name進行匹配,match tbl優先級最高,驅動名匹配優先級最低。只要有一種方式匹配成功,則返回,驅動與設備匹配成功后,調用驅動中設置的回調函數--probe函數。
5 小結
以上,驅動與設備成功綁定,且調用pwm_bl.c中的probe函數,但是到這里,還不能調整pwm以調整背光,也不能在用戶空間通過文件操作來動態的修改背光。現有的驅動是借助backlight class完成屬性背光調整及創建用戶空間的屬性文件。
