Linux 驱动开发 | 驱动世界里的宏伟建筑

嵌入式Linux

共 15823字,需浏览 32分钟

 ·

2021-03-17 07:42

哈喽,我是老吴。

是否每一个上进的人都会觉得自己还可以再努力一点?

事情到了最后,只要没达成目的,总能把失败的原因归为 "没有再努力一点"。

但是,对努力的最大错误认知就是:时间越长,过程越痛苦,代表我越努力。

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

以下是正文:

一、什么是 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 系统编程





推荐阅读:
专辑|Linux文章汇总
专辑|程序人生
专辑|C语言
我的知识小密圈

关注公众号,后台回复「1024」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~



浏览 22
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报