Linux 驅動開發 | 驅動世界里的宏偉建筑

哈嘍,我是老吳。

是否每一個上進的人都會覺得自己還可以再努力一點?

事情到了最后,只要沒達成目的,總能把失敗的原因歸為 "沒有再努力一點"。

但是,對努力的最大錯誤認知就是:時間越長,過程越痛苦,代表我越努力。

想一想,是否有更合理的努力方式?

以下是正文:

一、什么是 device model?
二、device?model?的?3?個核心概念
三、bus、device、driver 是如何關聯的?
四、bus、device、driver?最簡單示例
五、小結
六、相關參考

一、什么是 device model?

Linux 的 device model 是一個旨在統一管理所有設備驅動的模型。

它猶如一棟規模宏大的建筑:

以 kobject、kset、attribute 等作為基本的建筑材料,

構造出支撐驅動世界的 bus、device、driver 三大組件,

最后通過 sysfs 在各種基礎的建筑材料之間建立彼此的互聯層次關系,并向外界提供了與建筑內設施進行互動的文件接口。

點擊查看大圖

device model 有什么作用?

可以將 device 的硬件描述 和 driver 進行分離,提升 driver 的代碼復用率;

可以對 device 進行分類;

可以遍歷 device 和 driver;

可以更好地呈現設備的拓撲關系;

可以通過 sysfs 訪問設備;

可以讓設備支持熱插拔;

...

為了控制篇幅,本文將重點放在與驅動工程師關系最緊密的 bus、device、driver 3 個 組件

二、device model 的 3 個核心概念

device model 里有 3 個核心的概念:

  • bus

  • device

  • driver

什么是 bus?

bus 代表一種總線,例如 I2C、SPI、USB 等。

bus 是 Linux 設備驅動模型這種建筑的核心框架,系統中的設備和驅動都依附在其周圍。

啟動系統后,可以通過 /sys/bus 可以查看系統里當前有哪些總線。

bus 由 struct bus_type 來描述:

struct?bus_type?{
?const?char?*name;
?const?char?*dev_name;
?struct?device?*dev_root;
?const?struct?attribute_group?**bus_groups;
?const?struct?attribute_group?**dev_groups;
?const?struct?attribute_group?**drv_groups;

?int?(*match)(struct?device?*dev,?struct?device_driver?*drv);
?int?(*uevent)(struct?device?*dev,?struct?kobj_uevent_env?*env);
?int?(*probe)(struct?device?*dev);
?int?(*remove)(struct?device?*dev);
?void?(*shutdown)(struct?device?*dev);

?...
?struct?subsys_private?*p;
?struct?lock_class_key?lock_key;
};

不需要一下子了解各個成員的作用,用到的時候再說明。

重點關注成員:

  • int (*match)(struct device *dev, struct device_driver *drv),用于判斷掛在該 bus 上的設備和驅動是否匹配的回調函數;

  • int (*probe)(struct device *dev),如果 bus 具有探測設備的能力,則會提供該回調函數;

  • struct subsys_private *p,用于管理 bus 上的設備和驅動的數據結構;

注冊 bus 的 api:

int?bus_register(struct?bus_type?*bus);

什么是 device ?

device 代表了某個設備。

由 struct device 來描述:

struct?device?{
?struct?device?*parent;
?struct?device_private?*p;
?struct?kobject?kobj;
?const?char?*init_name;
?const?struct?device_type?*type;
?struct?mutex?mutex;
?struct?bus_type?*bus;
?struct?device_driver?*driver;
?void?*platform_data;
?void?*driver_data;
????...
}

重點關注成員:

  • struct kobject kobj,內核對象;

  • struct bus_type *bus,設備所在的總線;

  • struct device_driver *driver,和設備綁定在一起的驅動,如果還沒綁定,則為 NULL;

注冊 device 的 api:

int?device_register(struct?device?*dev)

什么是 driver?

driver 代表了設備驅動。

由 struct device_driver 來描述:

struct?device_driver?{
?const?char?*name;
?struct?bus_type?*bus;

?struct?module?*owner;
?const?char?*mod_name;?/*?used?for?built-in?modules?*/

?bool?suppress_bind_attrs;?/*?disables?bind/unbind?via?sysfs?*/
?enum?probe_type?probe_type;

?const?struct?of_device_id?*of_match_table;
?const?struct?acpi_device_id?*acpi_match_table;

?int?(*probe)?(struct?device?*dev);
?int?(*remove)?(struct?device?*dev);
?void?(*shutdown)?(struct?device?*dev);
?int?(*suspend)?(struct?device?*dev,?pm_message_t?state);
?int?(*resume)?(struct?device?*dev);
?const?struct?attribute_group?**groups;

?const?struct?dev_pm_ops?*pm;

?struct?driver_private?*p;
};

重點關注成員:

  • struct bus_type *bus;
  • int (*probe) (struct device *dev);

值得一提的是,總線控制器也是一種設備。

例如 I2C 總線控制器這個設備,對應的驅動為 I2C controller driver。

而掛在 I2C 總線上的設備,對應的驅動為 I2C device driver。

注冊 driver 的 api:

int?driver_register(struct?device_driver?*drv);

三、bus、device、driver 是如何關聯的?

device model 最核心的工作就是維護這三類抽象的實例,以及建立它們之間的關聯關系。

bus 如何管理 device 和 driver ?

在 struct bus_type 中有一個 struct subsys_private *p 指針,它負責管理掛在 bus 上的所有設備和驅動,其定義如下:

struct?subsys_private?{
?struct?kset?subsys;
?struct?kset?*devices_kset;
?struct?list_head?interfaces;
?struct?mutex?mutex;

?struct?kset?*drivers_kset;
?struct?klist?klist_devices;
?struct?klist?klist_drivers;
?struct?blocking_notifier_head?bus_notifier;
?unsigned?int?drivers_autoprobe:1;
?struct?bus_type?*bus;

?struct?kset?glue_dirs;
?struct?class?*class;
};
點擊查看大圖

兩個 klist 成員以鏈表的形式將該總線上所有的驅動與設備鏈接到一起。

struct kset *drivers_kset 和 struct kset *devices_kset 是在向系統注冊當前新總線時動態生成的容納該總線上所有驅動與設備的 kset。

在內核里,用 kobject 來表示一個對象,kset 則是 kobject set 的縮寫,即內核對象集合。

內核用 kobject 和 kset 等數據結構作為原材料,以實現面向對象的方式構建了 device model 的框架。

最后,device 和 device_driver 的 bus 成員也會指向總線:

device 和 driver 的綁定

無論是通過 device_register() 注冊一個 device 到 bus 上,

還是通過 driver_register() 注冊一個 device_driver 到 bus 上,

都會導致 bus 嘗試執行 device 和 driver 的綁定行為。

1. device_register() 觸發的綁定

注冊 device 時:

int?device_register(struct?device?*dev);
?device_add(dev);
??bus_probe_device(dev);
???__device_attach(dev,?true);

__device_attach(dev, true) 會為 device 遍歷 bus 上的所有 driver:

bus_for_each_drv(dev->bus,?NULL,?&data,?__device_attach_driver);
?driver_match_device(drv,?dev);
??drv->bus->match???drv->bus->match(dev,?drv)?:?1;
?driver_probe_device(drv,?dev);

driver_match_device() 通過 bus 里的 match 函數來判斷是否 device 和 driver 是否匹配,

是否 match 的判斷標準一般是通過 of_match_table 或者是 id_table 作為衡量的標準,

以 i2c bus 的 match 函數為例

static?int?i2c_device_match(struct?device?*dev,?struct?device_driver?*drv){
?struct?i2c_client?*client?=?i2c_verify_client(dev);
?struct?i2c_driver?*driver;


?/*?Attempt?an?OF?style?match?*/
?if?(i2c_of_match_device(drv->of_match_table,?client))
??return?1;

?/*?Then?ACPI?style?match?*/
?if?(acpi_driver_match_device(dev,?drv))
??return?1;

?driver?=?to_i2c_driver(drv);

?/*?Finally?an?I2C?match?*/
?if?(i2c_match_id(driver->id_table,?client))
??return?1;

?return?0;
}

一旦 match 成功,就會調用 driver_probe_device() 以觸發探測設備的行為:

int?driver_probe_device(struct?device_driver?*drv,?struct?device?*dev);
?really_probe(dev,?drv);
??if?(dev->bus->probe)?{
???ret?=?dev->bus->probe(dev);
??}?else?if?(drv->probe)?{
???ret?=?drv->probe(dev);
??}

如果 bus 具有探測設備的能力的話,例如 pci bus, 則會使用 bus->probe() 探測設備,

否則,使用 driver->probe() 探測設備,driver 的 probe 操作跟具體的硬件設備掛鉤。

2. 由 driver_register() 觸發的綁定

int?driver_register(struct?device_driver?*drv);
?bus_add_driver(drv);
??driver_attach(drv);

driver_attach(drv) 會為 driver 遍歷 bus 上的所有 device:

int?driver_attach(struct?device_driver?*drv);
?bus_for_each_dev(drv->bus,?NULL,?drv,?__driver_attach);
??__driver_attach();
???driver_match_device(drv,?dev);
???driver_probe_device(drv,?dev);

和 device_register() 一樣,最終都會調用 driver_match_device(drv, dev),

進而通過 bus 里的 match 函數來判斷是否 device 和 driver 是否匹配。

同樣地,一旦 match 成功,就會調用 driver_probe_device() 以觸發探測設備的行為,后續的操作和注冊設備時是一模一樣的。

3. device 和 drvier 的綁定關系

前面說了綁定是如何被觸發的,現在來明確一下綁定的具體操作。

對于能成功匹配的 device 和 driver,兩者之間的關系是 N 對 1,即可以有多個 device 和 1 個 driver 綁定在一起。

點擊查看大圖

對于 device:

其 driver 成員指向已綁定的 device_driver。

int?driver_probe_device(struct?device_driver?*drv,?struct?device?*dev)
?really_probe(dev,?drv);
??dev->driver?=?drv;

對于 driver:

在 device_driver 里鏈表 klist_devices 保存了該 driver 上已綁定的所有 device。

int?driver_probe_device(struct?device_driver?*drv,?struct?device?*dev)
?really_probe(dev,?drv);
??driver_bound(dev);
???klist_add_tail(&dev->p->knode_driver,?&dev->driver->p->klist_devices);

在 /driver/base/driver.c 中,提供了一些 api,用于遍歷處理 driver 上綁定的所有 device:

  • int driver_for_each_device()
  • struct device *driver_find_device()

四、bus、device、driver 最簡單示例

下面的例子,

構造了一個名為 "simple_bus" 的 bus 實例。

simple_bus.c:注冊了一條名為 "sb" 的 bus,并且提供了注冊 device 和 driver 的 api。

static?int?sb_match(struct?device?*dev,?struct?device_driver?*driver){
?return?!strncmp(dev_name(dev),?driver->name,?strlen(driver->name));
}

struct?bus_type?sb_bus_type?=?{
?.name?=?"sb",
?.match?=?sb_match,
};

static?ssize_t?version_show(struct?bus_type?*bus,?char?*buf){
?return?snprintf(buf,?PAGE_SIZE,?"%s\n",?Version);
}

static?BUS_ATTR_RO(version);

static?void?sb_dev_release(struct?device?*dev){?}

int?register_sb_device(struct?sb_device?*sbdev){
????sbdev->dev.bus?=?&sb_bus_type;
?sbdev->dev.release?=?sb_dev_release;
????dev_set_name(&sbdev->dev,?sbdev->name);
????return?device_register(&sbdev->dev);
}
EXPORT_SYMBOL(register_sb_device);

void?unregister_sb_device(struct?sb_device?*sbdev){
?device_unregister(&sbdev->dev);
}
EXPORT_SYMBOL(unregister_sb_device);

static?int?sb_drv_probe(struct?device?*dev){
?printk(KERN_INFO"sb_drv?probe?%s\n",?dev_name(dev));
?return?0;
}

int?register_sb_driver(struct?sb_driver?*sdrv){
?sdrv->driver.bus?=?&sb_bus_type;
?sdrv->driver.probe?=?&sb_drv_probe;
?return?driver_register(&sdrv->driver);
}
EXPORT_SYMBOL(register_sb_driver);

void?unregister_sb_driver(struct?sb_driver?*driver){
?driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(unregister_sb_driver);

static?int?__init?sb_bus_init(void){
?int?ret;

?ret?=?bus_register(&sb_bus_type);
?if?(ret)?{
??printk(KERN_ERR?"Unable?to?register?sb?bus,?failure?was?%d\n",ret);
??return?ret;
?}
?if?(bus_create_file(&sb_bus_type,?&bus_attr_version))
??printk(KERN_ERR?"Unable?to?create?version?attribute\n");
?return?0;
}

static?void?sb_bus_exit(void){
?bus_unregister(&sb_bus_type);
}

module_init(sb_bus_init);
module_exit(sb_bus_exit);

xxx_chip.c:注冊4個名為 "chipX" 的 device

struct?xxx_chip?{
?char?devname[20];
?struct?sb_device?sdev;
};

int?chipdev_num?=?4;
struct?xxx_chip?*chipdev;

static?void?chip_register_dev(struct?xxx_chip?*dev,?int?index){
?snprintf(dev->devname,?sizeof(dev->devname),?"chip%d",?index);
?dev->sdev.name?=?dev->devname;
?dev_set_drvdata(&dev->sdev.dev,?dev);
?register_sb_device(&dev->sdev);
}

int?chip_init(void){
????int?i;

????chipdev?=?kmalloc(chipdev_num*sizeof?(struct?xxx_chip),?GFP_KERNEL);

????memset(chipdev,?0,?chipdev_num*sizeof?(struct?xxx_chip));
????for?(i?=?0;?i?<?chipdev_num;?i++)?{
??chip_register_dev(chipdev?+?i,?i);
?}

????return?0;
}

void?chip_cleanup(void){
????int?i;
????for?(i?=?0;?i?<?chipdev_num;?i++)?{
??unregister_sb_device(&chipdev[i].sdev);
?}
????kfree(chipdev);
}

module_init(chip_init);
module_exit(chip_cleanup);

xxx_chip_drv.c:注冊1個名為 "chip" 的 driver

static?struct?sb_driver?sculld_driver?=?{
?.driver?=?{
??.name?=?"chip",
?},
};

int?xxx_chipdrv_init(void){
????return?register_sb_driver(&sculld_driver);
}

void?xxx_chipdrv_cleanup(void){
????unregister_sb_driver(&sculld_driver);
}

module_init(xxx_chipdrv_init);
module_exit(xxx_chipdrv_cleanup);

運行效果:

root@buildroot:~#?insmod?simple_bus.ko?
root@buildroot:~#?tree?/sys/bus/sb
/sys/bus/sb
├──?devices
├──?drivers
├──?drivers_autoprobe
├──?drivers_probe
├──?uevent
└──?version

root@buildroot:~#?insmod?xxx_chip.ko?
root@buildroot:~#?tree?/sys/bus/sb
/sys/bus/sb
├──?devices
│???├──?chip0?->?../../../devices/chip0
│???├──?chip1?->?../../../devices/chip1
│???├──?chip2?->?../../../devices/chip2
│???└──?chip3?->?../../../devices/chip3
├──?drivers
├──?drivers_autoprobe
├──?drivers_probe
├──?uevent
└──?version

root@buildroot:~#?insmod?xxx_chip_drv.ko
sb_drv?probe?chip0
sb_drv?probe?chip1
sb_drv?probe?chip2
sb_drv?probe?chip3

root@buildroot:~#?tree?/sys/bus/sb
/sys/bus/sb
├──?devices
│???├──?chip0?->?../../../devices/chip0
│???├──?chip1?->?../../../devices/chip1
│???├──?chip2?->?../../../devices/chip2
│???└──?chip3?->?../../../devices/chip3
├──?drivers
│???└──?chip
│???????├──?bind
│???????├──?chip0?->?../../../../devices/chip0
│???????├──?chip1?->?../../../../devices/chip1
│???????├──?chip2?->?../../../../devices/chip2
│???????├──?chip3?->?../../../../devices/chip3
│???????├──?uevent
│???????└──?unbind
├──?drivers_autoprobe
├──?drivers_probe
├──?uevent
└──?version

通過打印信息可知,device 和 driver 經由 bus 判斷是否 match 之后,執行了 driver 的 probe() 函數,符合我們前面的分析。

五、小結

Linux 的 device model 是個非常復雜的系統。

從一個比較高的層次來看,主要由總線、設備和驅動構成。

內核為了實現這些組件間的相關關系,定義了 kobject 和 kset 這樣的基礎底層數據結構,然后通過 sysfs 文件系統向用戶空間展示發生在內核空間中的各組件間的互聯層次關系,并以文件系統接口的方式為用戶空間程序提供了訪問內核對象屬性信息的簡易方法。

為了控制篇幅,本文并沒有涉及到 kojbect 和 sysfs。

如果你感興趣的話,去挖掘一下以下內容:

  • device model 的底層數據結構 kojbect、kset 是如何工作的?

  • 內核是如何使用 device model 去構建 i2c、spi、usb 等驅動框架?

  • device model 和 sysfs 是如何協同工作的?

  • sysfs 里如何創建屬性文件以訪問設備驅動?

  • sysfs 里的 class 有什么作用?

六、相關參考

《Linux 設備驅動》

  • 第 14 章 Linux 設備模型

《深入 Linux 設備驅動程序內核機制》

  • 第 9 章 Linux 設備驅動模型

《Linux設備驅動開發詳解》

  • 第 5 章 Linux文件系統與設備文件
  • 第 12 章 Linux設備驅動的軟件架構思想

Linux/Documentation/driver-model

  • bus.txt
  • class.txt
  • device.txt
  • driver.txt
  • overview.txt

推薦閱讀:

專輯 | Linux 驅動開發

專輯 | 每天一點 C

專輯 | Linux 系統編程

本文使用 文章同步助手 同步

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容