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
└── versionroot@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
└── versionroot@buildroot:~# insmod xxx_chip_drv.ko
sb_drv probe chip0
sb_drv probe chip1
sb_drv probe chip2
sb_drv probe chip3root@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」获取学习资料网盘链接。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/466250.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【乡音】海安话四级考试

海安话四级考试海安话国家四级考试试卷-------------启用前★绝密------------&#xff08;中国标准话语言研究中心命题&#xff09; 准考证号___________ 姓名_____________ 座位号___________ 一、选择题&#xff08;共五题&#xf…

POJ1179 Polygon 【例题精讲】

题意&#xff1a;多边形游戏是一个单人玩的游戏&#xff0c;开始时有一个由n个顶点构成的多边形。每个顶点被赋予一个整数值&#xff0c;每条边被赋予一个运算符“”或“*”。所有边依次用整数从1到n编号游戏第1步&#xff0c;将一条边删除随后n-1步按以下方式操作(1)选择一条边…

学模拟电路的神器everycircuit

之前转的几篇文章&#xff0c;大家对里面的软件非常感兴趣&#xff0c;所以就给你们找来了&#xff0c;这是一个仿真软件。▌官网https://everycircuit.com/▌视频介绍&#xff0c;视频是老外的比如&#xff0c;你想仿真一个555定时器▌手机上也可以用&#xff0c;可以在我刚才…

我也想再上个学

关注我的同学应该知道&#xff0c;我不止在一次告诉大家读书是可以让大家能有更多的机会的&#xff0c;有时候自己也想什么时候再深造一下&#xff0c;等楠哥不那么需要我了&#xff0c;我也不用那么努力赚钱了&#xff0c;再去读读书&#xff0c;在学校里面打打篮球&#xff0…

使用GenerateDriverDiskISO无需软驱安装WINDOWS操作系统

转自&#xff1a;[url]http://bbs.wuyou.com/viewthread.php?tid121630&extra&page1[/url]附件中有详细文章内容及GenerateDriverDiskISO.RAR下载作者: lookskyoo 时间: 2008-3-5 11:19 标题: &#xff3b;chenhall找到最好的解决方案&#xff3d;自认为最好的…

ESP32搞的NES游戏掌机

本文转自立创开源硬件平台作者&#xff1a;micespring原文链接&#xff1a;https://oshwhub.com/micespring/esp32-nesemu▌简介一个基于ESP32制作的开源游戏掌机&#xff0c;可以运行标准的NES&#xff08;日版为FC&#xff09;游戏&#xff0c;支持单声道音频。让你随时回味儿…

Matplotlib从文件绘图时Y轴坐标不正确

问题描述&#xff1a; 从文件中读取X坐标和Y坐标&#xff0c;绘制折线图&#xff0c;代码和结果如下&#xff1a; import matplotlib.pyplot as plt import matplotlib.animation as animation from matplotlib import stylestyle.use(dark_background)fig plt.figure()graph_…

全球最囧的爱情测试.....

据说这是全球最准的爱情测试&#xff1a;http://love.xf001.com/?uid224632 。。。。。。。。。。。。。。。。。。。。。。。。。。 。。。。。。。。。。。。。。。。。。。。。。。。。。 。。。。。。。。。。。。。。。。。。。。。。。。。。 。。。。。。。。。。。。…

回答嵌入式初学者的一些问题

如图&#xff0c;最近类似的提问很多&#xff0c;上周末&#xff0c;刚打球回来&#xff0c;跟一起刚工作的同学聊了一个晚上。要知道&#xff0c;一个晚上的时间我可以做很多事情&#xff0c;可以玩好多局王者荣耀&#xff0c;可以看很多论坛的资料。但是我愿意花费时间在他身…

什么是RAC定位?

笔者因为工作原因&#xff0c;接触到一种叫做RAC的定位产品&#xff0c;该定位产品不同于一般的定位模组&#xff0c;它产品外表看起来很像一个GPS定位天线&#xff0c;通过一个4芯线缆&#xff08;供电串口通信&#xff09;直接和其它MCU/CPU 相连接&#xff0c;输出定位信息。…

15个有趣的555电路,没事可以自己做一做

NE555几乎是一个非常常见的一个芯片&#xff0c;在很多的电器里都能看到它的身影&#xff0c;本文为大家总结了一些有趣的NE555电路。013*3*3光立方02激光射线03金属探测器04音乐盒05电子转盘06舵机测试器07反应计时器08高压发生器09触摸开关10交通信号灯11电视信号干扰器12自行…

使用ABAP和JavaScript代码生成PDF文件的几种方式

ABAP 方法1&#xff1a;使用ABAP Adobe Lifecycle Enterprise Service 详细步骤参考我的博客Convert word document into PDF via Adobe Livecycle Enterprise service 方法2&#xff1a;使用ABAP Webdynpro里的InteravtiveForm控件 Adobe Form Template 详细步骤参考我的博客…

Configuration Manager 纯模式所需的 PKI 证书的分步部署示例

Configuration Manager 纯模式所需的 PKI 证书的分步部署示例&#xff1a;Windows Server 2008 证书颁发机构此分步示例部署使用 Windows Server 2008 证书颁发机构 (CA)&#xff0c;提供一些过程以指导您完成创建和部署 Configuration Manager 2007 在纯模式下操作所需的公钥基…

周琦能和范志毅比高下?

在最新的直播中&#xff0c;徐静雨就专门谈到了周琦与范志毅的对比&#xff0c;他先是举例乒乓球界的张继科与篮球界的姚明对比&#xff0c;张继科在乒乓球界的成就要远高于姚明&#xff0c;但是姚明的影响力却是张继科无法比较的。而在篮球界与足球界的对比中&#xff0c;他明…

[原创]C/C++语言中,如何在main.c或main.cpp中调用另一个.c文件

C/C语言中&#xff0c;如何在main.cpp中调用另一个.c文件主要有5种思路&#xff1a; 1、在VS2012 IDE中&#xff0c;将被引用的.c文件后缀名全部修改为.h&#xff0c;然后通过IDE的解决方案资源管理器中鼠标右键单击“头文件”-"添加"-“现有项”&#xff0c;选中修改…

[零基础学JAVA]Java SE应用部分-35.JAVA类集之四

JAVA按以下重要顺序 面向对象 类集框架 JDBC 文件编程本季目标主要讲解以下的内容&#xff1a; Collection List Set Map Iterator ListIteator Enumeration for…each语句&#xff08;JDK 1.5的新特性&#xff09; Comparable接口 二叉树1、类集结构&#xff08…

可在单片机上运行的简易图形库

来源&#xff1a;电子森林&#xff0c;排版&#xff1a;晓宇微信公众号&#xff1a;芯片之家&#xff08;ID&#xff1a;chiphome-dy&#xff09;发现一个网站 - http://www.technoblogy.com/&#xff0c;基本都是关于用ATtiny单片机做各种创意设计的&#xff0c;非常适合初学单…

欧拉回路 欧拉路径

欧拉路径&#xff08;瞎&#xff09;定义 : 如果有一条路径使得能够走完所有的边且每一条边经过有且只有一次&#xff0c;这样的路径叫做欧拉路径 欧拉回路定义 : 如果有从起点出发最后回到起点的一条路径使得能够走完所有的边且每条边经过有且只有一次&#xff0c;称其为欧拉回…

终于搞清楚开漏输出和推挽输出这个鬼东西

先说下推挽输出&#xff0c;简单的说&#xff0c;就是想输出高电平&#xff0c;就输出高电平&#xff0c;想输出低电平就输出低电平。推挽电路上面是NPN三极管&#xff0c;下面是PNP三极管&#xff0c;请注意输入端和输出端的波形。下面是输入波形当输入为正时&#xff0c;上面…

被称为“C#圣经”的权威著作!

媒体评论 “哇&#xff0c;这本书太棒了&#xff01;对.NET 3.5和CLR的阐释非常深入。它已经成为我的头号参考书。强烈推荐给所有.NET程序员。” ——Jeff Wilcox&#xff0c;微软.NET开发团队资深工程师 “Troelsen大师王者归来了&#xff01;C#程序员分为两类&#xff1a;读…