Device_driver_model 设备驱动模型

文章目录

  • 设备驱动模型
    • 相关介绍
    • kobject/kset
    • bus、device和driver的数据类型
  • sysfs文件/目录
    • sysfs和Kobject的关系
    • sysfs和kobj_type的关系
      • **attribute的创建**
      • **attibute文件的read和write**
    • sysfs设备目录层次结构
  • **设备模型框架下驱动开发的基本步骤**
    • **设备驱动probe时机**
    • **probe函数的调用**
      • Device注册过程----device_register()
      • Driver注册过程----driver_register()
      • 小结
  • 附录

设备驱动模型

相关介绍

简单理解起来,设备驱动模型主要作用有两点:

  1. 在sysfs下形成树状层次结构,然后由sysfs文件系统作为设备入口。(以Kobject/Kset为主)
  2. 实现device、bus、driver的统一管理,方便设备与驱动的match和probe

框架大概是这样的:

Sysfs
Kobject/Kset
device–bus–driver
platform … SPI I2C

/sys 文件系统下的目录结构

/sys/devices 对所有设备的分层次表达
/sys/dev 维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件
/sys/bus 这是内核设备按总线类型分层放置的目录结构, devices 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,它也是构成 Linux 统一设备模型的一部分;
/sys/class 这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在 /sys/class/input 之下,而不论它们是以何种总线连接到系统。
/sys/block 这里是系统中当前所有的块设备所在 ,由于历史遗留问题一直存在,但在2.6.22开始已经被标记过时,为了向后兼容才保留着,但其内容已经链接到 /sys/devices/ 中真实设备的符号链接文件 。
/sys/firmware 这里是系统加载固件机制的对用户空间的接口,关于固件有专用于固件加载的一套API,在附录 LDD3 一书中有关于内核支持固件加载机制的更详细的介绍;
/sys/fs 这里按照设计是用于描述系统中所有文件系统,包括文件系统本身和按文件系统分类存放的已挂载点
/sys/module 这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在 /sys/module
/sys/power 这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。

不同的硬件平台上的设备是多种多样的,为了使设备都能抽象出来进行统一管理,Linux内核定义了一个kobject结构体,/sys下的每个目录都是通过kobject来抽象的,而kset即是一个特殊的kobject,也是同类型的kobject的一个集合,kobject和sysfs自然的绑定在一起,这样能方便组织驱动中的各种联系。

在Linux设备驱动模型中,主要需要的基本结构为

类型 内容 数据结构 /sys目录
设备(Devices) 设备是此模型中最基本的类型,以设备本身的连接按层次组织 strust device /sys/devices/*
设备驱动(Device Drivers) 在一个系统中安装多个相同设备,只需要一份驱动程序的支持 struct device_driver /sys/bus/pci/drivers/*
总线(Bus) 在整个总线级别对此总线上连接的所有设备进行管理 struct bus_type /sys/bus/*
设备类别(Device Classes) 这是按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备,都会出现在 /sys/class/input/ 下 struct class /sys/class/*

kobject/kset

从内核实现sysfs层次结构时所使用的数据结构角度来入手分析,Linux 统一设备模型又是以两种基本数据结构(Kobject/Kset)进行树型和链表型结构组织的。

/******************************************************************************
* Kobject是基本的数据类型,每个Kobject都会在"/sys/“文件系统中以目录的形式出现。
* struct kref 内含一个 atomic_t 类型用于引用计数, parent 是单个指向父节点的指针, entry 
* 用于父 kset 以链表头结构将 kobject 结构维护成双向链表;
*******************************************************************************/
struct kobject {
	const  char			*name;  //Kobject名字
	struct list_head	entry;	//用于把kobject添加到Kset中的list_head
	struct kobject		*parent;//指向parent kobject,以此形成结构
	struct kset			*kset;  //该kobject属于的kset,可以设置为NULL。如果存在,且还未制定parent,会默认kset为parent
	struct kobj_type	*ktype; //该Kobject属于的kobj_type,每个Kobject必须有一个ktype。
	struct kernfs_node	 *sd; /* sysfs directory entry */
	struct kref		kref;	    //用于原子操作的引用计数。
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
	struct delayed_work	release; 
#endif
	unsigned int state_initialized:1;//表示是否初始化
	unsigned int state_in_sysfs:1;	 //指示该Kobject是否已在sysfs中呈现,以便在自动注销时从sysfs中移除。 
	unsigned int state_add_uevent_sent:1;
	unsigned int state_remove_uevent_sent:1;
	unsigned int uevent_suppress:1;  //如果该字段为1,则表示忽略所有上报的uevent事件。
};
/******************************************************************************
* kset类型是属于特定子系统的一组特定类型的kobjects。
* KSET定义了一组Kobjects。它们可以是不同的“类型”,但总体上,这些Kobjects都希望被分组在一
* 起,并以相同的方式操作。KSET用于定义Kobjects中发生的属性回调和其他常见事件。
*******************************************************************************/
struct kset {
	struct list_head list;  //该kset所有kobjects的list
	spinlock_t list_lock;	//
	struct kobject kobj;	//嵌入的kobject
	const struct kset_uevent_ops *uevent_ops; //kset操作函数
} ;

Device_driver_model 设备驱动模型_第1张图片

这三个kobjects同属于一个kset,在没有指定parent的情况下,默认为它们的parent为kset内嵌的kobject。

kobj_type

有一些kobject下会有一些属性,在 /sys 下表现形式就是文件,通过这些文件可以向用户提供一些接口操作。

/* /include/linux/kobject.h */
struct kobj_type {
	void (*release)(struct kobject *kobj); //释放kobject函数接口
	const struct sysfs_ops *sysfs_ops;	   //属性操作函数实现接口
	struct attribute **default_attrs;	   //属性定义
	const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
	const void *(*namespace)(struct kobject *kobj);
};

/* /include/linux/sysfs.h */
struct attribute {
	const char		*name;
	umode_t			mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	bool			ignore_lockdep:1;
	struct lock_class_key	*key;
	struct lock_class_key	skey;
#endif
};

attribute, 属性。它以文件的形式输出到sysfs的目录当中。在kobject对应的目录下面。文件名就是name。文件读写的方法对应于kobj type中的sysfs ops。

小结

在Linux统一设备模型中,核心内容就是使用Bus、Class、Device、Driver四个核心数据结构,将大量不同功能的硬件设备以及驱动,以树状结构进行归纳、抽象,从而进行统一管理这也是kobject产生的原因。

目前为止,Kobject在设备模型中主要提供的功能为:

  1. 通过parent指针,可以将所有Kobject以层次结构的形式组合起来。
  2. 使用一个引用计数 kref,来记录Kobject被引用的次数,并在引用次数变为0时把它释放(这是Kobject诞生时的唯一功能)。
  3. 和sysfs虚拟文件系统配合,将每一个Kobject及其特性,以文件的形式,开放到用户空间。

由于kobject可以自动释放,使得kobject必须是动态分配的。而kobject大多数时候是内嵌在大型的数据结构中(如Kset、device_driver等),因此这些大型数据结构也是需要动态分配、动态释放的,释放的时机是内嵌的Kobject释放的时候。但是Kobject的释放是由Kobject模块自动完成的(在引用计数为0时),那么怎么一并释放包含自己的大型数据结构呢?

这时Kobj_type就派上用场了。我们知道,Kobj_type中的release回调函数负责释放Kobject(甚至是包含Kobject的数据结构)的内存空间,那么Kobj_type及其内部函数,是由谁实现呢?是由上层数据结构所在的模块!因为只有它,才清楚Kobject嵌在哪个数据结构中,并通过Kobject指针以及自身的数据结构类型,找到需要释放的上层数据结构的指针,然后释放它。

由此可知,每一个内嵌Kobject的数据结构,例如kset、device、device_driver等等,都要实现一个Ktype,并定义其中的回调函数。同理,sysfs相关的操作也一样,必须经过ktype的中转,因为sysfs看到的是Kobject,而真正的文件操作的主体,是内嵌Kobject的上层数据结构!

bus、device和driver的数据类型

/******************************************************************************
* bus_type 设备总线类型
*******************************************************************************/
struct bus_type {
	const char		*name; // 总线名称
	const char		*dev_name; // 用于子系统枚举像("foo%d",dev->id)这样的的设备
	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);
    	// 回调函数,用于匹配 device和driver
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
    	// 回调函数,在device发生添加、移除或者其他动作时候调用,用来修改环境变量
	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);

	int (*num_vf)(struct device *dev);

	int (*dma_configure)(struct device *dev);

	const struct dev_pm_ops *pm;

	const struct iommu_ops *iommu_ops;

	struct subsys_private *p; //私有数据,内有kset、klist
	struct lock_class_key lock_key;

	bool need_parent_lock;
};

/* /drivers/base/base.h */
struct subsys_private {
	struct kset subsys;  //在bus_type中代表bus本身,它下面可以包含其它的kset或者其它的kobject;
	struct kset *devices_kset; //bus下面所有的device
	struct list_head interfaces; //用于保存该bus下所有的interface。
	struct mutex mutex;

	struct kset *drivers_kset;//下面所有的device_driver
	struct klist klist_devices; // 保存了本bus下所有device的指针
	struct klist klist_drivers; // 保存了本bus下所有driver的指针
	struct blocking_notifier_head bus_notifier;
	unsigned int drivers_autoprobe:1; // 1表示bus下的device或者driver自动probe
	struct bus_type *bus; //用于保存上层的bus

	struct kset glue_dirs;
	struct class *class; //用于保存上层的class
};
/******************************************************************************
* device 设备类型
*******************************************************************************/
struct device {
	struct device		*parent;
	/* 父设备。在大多数情况下,父设备是某种总线或主机控制器。 如果parent为NULL,则设备是顶级设备*,
	 * 这通常不是您想要的。 */
	struct device_private	*p;
	/* 一个用于struct device的私有数据结构指针,该指针中会保存子设备链表、用于添加到
	 * bus/driver/prent等设备中的链表头等等 */
	struct kobject kobj; //该设备对应的kobject
	const char		*init_name; //设备名称
	const struct device_type *type;

	struct mutex		mutex;	//用于将调用与其驱动程序同步的互斥锁。

	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this device */
    
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
	void		*driver_data;	/* Driver data, set and get with
					   dev_set/get_drvdata */
	struct dev_links_info	links;
	struct dev_pm_info	power;
	struct dev_pm_domain	*pm_domain;

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
	struct irq_domain	*msi_domain;
#endif
#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
	struct list_head	msi_list;
#endif

#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	const struct dma_map_ops *dma_ops;
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations such descriptors. */
	unsigned long	dma_pfn_offset;

	struct device_dma_parameters *dma_parms;

	struct list_head	dma_pools;	/* dma pools (if dma'ble) */

	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
#ifdef CONFIG_DMA_CMA
	struct cma *cma_area;		/* contiguous memory area for dma
					   allocations */
#endif
	/* arch specific additions */
	struct dev_archdata	archdata;

	struct device_node	*of_node; /* associated device tree node */
	struct fwnode_handle	*fwnode; /* firmware device node */

	dev_t			devt;	
    /* dev_t是一个32位的整数,它由两个部分(Major和Minor)组成,在需要以设备节点的形式(字符设备和
     * 块设备)向用户空间提供接口的设备中,当作设备号使用。在这里,该变量主要用于在sys文件系统中,为每
     * 个具有设备号的device,创建/sys/dev/* 下的对应目录 */
	u32			id;	/* device instance */

	spinlock_t		devres_lock;
	struct list_head	devres_head;

	struct klist_node	knode_class;
	struct class		*class;
	const struct attribute_group **groups;	/* 该设备的默认attribute集合 */

	void	(*release)(struct device *dev);
	struct iommu_group	*iommu_group;
	struct iommu_fwspec	*iommu_fwspec;

	bool			offline_disabled:1;
	bool			offline:1;
	bool			of_node_reused:1;
	bool			dma_32bit_limit:1;
};
/******************************************************************************
* device_driver 设备驱动
*******************************************************************************/
struct device_driver {
	const char		*name; //drier名
	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;
	void (*coredump) (struct device *dev);

	struct driver_private *p;
};

sysfs文件/目录

sysfs和Kobject的关系

由sysfs/kobject可知,每一个Kobject,都会对应sysfs中的一个目录。因此在将Kobject添加到Kernel时,create_dir接口会调用sysfs文件系统的创建目录接口,创建和Kobject对应的目录。

/* lib/kobject.c */
static int create_dir(struct kobject *kobj)
{
	const struct kobj_ns_type_operations *ops;
	int error;

	error = sysfs_create_dir_ns(kobj, kobject_namespace(kobj));
    /**************************
     * 函数kobject_namespace(kobj)
     * -- 如果父节点启用了名称空间操作,则返回@kobj的名称空间标记,因此@kobj
     * -- 应该具有与之关联的名称空间标记。否则返回 NULL。 
     **************************/
	if (error)
		return error;

	error = populate_dir(kobj);
	if (error) {
		sysfs_remove_dir(kobj);
		return error;
	}

    ...
    /* 这部分是与生成时候的稳定有关,目前不作学习 */
    ...
	return 0;
}
 
/* fs/sysfs/dir.c */
/**
 * sysfs_create_dir_ns - 用 namespace tag 创建对象的目录
 * @kobj: 目标kobject
 * @ns: the namespace tag to use
 */
int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
{
	struct kernfs_node *parent, *kn;

	BUG_ON(!kobj);

	if (kobj->parent)
		parent = kobj->parent->sd;
	else
		parent = sysfs_root_kn;

	if (!parent)
		return -ENOENT;

	kn = kernfs_create_dir_ns(parent, kobject_name(kobj),
				  S_IRWXU | S_IRUGO | S_IXUGO, kobj, ns);
	if (IS_ERR(kn)) {
		if (PTR_ERR(kn) == -EEXIST)
			sysfs_warn_dup(parent, kobject_name(kobj));
		return PTR_ERR(kn);
	}

	kobj->sd = kn;
	return 0;
}

sysfs和kobj_type的关系

由前文可知,有一些kobject下会有一些属性,在 /sys 下表现形式就是文件,通过这些文件可以向用户提供一些接口操作。即:

	const struct sysfs_ops *sysfs_ops;	   //属性操作函数实现接口
	struct attribute **default_attrs;	   //属性定义

attribute分两种

 /* include/linux/sysfs.h, line 30,普通attribute */
struct attribute {
	const char		*name;
	umode_t			mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	bool			ignore_lockdep:1;
	struct lock_class_key	*key;
	struct lock_class_key	skey;
#endif
};
/* include/linux/sysfs.h, line 159,二进制attribute */
struct bin_attribute {
	struct attribute	attr;
	size_t			size;
	void			*private;
	ssize_t (*read)(struct file *, struct kobject *, struct bin_attribute *,
			char *, loff_t, size_t);
	ssize_t (*write)(struct file *, struct kobject *, struct bin_attribute *,
			 char *, loff_t, size_t);
	int (*mmap)(struct file *, struct kobject *, struct bin_attribute *attr,
		    struct vm_area_struct *vma);
};

struct attribute为普通的attribute,使用该attribute生成的sysfs文件,只能用字符串的形式读写。而struct bin_attribute在struct attribute的基础上,增加了read、write等函数,因此它所生成的sysfs文件可以用任何方式读写。

attribute的创建

/** /fs/sysfs/file.c  line 321
 * sysfs_create_file_ns - create an attribute file for an object with custom ns
 * @kobj: object we're creating for
 * @attr: attribute descriptor
 * @ns: namespace the new file should belong to
 */
int sysfs_create_file_ns(struct kobject *kobj, const struct attribute *attr,
			 const void *ns)
{
	BUG_ON(!kobj || !kobj->sd || !attr);

	return sysfs_add_file_mode_ns(kobj->sd, attr, false, attr->mode, ns);

}

/* /fs/sysfs/file.c  line 246 */
int sysfs_add_file_mode_ns(struct kernfs_node *parent,
			   const struct attribute *attr, bool is_bin,
			   umode_t mode, const void *ns)
{
	struct lock_class_key *key = NULL;
	const struct kernfs_ops *ops;
	struct kernfs_node *kn;
	loff_t size;

	if (!is_bin) {
		struct kobject *kobj = parent->priv;
		const struct sysfs_ops *sysfs_ops = kobj->ktype->sysfs_ops;

		/* every kobject with an attribute needs a ktype assigned */
		if (WARN(!sysfs_ops, KERN_ERR
			 "missing sysfs attribute operations for kobject: %s\n",
			 kobject_name(kobj)))
			return -EINVAL;

		if (sysfs_ops->show && sysfs_ops->store) {
			if (mode & SYSFS_PREALLOC)
				ops = &sysfs_prealloc_kfops_rw;
			else
				ops = &sysfs_file_kfops_rw;
		} else if (sysfs_ops->show) {
			if (mode & SYSFS_PREALLOC)
				ops = &sysfs_prealloc_kfops_ro;
			else
				ops = &sysfs_file_kfops_ro;
		} else if (sysfs_ops->store) {
			if (mode & SYSFS_PREALLOC)
				ops = &sysfs_prealloc_kfops_wo;
			else
				ops = &sysfs_file_kfops_wo;
		} else
			ops = &sysfs_file_kfops_empty;

		size = PAGE_SIZE;
	} else {
		struct bin_attribute *battr = (void *)attr;

		if (battr->mmap)
			ops = &sysfs_bin_kfops_mmap;
		else if (battr->read && battr->write)
			ops = &sysfs_bin_kfops_rw;
		else if (battr->read)
			ops = &sysfs_bin_kfops_ro;
		else if (battr->write)
			ops = &sysfs_bin_kfops_wo;
		else
			ops = &sysfs_file_kfops_empty;

		size = battr->size;
	}

#ifdef CONFIG_DEBUG_LOCK_ALLOC
	if (!attr->ignore_lockdep)
		key = attr->key ?: (struct lock_class_key *)&attr->skey;
#endif
	kn = __kernfs_create_file(parent, attr->name, mode & 0777, size, ops,
				  (void *)attr, ns, key);
	if (IS_ERR(kn)) {
		if (PTR_ERR(kn) == -EEXIST)
			sysfs_warn_dup(parent, attr->name);
		return PTR_ERR(kn);
	}
	return 0;
}

attibute文件的read和write

在浏览/fs/sysfs/file.c下的代码时,发现基本所有的读写操作都带有"kf"标志,这里是通过sysfs_add_file_mode_ns() 函数进行一个file_oprations的链接。

/* /fs/sysfs/file.c  line 246 */
int sysfs_add_file_mode_ns(struct kernfs_node *parent,
			   const struct attribute *attr, bool is_bin,
			   umode_t mode, const void *ns)
{
	struct lock_class_key *key = NULL;
	const struct kernfs_ops *ops;
	struct kernfs_node *kn;
	loff_t size;

	if (!is_bin) { //是否为二进制文件
		struct kobject *kobj = parent->priv;
		const struct sysfs_ops *sysfs_ops = kobj->ktype->sysfs_ops;

		/* every kobject with an attribute needs a ktype assigned */
		if (WARN(!sysfs_ops, KERN_ERR
			 "missing sysfs attribute operations for kobject: %s\n",
			 kobject_name(kobj)))
			return -EINVAL;

		if (sysfs_ops->show && sysfs_ops->store) {
			if (mode & SYSFS_PREALLOC)
				ops = &sysfs_prealloc_kfops_rw;
			else
				ops = &sysfs_file_kfops_rw;
		} else if (sysfs_ops->show) {
			if (mode & SYSFS_PREALLOC)
				ops = &sysfs_prealloc_kfops_ro;
			else
				ops = &sysfs_file_kfops_ro;
		} else if (sysfs_ops->store) {
			if (mode & SYSFS_PREALLOC)
				ops = &sysfs_prealloc_kfops_wo;
			else
				ops = &sysfs_file_kfops_wo;
		} else
			ops = &sysfs_file_kfops_empty;

		size = PAGE_SIZE;
	} else {
		struct bin_attribute *battr = (void *)attr;

		if (battr->mmap)
			ops = &sysfs_bin_kfops_mmap;
		else if (battr->read && battr->write)
			ops = &sysfs_bin_kfops_rw;
		else if (battr->read)
			ops = &sysfs_bin_kfops_ro;
		else if (battr->write)
			ops = &sysfs_bin_kfops_wo;
		else
			ops = &sysfs_file_kfops_empty;

		size = battr->size;
	}

#ifdef CONFIG_DEBUG_LOCK_ALLOC
	if (!attr->ignore_lockdep)
		key = attr->key ?: (struct lock_class_key *)&attr->skey;
#endif
	kn = __kernfs_create_file(parent, attr->name, mode & 0777, size, ops,
				  (void *)attr, ns, key);
	if (IS_ERR(kn)) {
		if (PTR_ERR(kn) == -EEXIST)
			sysfs_warn_dup(parent, attr->name);
		return PTR_ERR(kn);
	}
    
	return 0;
}

/* /fs/sysfs/file.c  line 246 */
static const struct kernfs_ops sysfs_file_kfops_empty = {
};

static const struct kernfs_ops sysfs_file_kfops_ro = {
	.seq_show	= sysfs_kf_seq_show,
};

static const struct kernfs_ops sysfs_file_kfops_wo = {
	.write		= sysfs_kf_write,
};

static const struct kernfs_ops sysfs_file_kfops_rw = {
	.seq_show	= sysfs_kf_seq_show,
	.write		= sysfs_kf_write,
};

static const struct kernfs_ops sysfs_prealloc_kfops_ro = {
	.read		= sysfs_kf_read,
	.prealloc	= true,
};

static const struct kernfs_ops sysfs_prealloc_kfops_wo = {
	.write		= sysfs_kf_write,
	.prealloc	= true,
};

static const struct kernfs_ops sysfs_prealloc_kfops_rw = {
	.read		= sysfs_kf_read,
	.write		= sysfs_kf_write,
	.prealloc	= true,
};

static const struct kernfs_ops sysfs_bin_kfops_ro = {
	.read		= sysfs_kf_bin_read,
};

static const struct kernfs_ops sysfs_bin_kfops_wo = {
	.write		= sysfs_kf_bin_write,
};

static const struct kernfs_ops sysfs_bin_kfops_rw = {
	.read		= sysfs_kf_bin_read,
	.write		= sysfs_kf_bin_write,
};

static const struct kernfs_ops sysfs_bin_kfops_mmap = {
	.read		= sysfs_kf_bin_read,
	.write		= sysfs_kf_bin_write,
	.mmap		= sysfs_kf_bin_mmap,
};

这里bin_attribute里面的read、write、mmap就对应成了 sysfs_bin_kfops_mmap 中的sysfs_kf_bin_read、sysfs_kf_bin_write以及sysfs_kf_bin_mmap,具体函数实现不展开,在/fs/sysfs/file.c下可详细查看。

sysfs设备目录层次结构

/*************************************
 * 将设备添加到设备层次结构。  
 * 这通过kobject_add()将@dev添加到kobject层次结构中,将其添加到设备的全局和兄弟列表,
 * 然后将其添加到驱动程序模型的其他相关子系统。
 *************************************/

int device_add(struct device *dev)
{
    struct device *parent = NULL;
    struct kobject *kobj;
    struct class_interface *class_intf;
    int error = -EINVAL;

    dev = get_device(dev);
    if (!dev)
        goto done;

    if (!dev->p) {
        error = device_private_init(dev);
        if (error)
            goto done;
    }

    /*
     * for statically allocated devices, which should all be converted
     * some day, we need to initialize the name. We prevent reading back
     * the name, and force the use of dev_name()
     */
    if (dev->init_name) {
        dev_set_name(dev, "%s", dev->init_name);                   //有初始name就设置
        dev->init_name = NULL;
    }

    /* subsystems can specify simple device enumeration */
    if (!dev_name(dev) && dev->bus && dev->bus->dev_name)
        dev_set_name(dev, "%s%u", dev->bus->dev_name, dev->id);  //没有初始name就设置默认的

    if (!dev_name(dev)) {
        error = -EINVAL;
        goto name_error;
    }

    pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

    parent = get_device(dev->parent);
    kobj = get_device_parent(dev, parent);
    if (kobj)
        dev->kobj.parent = kobj;

    /* use parent numa_node */
    if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
        set_dev_node(dev, dev_to_node(parent));

    /* first, register with generic layer. */
    /* we require the name to be set before, and pass NULL */
    error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);// 在/sys/devices目录下创建当前设备目录
    if (error)
        goto Error;

    /* notify platform of device entry */
    if (platform_notify)
        platform_notify(dev);

    error = device_create_file(dev, &dev_attr_uevent);    //在当前设备目录下创建文件uevent
    if (error)
        goto attrError;

    error = device_add_class_symlinks(dev);               //创建链接文件
    if (error)
        goto SymlinkError;
    error = device_add_attrs(dev);                        //创建其他文件,如在class目录下
    if (error)
        goto AttrsError;
    error = bus_add_device(dev);                          //device加入到bus-device的链表中
    if (error)
        goto BusError;
    error = dpm_sysfs_add(dev);
    if (error)
        goto DPMError;
    device_pm_add(dev);

    if (MAJOR(dev->devt)) {
        error = device_create_file(dev, &dev_attr_dev);
        if (error)
            goto DevAttrError;

        error = device_create_sys_dev_entry(dev);
        if (error)
            goto SysEntryError;

        devtmpfs_create_node(dev);
    }

    /* Notify clients of device addition.  This call must come
     * after dpm_sysfs_add() and before kobject_uevent().
     */
    if (dev->bus)
        blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                         BUS_NOTIFY_ADD_DEVICE, dev);

    kobject_uevent(&dev->kobj, KOBJ_ADD);
    bus_probe_device(dev);           //__device_attach-------调用此函数来和driver进行匹配
    if (parent)                      //加入到父设备链表中
        klist_add_tail(&dev->p->knode_parent,
                   &parent->p->klist_children);

    if (dev->class) {                //和class相关的操作
        mutex_lock(&dev->class->p->mutex);
        /* tie the class to the device */
        klist_add_tail(&dev->knode_class,
                   &dev->class->p->klist_devices);

        /* notify any interfaces that the device is here */
        list_for_each_entry(class_intf,
                    &dev->class->p->interfaces, node)
            if (class_intf->add_dev)
                class_intf->add_dev(dev, class_intf);
        mutex_unlock(&dev->class->p->mutex);
    }
    ... //goto
}

设备模型框架下驱动开发的基本步骤

主要分为以下几个步骤:

  1. 分配一个 struct device 类型的变量,填充必要信息后,把它注册到内核中。
  2. 分配一个 struct device_driver类型的变量,填充必要的信息后,把它注册到内核中。
  3. 内核再合适的时机,调用truct device_driver变量中的probe、remove、suspend、resume等回调函数,从而触发或者终结设备驱动的执行。

注:

  1. 内核提供很多struct device结构的操作接口,主要包括初始化(device_initialize)、注册到内核(device_register)、分配存储空间+初始化+注册到内核(device_create)等等,可以根据需要使用。
  2. device和device_driver必须具备相同的名称,内核才能完成匹配操作,进而调用device_driver中的相应接口。这里的同名,作用范围是同一个bus下的所有device和device_driver。

设备驱动probe时机

所谓probe,是指在Linux内核中,如果存在相同名称的device和device_driver,内核就会执行device_driver的中的probe回调函数,而该函数就是所有driver的入口,可以执行诸如硬件设备初始化、字符设备注册、设备文件操作ops注册等动作("remove”是它的反操作,发生在device或者device_driver任何一方从内核注销时,其原理类似,就不再单独说明了)。

触发的时机为一下几种:

  • 将struct device类型的变量注册到内核中时自动触发(device_register,device_add,device_create_vargs,device_create)

  • 将struct device_driver类型的变量注册到内核中时自动触发(driver_register)

  • 手动查找同一bus下的所有device_driver,如果有和指定device同名的driver,执行probe操作(device_attach)

  • 手动查找同一bus下的所有device,如果有和指定driver同名的device,执行probe操作(driver_attach)

  • 自行调用driver的probe接口,并在该接口中将该driver绑定到某个device结构中----即设置dev->driver(device_bind_driver)

实际上,probe动作是由Bus模块实现的,因为device和driver都是挂在在Bus这根线上,只有Bus最清楚该为哪些device、哪些driver配对。每个bus都有一个drivers_autoprobe变量,用于控制是否在device或者driver注册时,自动probe。该变量默认为1(即自动probe),bus模块将它开放到sysfs中了,因而可在用户空间修改,进而控制probe行为。

即,probe的逻辑为:搜索所在的bus,比对是否有同名的device_driver(或device),如果有并且该设备没有绑定Driver(注:这一点很重要,通过它,可以使同一个Driver,驱动相同名称的多个设备,后续在Platform设备的描述中会提及)则调用device_driver的probe接口。

probe函数的调用

在Linux下,设备(或者驱动)执行probe有两个时机:

  1. 设备创建时;
  2. 驱动创建时;

而无论是通过platform_ device _register还是通过dts创建设备都是在调用 XX_register 函数时候调用probe,但这里有一个需要注意的地方:

任意一个注册时都会调用probe,但调用的前提是另一个已经存在


Device注册过程----device_register()

Device_driver_model 设备驱动模型_第2张图片


Driver注册过程----driver_register()

Device_driver_model 设备驱动模型_第3张图片


小结

从两个流程图上来看,无论是从device_register()开始,或者是从driver_register()开始,如果要成功probe,最终都需要通过 driver_match_device(drv, dev) ,然后才能进行really_probe()。

static int really_probe(struct device *dev, struct device_driver *drv)
{
...
//1.先是调用的驱动所属总线的probe函数:
if (dev->bus->probe) {
   ret = dev->bus->probe(dev);
   if (ret)
    goto probe_failed;

} else if (drv->probe) {
//2.再调用你的驱动中的probe函数:
   ret = drv->probe(dev);
   if (ret)
    goto probe_failed;
}
...
}

但在这里,有些总线上的probe回调函数是和device_driver中的非常类似,它们的存在是非常有意义的。可以想象一下,如果需要probe(其实就是初始化)指定的device话,需要保证该device所在的bus是被初始化过、确保能正确工作的。这就要就在执行device_driver的probe前,先执行它的bus的probe。

因此 drv->probe(dev),才是真正调用你的驱动实现的具体的probe函数。

附录

参考:

Linux 源码 4.18.5 https://elixir.bootlin.com/linux/latest/source

sysfs文件系统 https://www.ibm.com/developerworks/cn/linux/l-cn-sysfs/index.html

设备模型 http://www.wowotech.net/sort/device_model https://blog.csdn.net/cc289123557/article/category/6289555

设备Probe相关 http://www.cnblogs.com/hoys/archive/2011/04/01/2002299.html

Device_driver_model 设备驱动模型_第4张图片

你可能感兴趣的:(linux,linux内核)