天天看點

Linux 驅動開發 | 驅動世界裡的宏偉建築

哈喽,我是老吳。

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

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

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

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

以下是正文:

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

一、什麼是 device model?

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

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

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

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

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

Linux 驅動開發 | 驅動世界裡的宏偉建築

點選檢視大圖

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;
};      
Linux 驅動開發 | 驅動世界裡的宏偉建築

兩個 klist 成員以連結清單的形式将該總線上所有的驅動與裝置連結到一起。

struct kset *drivers_kset 和 struct kset *devices_kset 是在向系統注冊目前新總線時動态生成的容納該總線上所有驅動與裝置的 kset。

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

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

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

Linux 驅動開發 | 驅動世界裡的宏偉建築

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 綁定在一起。

Linux 驅動開發 | 驅動世界裡的宏偉建築

對于 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 驅動開發 | 驅動世界裡的宏偉建築

收獲了什麼?

  • 溫習了一些關于基金定投的基礎知識;

你和我各有一個蘋果,如果我們交換蘋果的話,我們還是隻有一個蘋果。但當你和我各有一個想法,我們交換想法的話,我們就都有兩個想法了。

覺得文章對你有價值,不妨 在看 + 分享。

推薦閱讀:

專輯 | Linux 驅動開發

繼續閱讀