专栏首页Linux内核深入分析Linux设备驱动模型-Bus

Linux设备驱动模型-Bus

前言

在linux设备驱动模型中,总线可以看作是linux设备模型的核心,系统中的其他设备以及驱动都是以总线为核心围绕。不过驱动程序员在系统中创建一条总线的机会并不多。驱动模型中的总线可以是真是存在的物理总线(USB总线,I2C总线,PCI总线),也可以是为了驱动模型架构设计出的虚拟总线(Platform总线)。为此linux设备驱动模型都将围绕"总线--设备--驱动"来展开,因为符合linux设备驱动模型的设备与驱动都是必须挂载在一个总线上的,无论是实际存在的或者虚拟的。

数据结构

在详细说明bus开始的时候,先需要说明其数据结构,因为一个好的数据结构,就可以很大程度上了解其功能,内核中使用bus_type代表一个总线。

struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	struct device_attribute	*dev_attrs;	/* use dev_groups instead */
	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);

	int (*online)(struct device *dev);
	int (*offline)(struct device *dev);

	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);

	const struct dev_pm_ops *pm;

	const struct iommu_ops *iommu_ops;

	struct subsys_private *p;
	struct lock_class_key lock_key;
};

.name: 总线的名称,比如i2c, spi

.dev_name: 通常用户枚举设备的名称,比如常见的i2c-0, i2c-1....

.dev_root: 该bus默认的父设备。

.dev_attrs: 此bus设备上默认的属性。

.bus_groups, dev_groups, drv_groups: 分别是总线, 设备,驱动的属性。

.match: 当一个设备或者驱动添加到此总线上的时候,bus就会调用match对设备和驱动一一匹配的。

.uevent: 当该bus下的任何设备,驱动发生增加,删除的操作时,就会调用到uevnet函数。

.probe,remove: 当一个driver添加到系统的时候,是先会调用bus的probe的,如果bus的probe都没有初始化,则bus下的任何驱动或者设备都无法使用。但是不是所有的bus都会实现probe函数的,虚拟的bus是不存在初始化的。

.supend,resume: bus电源管理回调函数。

.pm: 一组电源管理的回调函数。

p: 一个用来管理总线上设备与驱动的数据结构。

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;
};

.subsys: 用来表示bus所在的子系统,系统中所有注册的bus都将指向bus_kset。

.devices_kset: 代表该bus下所有设备的集合。

.drivers_kset: 代表该bus下所有驱动的集合。

.interfaces: 用于保存该bus下所有的interface。

.klist_devices: 代表该bus下所有设备的链表。

.klist_drivers: 代表该bus下所有驱动的链表。

.bus_notifier: 该bus创建的通知链。

.drivers_autoprobe: 代表在该bus下注册某一个驱动或者设备的时候,是否需要自动匹配

.bus: 指向所属的bus。

.class: 指向所属的class。

BUS相关的函数

  • buses_init(创建bus/system集合)
int __init buses_init(void)
{
	bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
	if (!bus_kset)
		return -ENOMEM;

	system_kset = kset_create_and_add("system", NULL, &devices_kset->kobj);
	if (!system_kset)
		return -ENOMEM;

	return 0;
}

通过上述的操作将在sysfs下创建了一个名字为"bus"的目录,同时也会在/sys/devices/下创建一个名字为"system"的目录。

在前面kset学习中知道,当这个名为bus的kset下有状态变化的时候,就会发送uevent消息的,在发送过程中就会调用到bus_uevent_ops中实现的函数。

static int bus_uevent_filter(struct kset *kset, struct kobject *kobj)
{
	struct kobj_type *ktype = get_ktype(kobj);

	if (ktype == &bus_ktype)
		return 1;
	return 0;
}

static const struct kset_uevent_ops bus_uevent_ops = {
	.filter = bus_uevent_filter,
};

在bus_uevent_ops中只实现了filter函数。此函数只要是判断发生状态的kobj对象类型是不是总线类型,不是就不会上报该事件的。

  • bus_register(注册一个总线到系统中)
int bus_register(struct bus_type *bus)
{
	int retval;
	struct subsys_private *priv;
	struct lock_class_key *key = &bus->lock_key;

	priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL);         //分配subsys_private结构体
	if (!priv)
		return -ENOMEM;

	priv->bus = bus;                                        //设置bus指针
	bus->p = priv;                                          //设置bus->p指针
 
	BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);       //初始化bus通知链

	retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);  //设置该bus的名字,体现在/sys/bus/下
	if (retval)
		goto out;

	priv->subsys.kobj.kset = bus_kset;         //设置该bus所属的kset, 以及ktype
	priv->subsys.kobj.ktype = &bus_ktype;
	priv->drivers_autoprobe = 1;

	retval = kset_register(&priv->subsys);     //注册该kset到系统中,表现在/sys/bus下
	if (retval)
		goto out;

	retval = bus_create_file(bus, &bus_attr_uevent); //创建该bus的uevent属性
	if (retval)
		goto bus_uevent_fail;

	priv->devices_kset = kset_create_and_add("devices", NULL,        
						 &priv->subsys.kobj);  //在该bus下创建devices目录
	if (!priv->devices_kset) {
		retval = -ENOMEM;
		goto bus_devices_fail;
	}

	priv->drivers_kset = kset_create_and_add("drivers", NULL,
						 &priv->subsys.kobj);  //在该bus下创建drivers目录
	if (!priv->drivers_kset) {
		retval = -ENOMEM;
		goto bus_drivers_fail;
	}

	INIT_LIST_HEAD(&priv->interfaces);          //初始化interface, mutex, klist_devices, klist_drivers
	__mutex_init(&priv->mutex, "subsys mutex", key);
	klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
	klist_init(&priv->klist_drivers, NULL, NULL);

	retval = add_probe_files(bus);      //创建bus的probe属性
	if (retval)
		goto bus_probe_files_fail;

	retval = bus_add_groups(bus, bus->bus_groups);  //创建bus的属性
	if (retval)
		goto bus_groups_fail;

	pr_debug("bus: '%s': registered\n", bus->name);
	return 0;
}

比如常见的通过bus_register创建一个i2c总线,就会在/sys/bus/下创建i2c目录, 以及devices目录, driver目录, 以及probe属性, uevent属性。

root@test:/sys/bus/i2c # ls -l
drwxr-xr-x root     root              2012-01-01 08:17 devices
drwxr-xr-x root     root              2012-01-01 08:17 drivers
-rw-r--r-- root     root         4096 2012-01-01 08:17 drivers_autoprobe
--w------- root     root         4096 2012-01-01 08:17 drivers_probe
--w------- root     root         4096 2012-01-01 08:17 uevent

而在devices下就是该i2c-bus下所有的设备,drivers下就是i2c-bus下所有去的驱动。

  • subsys_register(注册一个子系统)
static int subsys_register(struct bus_type *subsys,
			   const struct attribute_group **groups,
			   struct kobject *parent_of_root)
{
	struct device *dev;
	int err;

	err = bus_register(subsys);       //注册一个总线
	if (err < 0)
		return err;

	dev = kzalloc(sizeof(struct device), GFP_KERNEL);        //分配一个device结构,然后设置名字
	if (!dev) {
		err = -ENOMEM;
		goto err_dev;
	}

	err = dev_set_name(dev, "%s", subsys->name);
	if (err < 0)
		goto err_name;

	dev->kobj.parent = parent_of_root;                //设置该device的parent,通常都是在buses_init中的system_kset
	dev->groups = groups;                             //表现在sys中就是在/sys/devices/system下新建一个subsys->name的目录
	dev->release = system_root_device_release;

	err = device_register(dev);  //注册该设备
	if (err < 0) 
		goto err_dev_reg;

	subsys->dev_root = dev;
	return 0;
}
  • subsys_system_register(/sys/devices/system/下注册一个子系统)
int subsys_system_register(struct bus_type *subsys,
			   const struct attribute_group **groups)
{
	return subsys_register(subsys, groups, &system_kset->kobj);
}

该函数将会调用上面的subsys_register函数。举例:

static int __init clockevents_init_sysfs(void)
{
	int err = subsys_system_register(&clockevents_subsys, NULL);

	if (!err)
		err = tick_init_sysfs();
	return err;
}
struct bus_type clockevents_subsys = {
	.name		= "clockevents",
	.dev_name       = "clockevent",
};

这样的话,就会在/sys/devices/system下创建一个名字为"clockevent"设备,在/sys/bus/下创建一个名字为“clockevents”的总线

root@test:/sys/bus/clockevents # ls
devices
drivers
drivers_autoprobe
drivers_probe
uevent
root@test:/sys/devices/system/clockevents # ls -l                   
drwxr-xr-x root     root              2012-01-01 08:00 broadcast
drwxr-xr-x root     root              2012-01-01 08:00 clockevent0
drwxr-xr-x root     root              2012-01-01 08:00 clockevent1
drwxr-xr-x root     root              2012-01-01 08:00 clockevent2
drwxr-xr-x root     root              2012-01-01 08:00 clockevent3
drwxr-xr-x root     root              2012-01-01 08:00 clockevent4
drwxr-xr-x root     root              2012-01-01 08:00 clockevent5
drwxr-xr-x root     root              2012-01-01 08:00 clockevent6
drwxr-xr-x root     root              2012-01-01 08:00 clockevent7
drwxr-xr-x root     root              2012-01-01 08:00 power
-rw-r--r-- root     root         4096 2012-01-01 08:00 uevent

不过根据该函数的注释“Do not use this interface for anything new, it exists for compatibility with bad ideas only.” 意思是不在建议使用该函数了。

  • subsys_virtual_register(在/sys/devices/virtual/下创建一个子系统)
int subsys_virtual_register(struct bus_type *subsys,
			    const struct attribute_group **groups)
{
	struct kobject *virtual_dir;

	virtual_dir = virtual_device_parent(NULL);
	if (!virtual_dir)
		return -ENOMEM;

	return subsys_register(subsys, groups, virtual_dir);
}

中间会调用virtual_device_parent函数

struct kobject *virtual_device_parent(struct device *dev)
{
	static struct kobject *virtual_dir = NULL;

	if (!virtual_dir)
		virtual_dir = kobject_create_and_add("virtual",
						     &devices_kset->kobj);

	return virtual_dir;
}

也就是在/sys/devices/下创建一个名字为“virtual”的目录。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Mutex(互斥锁)

    在信号量最后的部分说,当count=1的时候可以用信号量实现互斥。在早期的Linux版本中就是当count=1来实现mutex的。

    DragonKingZhu
  • ThreadInfo结构和内核栈的两种关系

    本来本节是要学习内核启动的第一个进程的建立,也就是0号进程,也称idle进程,也称swapper进程。但是在学习第一个进程建立之前需要先学习threadinfo...

    DragonKingZhu
  • Linux音频驱动-ALSA概述

    ALSA(Advanced Linux Sound Architecture)是linux上主流的音频结构,在没有出现ALSA架构之前,一直使用的是OSS(Op...

    DragonKingZhu
  • 看故事,学排序

    在面试的过程中,有的面试官也会问到这个排序。今天我们看一个故事来感受一下冒泡排序的过程。

    用户1260737
  • VulnHub通关日记-DC_4-Walkthrough

    与以前的DC版本不同,此版本主要为初学者/中级用户设计。只有一个标志,但是从技术上讲,有多个入口点,就像上次一样,没有任何线索

    Gcow安全团队
  • 爬虫篇 | 高级爬虫( 二):Scrapy爬虫框架初探

    先确保你已经在电脑上安装好了Scrapy模块,说一下Scrapy安装的问题,网上大部分安装办法已经失效了,主要是因为 网站:https://www.lfd.uc...

    叫我龙总
  • 高级爬虫( 二):Scrapy爬虫框架初探

    先确保你已经在电脑上安装好了Scrapy模块,说一下Scrapy安装的问题,网上大部分安装办法已经失效了,主要是因为 网站:https://www.lfd.uc...

    叫我龙总
  • Web前端知识体系大全

    1、前言   大约在几个月之前,让我看完了《webkit技术内幕》这本书的时候,突然有了一个想法。想把整个web前端开发所需要的知识都之中在一个视图中,形成一个...

    用户1667431
  • 新手程序员登录服务器杀进程!高级:你别再瞎Kill进程服务了

    kill命令可将指定的信号发送给相应的进程或工作。kill命令默认使用信号为15,用于结束进程或工作。如果进程或工作忽略此信号,则可以使用信号9,强制杀死进程或...

    IT大咖说
  • umount.nfs: /mnt/web: device is busy

    卸载失败 umount.nfs: /mnt/web: device is busy umount.nfs: /mnt/web: device is busy 查...

    小柒2012

扫码关注云+社区

领取腾讯云代金券