简单理解起来,设备驱动模型主要作用有两点:
框架大概是这样的:
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/* |
从内核实现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操作函数
} ;
这三个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在设备模型中主要提供的功能为:
- 通过parent指针,可以将所有Kobject以层次结构的形式组合起来。
- 使用一个引用计数 kref,来记录Kobject被引用的次数,并在引用次数变为0时把它释放(这是Kobject诞生时的唯一功能)。
- 和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_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/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;
}
由前文可知,有一些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文件可以用任何方式读写。
/** /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;
}
在浏览/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下可详细查看。
/*************************************
* 将设备添加到设备层次结构。
* 这通过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
}
主要分为以下几个步骤:
注:
- 内核提供很多struct device结构的操作接口,主要包括初始化(device_initialize)、注册到内核(device_register)、分配存储空间+初始化+注册到内核(device_create)等等,可以根据需要使用。
- device和device_driver必须具备相同的名称,内核才能完成匹配操作,进而调用device_driver中的相应接口。这里的同名,作用范围是同一个bus下的所有device和device_driver。
所谓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接口。
在Linux下,设备(或者驱动)执行probe有两个时机:
而无论是通过platform_ device _register还是通过dts创建设备都是在调用 XX_register 函数时候调用probe,但这里有一个需要注意的地方:
任意一个注册时都会调用probe,但调用的前提是另一个已经存在
从两个流程图上来看,无论是从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