总线,设备,驱动与class
前面己经介绍了Linux设备驱动模型的底层数据结构及相关操作,现在开始讨论该模型的高层部分,也是Linux下设备驱动程序员与之打交道最多的部分。高层部分的核心分为三个组件,正如本节标题揭示的那样,分别是总线(bus)、设备〈device)和驱动(driver),它们构成了Linux设备驱动模型这一宏大建筑的外在表现。接下来将依次讨论每个组件,看看Linux引入的这个新的设备模型到底给系统,给设备驱动程序员带来了哪些好处和不足。
总线
总线及其注册
总线可以看成Linux设备驱动模型这座建筑的核心框架,系统中其他的设备与驱动将紧密团结在以总线为核心的设备模型的周围,完成各自的使命。不过设备驱动程序员在系统中创建一个新的总线的机会并不多。驱动模型中的总线,既可以是实际物理总线(比如PCI总线和I2C总线等)的抽象,也可以是出于驱动模型架构需要而产生的虚拟“平台”总线,因为一个符合Linux驱动模型的设备与驱动必须挂靠在一根总线上,无论它是实际存在的总线还是系统虚拟出的总线。
内核为总线对象定义的数据结构是bus_type,其完整定义如下:
//总线对象,描述一个总线,管理device和driver,完成匹配.总线数据结构类型的表示,成员:
struct bus_type {
const char *name;//总线的名称。如同每个人都有一个名字一样,总线也不例外。
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; //挂载到该总线上的设备的属性,功能逻辑与总线属性一样。
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);
//匹配总线中dev和driver,返回1 匹配成功,否则匹配失败
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
//用于总线对uevent的环境变量增加,但在总线下设备的dev_uevent处理函数也有对他的调用
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;
};
subsys_private 内核中定义为:
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;
};
其中,struct kset subsys用来表示该bus所在的子系统,在内核中所有通过busregister注册进系统的bus所在的kset都将指向bus kset,换句话说buskset是系统中所有bus内核对象的容器,而新注册的bus本身也是一个kset型对象。struct kset *driver_skset表示该bus上所有驱动的一个集合,struct kset *devices_kset则表不该bus上所有设备的一个集合。struct klist klist_devices和struct klist klist_drivers则分别表示该bus上所有设备与驱动的链表。drivers_autoprobe用来表示当向系统(确切地说是系统中某一总线)中注册某一设备或者驱动的时候,是否进行设备与驱动的绑定操作。struct bus_type *bus指向与struct bus_type_private对象相关联的bus。
为了便于接下来对总线、设备与驱动的讨论,我们从总线的角度,给出图所示三者之间的互联层次关系:
图9·3展示了一个总线对象所衍生出来的拓扑关系,这种拓扑关系主要通过bus_type中的struct *p成员来体现。在这个成员中,struct kset_subsys标识了系统中当前总线对象与bus kset间的隶属关系,而struct kset *drivers_kset和struct kset *devices_kset则是在向系统注册当前新总线时动态生成的容纳该总线上所有驱动与设备的kset,与此对应,两个klist成员则以链表的形式将该总线上所有的驱动与设备链接到了一起。
Linux内核中针对总线的一些主要操作有:
- buses_init
buses_init函数揭示了总线在系统中的起源,在系统的初始化阶段,就通过buses_init函数为系统中后续的bus操作奠定了基础,该函数的实现为:
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;
}
它将创建一个名称为“bus”的kset并将其加入到sysfs文件系统树中,注意这里的bus_uevent_ops定义了当“bus”这个kset中有状态变化时,用来通知用户空间uevent消息的操作集。前面在讨论kset时知道,当某个kset中有状态的变化时,如果需要向用户空间发送event消息,将由该kset的最顶层kset来执行,因为buskset是系统中所有bus subsystem最顶层的kset,所以bus中的uevent调用最终会汇集到这里的bus_uevent_ops中。这个操作集只定义了一个filter操作,意味着当“bus,中发生状态变化时,会通过bus_uevent_ops中的filter函数先行处理,以决定是否通知用户态空间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,
};
如果要求发送uevent消息的kobj对象类型不是总线类型(bus_type),那么函数将返回0,意味着uevent消息将不会发送到用户空间,所以bus_uevent_ops使得bus_kset只用来发送bus类型的内核对象产生的uevent消息。
buses_int将在sysfs文件系统的根目录下建立一个“bus”目录,在用户空间看来,就是/sys/bus。buses_init函数创建的“bus”总线将是系统中所有后续注册总线的祖先。
- bus_register
该函数用来向系统中注册一个bus,其部分核心代码如下:
//注册总线
/*在可以注册设备及驱动程序之前,需要有总线,提供此函数注册总线*/
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);
if (!priv)
return -ENOMEM;
priv->bus = bus;
bus->p = priv;
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
if (retval)
goto out;
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
retval = kset_register(&priv->subsys);//在/sys/bus目录下为当前注册的bus生成一个新的目录
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent);//生成bus的属性文件
if (retval)
goto bus_uevent_fail;
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);//为当前bus产生容纳设备的kset容器
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);//为当前bus产生容纳驱动的kset容器
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
INIT_LIST_HEAD(&priv->interfaces);
__mutex_init(&priv->mutex, "subsys mutex", key);
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);//初始化bus上的设备与驱动的链表
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);
if (retval)
goto bus_groups_fail;
pr_debug("bus: '%s': registered\n", bus->name);
return 0;
bus_groups_fail:
remove_probe_files(bus);
bus_probe_files_fail:
kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
kset_unregister(bus->p->devices_kset);
bus_devices_fail:
bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
kset_unregister(&bus->p->subsys);
out:
kfree(bus->p);
bus->p = NULL;
return retval;
}
函数首先分配一个struct subsys_private类型的对象,然后通过kobject_set_name为bus所在的内核对象设定名称,该名称将显示在sysfs文件系统树中。前面提到,bus作为一个kset类型的内核对象,其对象属性等特性体现在对象的subsys成员中,这是个kset型变量,所以注册一个bus,将同时赋予该bus特定的属性特质,这由下面两条语句完成:
priv->subsys.kobj.kset = bus_kset;
priv->subsys.kobj.ktype = &bus_ktype;
第一条语句指明了当前注册的bus对象所属的上层kset对象,就是buses_init中创建的名为“bus”的kset。第二条语句指明了当前注册的bus的属性类型bus_ktype,后者定义了该特定bus上的一些与总线属性文件相关的操作:
static struct kobj_type bus_ktype = {
.sysfs_ops = &bus_sysfs_ops,
.release = bus_release,
};
bus_sysfs_ops中的操作主要是用来显示(show)或者设置(store)当前注册的bus在sysfs文件系统中的属性。
函数中的kset_register(&priv->subsys)用来将当前操作的bus所对应的kset加入到sysfs文件系统树中,因为priv->subsys.kobJ.parent=NULL并且priv->subsys.kobJ.kset=buskset,所以当前注册的bus对应的kset的目录将建立在/syus当中。
bus_create_file(bus,&bus_attr_uevent)将为该bus创建一属性文件。关于bus的属性问题,稍后将另开一节予以讨论。
接下来可以看到有两个kset_create_and_add调用:
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);//为当前bus产生容纳设备的kset容器
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);//为当前bus产生容纳驱动的kset容器
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
前面讨论过kset_create_and_add函数,它将生成一个kset对象并将其加入到sysfs文件系统中。注意这里在调用kset_create_and_add函数时,parent参数均为&priv->subsys.kobJ,这意味着将在当前正在向系统注册的新bus目录下产生两个kset目录,分别对应新bus的devices和drivers,假设新bus的名称是“newbus”,那么反应到/sys文件目录中就是
/sys/bus/newbus/devices和/sys/bus/newbus/drivers。
图9-4反应了通过bus_register向系统注册一个新的busl时所产生的组件及层次关系:
图中虚线部分是将busl通过busregister注册进系统时所产生的层次关系结构。首先代表bus1的一个kset对象将被产生出来并且血入到sysfs文件系统中,该kset的parent内核对象为buses_init函数中所产生的bus_kset。其次bus_regster通过调用kset_create_and_add
函数产生连接到bus1上的devices_kset和drivers_kset两个集合,对应到sysfs文件系统,将会在bus1的目录下产生两个新的目录"devices”和"drivers”。最后为了让用户空间看到或者重新配置bus1上的某些属性值,bus_register调用bus_createf_ile函数为bus1产生一些属性文件,这些属性文件也将位于/syu1目录之下,属性文件实际上向用户空间提供了一种接口,使得用户程序可以通过文件的方式来显示某一内核对象的属性或者重新配置这一属性。
总线的属性
总线属性代表着该总线特有的信息与配置,如果通过sysfs文件系统为总线生成属性文件,那么用户空间的程序可以通过该文件接口的方式很容易地显示或者更改该总线的属性。根据实际需要,可以为总线创建不止一个属性文件,每个文件代表该总线的一个或一组属性信息。总线属性在内核中的数据结构为:
struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *bus, char *buf);
ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};
成员变量attr表示总线的属性信息,其类型为struct 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
};
struct bus_attribute的另外两个成员show与store分别用来显示和史改总线的属性。内核定义有一个宏BUSATTR,用来方便为总线定义一个属性对象:
#define BUS_ATTR(_name, _mode, _show, _store) \
struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define __ATTR(_name, _mode, _show, _store) { \
.attr = {.name = __stringify(_name), \
.mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \
.show = _show, \
.store = _store, \
}
BUS_ATTR宏将定义一个以“bus_attr_”开头的总线属性对象,而生成总线属性文件则需要使用bus_create_file函数:
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr)
{
int error;
if (bus_get(bus)) {
error = sysfs_create_file(&bus->p->subsys.kobj, &attr->attr);
bus_put(bus);
} else
error = -EINVAL;
return error;
}
sysfs_create_file用来在sysfs文件树中创建一个属性文件,这里不会讨论sysfs实现这个函数的细节。我们关注的是,用户层的应用程序如何利用总线属性文件的接口来显示和更改总线属性。这里以bus_register函数中的add_probe_files调用为例,后者会用BUS_ATTR宏定义一个总线属性,然后为之生成一个属性文件(读者可以在/sys/bus目录中的任一总线目录下发现drivers_autoprobe文件)。
通过BUSAI丁R宏,add-probefiles为此定义的总线属性为:
static BUS_ATTR(drivers_probe, S_IWUSR, NULL, store_drivers_probe);
static BUS_ATTR(drivers_autoprobe, S_IWUSR | S_IRUGO,
show_drivers_autoprobe, store_drivers_autoprobe);
上面的宏将产生一个总线属性对象bus_attr_drivers_autoprobe,该文件的模式为S_IWUSR |S_IRUGO,表明对root用户而言具有读与写的权限。
显示该总线属性的函数为show_drivers_autoprobe:
static ssize_t show_drivers_autoprobe(struct bus_type *bus, char *buf)
{
return sprintf(buf, "%d\n", bus->p->drivers_autoprobe);
}
static ssize_t store_drivers_autoprobe(struct bus_type *bus,
const char *buf, size_t count)
{
if (buf[0] == '0')
bus->p->drivers_autoprobe = 0;
else
bus->p->drivers_autoprobe = 1;
return count;
}
通过上面这个函数实现,可以发现该属性文件向用户空间提供了一个显示和更改bus->p->drivers_autoprobe成员的接口。
在构造sysfs文件系统的超级块时,内核会调用到kernfs_init_inode函数,这个函数为sysfs文件系统中的inode初始化了相关的操作对象i_op和i_fop,这样对于在sysfs文件系统中生成总线属性文件的bus_create_file而言,它生成的属性文件被用户空间的shell命令操作时,将利用到inode上i_fop操作集:
static void kernfs_init_inode(struct kernfs_node *kn, struct inode *inode)
{
kernfs_get(kn);
inode->i_private = kn;
inode->i_mapping->a_ops = &kernfs_aops;
inode->i_op = &kernfs_iops;
set_default_inode_attr(inode, kn->mode);
kernfs_refresh_inode(kn, inode);
/* initialize inode according to type */
switch (kernfs_type(kn)) {
case KERNFS_DIR:
inode->i_op = &kernfs_dir_iops;
inode->i_fop = &kernfs_dir_fops;
if (kn->flags & KERNFS_EMPTY_DIR)
make_empty_dir_inode(inode);
break;
case KERNFS_FILE:
inode->i_size = kn->attr.size;
inode->i_fop = &kernfs_file_fops;
break;
case KERNFS_LINK:
inode->i_op = &kernfs_symlink_iops;
break;
default:
BUG();
}
unlock_new_inode(inode);
}
kernfs_file_fops 定义为:
const struct file_operations kernfs_file_fops = {
.read = kernfs_fop_read,
.write = kernfs_fop_write,
.llseek = generic_file_llseek,
.mmap = kernfs_fop_mmap,
.open = kernfs_fop_open,
.release = kernfs_fop_release,
.poll = kernfs_fop_poll,
};
所以shell环境下的命令最终会调用到kernfs_fop_read函数,在后者调用的readbuffer
中,将调用到总线属性对象中的show函数:
ssize_t seq_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
struct seq_file *m = file->private_data;
size_t copied = 0;
loff_t pos;
size_t n;
void *p;
int err = 0;
mutex_lock(&m->lock);
/*
* seq_file->op->..m_start/m_stop/m_next may do special actions
* or optimisations based on the file->f_version, so we want to
* pass the file->f_version to those methods.
*
* seq_file->version is just copy of f_version, and seq_file
* methods can treat it simply as file version.
* It is copied in first and copied out after all operations.
* It is convenient to have it as part of structure to avoid the
* need of passing another argument to all the seq_file methods.
*/
m->version = file->f_version;
/* Don't assume *ppos is where we left it */
if (unlikely(*ppos != m->read_pos)) {
while ((err = traverse(m, *ppos)) == -EAGAIN)
;
if (err) {
/* With prejudice... */
m->read_pos = 0;
m->version = 0;
m->index = 0;
m->count = 0;
goto Done;
} else {
m->read_pos = *ppos;
}
}
/* grab buffer if we didn't have one */
if (!m->buf) {
m->buf = seq_buf_alloc(m->size = PAGE_SIZE);
if (!m->buf)
goto Enomem;
}
/* if not empty - flush it first */
if (m->count) {
n = min(m->count, size);
err = copy_to_user(buf, m->buf + m->from, n);
if (err)
goto Efault;
m->count -= n;
m->from += n;
size -= n;
buf += n;
copied += n;
if (!m->count)
m->index++;
if (!size)
goto Done;
}
/* we need at least one record in buffer */
pos = m->index;
p = m->op->start(m, &pos);
while (1) {
err = PTR_ERR(p);
if (!p || IS_ERR(p))
break;
err = m->op->show(m, p);
if (err < 0)
break;
if (unlikely(err))
m->count = 0;
if (unlikely(!m->count)) {
p = m->op->next(m, p, &pos);
m->index = pos;
continue;
}
if (m->count < m->size)
goto Fill;
m->op->stop(m, p);
kvfree(m->buf);
m->count = 0;
m->buf = seq_buf_alloc(m->size <<= 1);
if (!m->buf)
goto Enomem;
m->version = 0;
pos = m->index;
p = m->op->start(m, &pos);
}
m->op->stop(m, p);
m->count = 0;
goto Done;
Fill:
/* they want more? let's try to get some more */
while (m->count < size) {
size_t offs = m->count;
loff_t next = pos;
p = m->op->next(m, p, &next);
if (!p || IS_ERR(p)) {
err = PTR_ERR(p);
break;
}
err = m->op->show(m, p);
if (seq_has_overflowed(m) || err) {
m->count = offs;
if (likely(err <= 0))
break;
}
pos = next;
}
m->op->stop(m, p);
n = min(m->count, size);
err = copy_to_user(buf, m->buf, n);
if (err)
goto Efault;
copied += n;
m->count -= n;
if (m->count)
m->from = n;
else
pos++;
m->index = pos;
Done:
if (!copied)
copied = err;
else {
*ppos += copied;
m->read_pos += copied;
}
file->f_version = m->version;
mutex_unlock(&m->lock);
return copied;
Enomem:
err = -ENOMEM;
goto Done;
Efault:
err = -EFAULT;
goto Done;
}
设备与驱动的绑定
在继续讨论设备与驱动的话题之前,先来看看Linux设备驱动模型中的一个重要概念:设备与驱动的绑定(binding)。这里的绑定,简单地说就是将一个设备与能控制它的驱动程序结合到一起的行为。两个内核对象间的结合自然是靠各自背后的数据结构中的某些成员来完成。
总线在设备与驱动绑定的过程中发挥着核心作用:总线相关的代码屏蔽了大量底层琐碎的技术细节,为驱动程序员们提供了一组使用友好的外在接囗,从而简化了驱动程序的开发工作。在总线上发生的两类事件将导致设备与驱动绑定行为的发生:一是通过device_register函数向某一bus上注册一设备,这种情况下内核除了将该设备加入到bus上的设备链表的尾端,同时会试图将此设备与总线上的所有驱动对象进行绑定操作(当然,操作归操作,能否成功则是另外一回事头二是通过driver_register将某一驱动注册到其所属的bus上,内核此时除了将该驱动对象加入到bus的所有驱动对象构成的链表的尾部,也会试图将该驱动与其上的所有设备进行绑定操作。
下面从代码的角度看看设备与驱动的绑定到底意味着什么。当调用device_register向某一bus上注册一设备对象时,device_bind_driver函数会被调用来将该设备与它的驱动程序绑定起来:
int device_bind_driver(struct device *dev)
{
int ret;
ret = driver_sysfs_add(dev);
if (!ret)
driver_bound(dev);
return ret;
}
其中driver_sysfs_add用来在sysfs文件系统中建立绑定的设备与驱动程序之间的链接符号文件。而driver_bound函数中关于绑定的最核心的代码为:
klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);
用来将设备private结构中的knode_driver节点加入到与该设备绑定的驱动private结构中的klist_devices链表中。所以所谓设备与驱动的绑定,从代码的允度看,其实是在两者之间通过某种数据结构的使用建立了一种关联的渠道。
设备
设备在内核中的数据结构为struct device,该类型的实例是对具体设备的一个抽象:
//设备驱动模型中的device结构体
struct device {
struct device *parent;//指向设备的“父”设备,它所连接的设备
struct device_private *p;//用于保存设备驱动核心部分的私有数据
struct kobject kobj;//嵌入的struct kobject对象实例
const char *init_name; //设备对象的名称。在将该设备对象加入到系统中时,内核会把init_name设置成kobj成员的名称,后者在sysfs中表现为一个目录。
/* initial name of the device */
const struct device_type *type;//设备类型,哦那个与标识设备类型并携带特定类型的信息
struct mutex mutex; //用于同步的互斥锁
/* mutex to synchronize calls to
* its driver.
*/
struct bus_type *bus; //设备所在的总线对象指针。
/* type of bus device is on */
struct device_driver *driver;//用以表示当前设备是否已经与它的driver进行了绑定,如果该值为NULL,说明当前设备还没有找到它的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_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
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, creates the sysfs "dev" */
u32 id; /* device instance */
spinlock_t devres_lock;//保护资源的自旋锁
struct list_head devres_head;//设备资源的双向链表
struct klist_node knode_class;//接入class链表时所需要的klist节点
struct class *class;//指向设备所属的class指针
const struct attribute_group **groups;//设备属性集合 /* optional groups */
void (*release)(struct device *dev);//函数指针,当设备需要释放时调用此函数
struct iommu_group *iommu_group;
bool offline_disabled:1;
bool offline:1;
};
系统中的每个设备都是一个structdevice对象,内核为容纳所有这些设备定义了一个kset—devicesk_set,作为系统中所有struct device类型内核对象的容器。同时,内核将系统中的设备分为两大类:block和char。每类对应一个内核对象,分别为sysfs_dev_block_kobj和sysfs_devchar_kobj,自然地这些内核对象也在sysfs文件树中占有对应的入口点,block和char内核对象的上级内核对象为dev_kobj。设备相关的这些事儿发生得比较早,在Linux系统初始化期间由devices_init来完成,有关设备的故事就从那里开始:
int __init devices_init(void)
{
devices_kset = kset_create_and_add("devices", &device_uevent_ops, NULL);
if (!devices_kset)
return -ENOMEM;
dev_kobj = kobject_create_and_add("dev", NULL);
if (!dev_kobj)
goto dev_kobj_err;
sysfs_dev_block_kobj = kobject_create_and_add("block", dev_kobj);
if (!sysfs_dev_block_kobj)
goto block_kobj_err;
sysfs_dev_char_kobj = kobject_create_and_add("char", dev_kobj);
if (!sysfs_dev_char_kobj)
goto char_kobj_err;
return 0;
char_kobj_err:
kobject_put(sysfs_dev_block_kobj);
block_kobj_err:
kobject_put(dev_kobj);
dev_kobj_err:
kset_unregister(devices_kset);
return -ENOMEM;
}
这个函数的操作反映到/sys文件目录下,就是生成了/sys/devices、/sys/dev、/sys/dev/block和/sys/dev/char。
Linux内核中针对设备的主要操作有:
- device_initialize
用于设备的初始化,该函数的实现为:
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, -1);
}
这个函数主要用于初始化dev的一些成员,其中dev->kobj.kset=devicek_set表明了dev所属的kset对象为device_skset,device_pm_init用来初始化dev与电源管理相关的部分。
- device_register
用来向系统注册一个设备,在源码中的实现为:
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
所以device_register内部除了调用device_imtialize来初始化dev对象外,还会通过device_add的调用将设备对象dev加入到系统中。
device_add是个非常重要的函数,对于理解Linux设备驱动模型非常有帮助。该函数的原型为:
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);
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);
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)
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);
if (error)
goto Error;
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
error = device_create_file(dev, &dev_attr_uevent);
if (error)
goto attrError;
error = device_add_class_symlinks(dev);
if (error)
goto SymlinkError;
error = device_add_attrs(dev);
if (error)
goto AttrsError;
error = bus_add_device(dev);
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);
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);
if (dev->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);
}
done:
put_device(dev);
return error;
SysEntryError:
if (MAJOR(dev->devt))
device_remove_file(dev, &dev_attr_dev);
DevAttrError:
device_pm_remove(dev);
dpm_sysfs_remove(dev);
DPMError:
bus_remove_device(dev);
BusError:
device_remove_attrs(dev);
AttrsError:
device_remove_class_symlinks(dev);
SymlinkError:
device_remove_file(dev, &dev_attr_uevent);
attrError:
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
kobject_del(&dev->kobj);
Error:
cleanup_device_parent(dev);
put_device(parent);
name_error:
kfree(dev->p);
dev->p = NULL;
goto done;
}
我们把deviceadd函数中一些比较重要的功能分成下面几个部分来描述:
在sysfs文件系统中建立系统硬件拓扑关系结构图
建立代表dev的内核对象kobject的层次关系,简单地说就是为dev找到它的上级(parent)内核对象,这个层次关系决定了dev加入到系统后在sysfs文件树中的目录层次。代码中关于这种层次关系的建立虽然不是很难理解,但是比较烦琐,而且牵涉到dev->class成员,根据dev->class与dev->parent的值分成四种情况讨论:
-
dev->class和dev->parent都为空
由于在对dev对象调用device_initialize函数时,曾指定了dev所属的kset为devices_kset:dev->kobJ.kset=devices_kset,所以这种情况下在将dev->kobJ加入系统时,内核会将devices_kset所对应的kobJ为dev->kobj的parent,所以dev->kobJ.parent=devices_kset->kobJ。由于deviceskset是在devices_init中建立的设备的顶层kset,这种情况下dev对象将会在/sys/devices目录下产生一个新的目录/sys/devices/dev->init_name。 -
dev->class为空,dev->parent不为空
这种情况下对应dev对象的新目录将建立在dev->parent->kobJ对应的目录之下。 -
dev->class不为空,dev->parent为空
dev->class不为空意味着该dev属于某一class,对于这种情况系统将为dev->kobj.parent建立一个虚拟上层对象“virtual”如此,将对象加入系统将会在/sys/device/virtusl中产生一个新的目录/sys/devices/virtual/dev->initname。 -
dev->cls和dev->parent都不为空
这种情况下要看dev->parent->class是否为空,如果不为空,则dev的parent kobject为dev->parent->kobj,即父设备的内嵌kobject。
如果dev->parent->class为空,则内核需要在dev->class->p->class_dirs.list中寻找是否有满足条件的kobject对象k,使得k->parent=&parent->kobJ,如果找到那么dev->kobJ的parent kobj就是dev设备的父设备的内嵌kobject,否则需要重新生成一个kobJect对象作为dev->kobj的父kobJ
调用device_create_sys_dev_entry(dev)建立一个新的链接,该链接的目的和源取决于dev->class。链接源的产生:
static struct kobject *device_to_dev_kobj(struct device *dev)
{
struct kobject *kobj;
if (dev->class)
kobj = dev->class->dev_kobj;
else
kobj = sysfs_dev_char_kobj;
return kobj;
}
假设dev对象的设备号major=251,minor=0,设备名称为dev->initname,那么:
如果dev->class为空,则新链接为/sys/dev/char/251 0 /sys/devices/dev->initname;
如果dev->class不为空,那么链接文件的源头将在dev->cJass->devkobJ所对应的目录下产生,目的链接则为/sys/devices/vtrtual/dev->initname。
调用bus_add_device(dev),在/sys/bus/devices目录下创建一个链接文件,指向/sys/devices/dev->initname。
假设设备的名称dev->initname为“demodev”,主次设备号分别为251和0,dev->class为空,那么通过deviceadd向系统添加“demodev”设备后,sysfs文件树中反映的系统硬件拓扑结构如图9-5所示,图中阴影部分为device_add新增的目录和链接文件:
在sysfs文件树中创建与该dev对象对应的属性文件
uevent_attr是dev对象的一个属性,其定义如下:
一个体现设备驱动模型中总线、设备与驱动相互沟通的重要函数调用
bus_probe_device(dev),该函数的实现如下:
void bus_probe_device(struct device *dev)
{
struct bus_type *bus = dev->bus;
struct subsys_interface *sif;
int ret;
if (!bus)
return;
if (bus->p->drivers_autoprobe) {
ret = device_attach(dev);
WARN_ON(ret < 0);
}
mutex_lock(&bus->p->mutex);
list_for_each_entry(sif, &bus->p->interfaces, node)
if (sif->add_dev)
sif->add_dev(dev, sif);
mutex_unlock(&bus->p->mutex);
}
如果满足if语句中的条件,将会调用device_attach试图将当前的设备绑定到它的驱动程序上,device_attach进行绑定的核心代码如下:
int device_attach(struct device *dev)
{
int ret = 0;
device_lock(dev);
if (dev->driver) {
if (klist_node_attached(&dev->p->knode_driver)) {
ret = 1;
goto out_unlock;
}
ret = device_bind_driver(dev);
if (ret == 0)
ret = 1;
else {
dev->driver = NULL;
ret = 0;
}
} else {
ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
pm_request_idle(dev);
}
out_unlock:
device_unlock(dev);
return ret;
}
如果dev->driver不为空,表明当前的设备对象dev已经和它的驱动程序进行了绑定,这种情况下只需调用device_bind_driver(dev)在文件树中建立dev与其驱动程序之间的互联关系。
如果dev->driver为空,表明当前设备对象还没有和它的驱动程序绑定,此时需要遍历dev所在总线dev->bus上挂载的所有驱动程序对象:
bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
然后对遍历过程中的每个驱动程序对象drv,调用__device_attach(drv,dev)进行绑定:
static int __device_attach(struct device_driver *drv, void *data)
{
struct device *dev = data;
if (!driver_match_device(drv, dev))
return 0;
return driver_probe_device(drv, dev);
}
函数中的driver_match_device(drv,dev)用来判断drv与dev是否匹配,其内部的实现为:
static inline int driver_match_device(struct device_driver *drv,
struct device *dev)
{
return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
意味着如果当前drv对象所在的总线定义了match方法,那么就调用它来进行是否匹配的判断:如果总线没有定义match,那么driver_match_device(drv,dev)函数返回1,表明匹配成功,不成功返回0,device_attach函数继续对dev->bus上的下一个驱动程序对象进行匹配操作。
如果driver_match_device匹配成功,那么将调用driver_probe_device(drv,dev)将drv和dev进行绑定,这个工作实际上是由really_probe函数来完成的:
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
int local_trigger_count = atomic_read(&deferred_trigger_count);
atomic_inc(&probe_count);
pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
drv->bus->name, __func__, drv->name, dev_name(dev));
WARN_ON(!list_empty(&dev->devres_head));
dev->driver = drv;
/* If using pinctrl, bind pins now before probing */
ret = pinctrl_bind_pins(dev);
if (ret)
goto probe_failed;
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
if (dev->pm_domain && dev->pm_domain->activate) {
ret = dev->pm_domain->activate(dev);
if (ret)
goto probe_failed;
}
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
if (dev->pm_domain && dev->pm_domain->sync)
dev->pm_domain->sync(dev);
driver_bound(dev);
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
goto done;
probe_failed:
devres_release_all(dev);
driver_sysfs_remove(dev);
dev->driver = NULL;
dev_set_drvdata(dev, NULL);
if (dev->pm_domain && dev->pm_domain->dismiss)
dev->pm_domain->dismiss(dev);
switch (ret) {
case -EPROBE_DEFER:
/* Driver requested deferred probing */
dev_dbg(dev, "Driver %s requests probe deferral\n", drv->name);
driver_deferred_probe_add(dev);
/* Did a trigger occur while probing? Need to re-trigger if yes */
if (local_trigger_count != atomic_read(&deferred_trigger_count))
driver_deferred_probe_trigger();
break;
case -ENODEV:
case -ENXIO:
pr_debug("%s: probe of %s rejects match %d\n",
drv->name, dev_name(dev), ret);
break;
default:
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev_name(dev), ret);
}
/*
* Ignore errors returned by ->probe so that the next driver can try
* its luck.
*/
ret = 0;
done:
atomic_dec(&probe_count);
wake_up(&probe_waitqueue);
return ret;
}
函数首先将当前驱动程序对象drv赋值给dev->driver,然后,如果dev->bus->probe不为空,即dev所在的总线定义了probe方法,则调用之,否则如果drv对象定义了该方法,就调用drv->probe(dev),所以现在知道了我们driver中定义的probe函数什么时候会被调用到。这种设计机制给驱动程序提供了一个探测硬件的机会,即在其probe函数中作出判断:当前的设备是不是自己所支持的,以及当前设备是否处于工作状态等。驱动程序中实现的probe函数如果认为探测成功,那么应该返回0。
最后的driver_bound(dev)用来将驱动程序的一些数据信息加入到dev对象中。
- device_unregister
用来将一个设备从系统中注销掉,其实现为:
void device_unregister(struct device *dev)
{
pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
device_del(dev);
put_device(dev);
}
函数的重点在device_del中:
void device_del(struct device *dev)
{
struct device *parent = dev->parent;
struct class_interface *class_intf;
/* Notify clients of device removal. This call must come
* before dpm_sysfs_remove().
*/
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_DEL_DEVICE, dev);
dpm_sysfs_remove(dev);//设备电源管理函数,关闭本设备电源同时邇知其父设备(如果有的话)
if (parent)//有父设备,将当前设备从父设备所属链表中删除
klist_del(&dev->p->knode_parent);
if (MAJOR(dev->devt)) {//如果dev设备对象的主设备号不为0
devtmpfs_delete_node(dev);//动态删除设备节点文件
device_remove_sys_dev_entry(dev);
device_remove_file(dev, &dev_attr_dev);//删除设备的属性文件
}
if (dev->class) {
device_remove_class_symlinks(dev);
mutex_lock(&dev->class->p->mutex);
/* notify any interfaces that the device is now gone */
list_for_each_entry(class_intf,
&dev->class->p->interfaces, node)
if (class_intf->remove_dev)
class_intf->remove_dev(dev, class_intf);
/* remove the device from the class list */
klist_del(&dev->knode_class);
mutex_unlock(&dev->class->p->mutex);
}
device_remove_file(dev, &dev_attr_uevent);
device_remove_attrs(dev);
bus_remove_device(dev);
device_pm_remove(dev);
driver_deferred_probe_del(dev);
/* Notify the platform of the removal, in case they
* need to do anything...
*/
if (platform_notify_remove)
platform_notify_remove(dev);
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_REMOVED_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_REMOVE);
cleanup_device_parent(dev);
kobject_del(&dev->kobj);
put_device(parent);
}
驱动
内核为驱动对象定义的数据结构是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 */
const struct of_device_id *of_match_table;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
/*驱动程序定义探测函数。当在总线bus中将该驱动与对应的设备进行绑定时,
内核会首先调用bus中的probe函数(如果该bus实现了probe函数),如果bus
没有实现自己的probe函数,那么内核会调用驱动程序中实现的probe函数。*/
int (*remove) (struct device *dev);
/*驱动程序所定义的卸载函数。当调用driverunregister从系统中卸载一个驱
动对象时,内核会首先调用bus中的remove函数(如果该bus实现了remove函数),
如果bus没有实现自己的remove函数,那么内核会调用驱动程序中实现的remove
函数。*/
//下面三个用于电源管理
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;
};
驱动上的主要操作有:
- driver_find
在一个bus的drivers_kset集合中查找指定的驱动,函数原型为:
struct device_driver *driver_find(const char *name, struct bus_type *bus)
{
struct kobject *k = kset_find_obj(bus->p->drivers_kset, name);
struct driver_private *priv;
if (k) {
/* Drop reference added by kset_find_obj() */
kobject_put(k);
priv = to_driver(k);
return priv->driver;
}
return NULL;
}
参数name是要查找的驱动的名称,参数bus指明在哪个总线上进行当前的查找。如果查找成功,将返回该驱动对象的指针,否则返回0。
- driver_register
该函数用来向系统注册一个驱动,其核心实现代码为:
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;
BUG_ON(!drv->bus->p);
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
printk(KERN_WARNING "Driver '%s' needs updating - please use "
"bus_type methods\n", drv->name);
other = driver_find(drv->name, drv->bus);
if (other) {
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv);
if (ret)
return ret;
ret = driver_add_groups(drv, drv->groups);
if (ret) {
bus_remove_driver(drv);
return ret;
}
kobject_uevent(&drv->p->kobj, KOBJ_ADD);
return ret;
}
函数首先调用driver_find在drv->bus上查找当前要注册的drv,这主要是防止向系统重复注册同一个驱动,如果当前要注册的驱动没有被注册过,那么将调用bus_add_driver(drv)进行实际的注册操作。
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv;
int error = 0;
bus = bus_get(drv->bus);
if (!bus)
return -EINVAL;
pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
error = -ENOMEM;
goto out_put_bus;
}
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;
priv->kobj.kset = bus->p->drivers_kset;
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);
if (error)
goto out_unregister;
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);
if (error)
goto out_unregister;
}
module_add_driver(drv->owner, drv);
error = driver_create_file(drv, &driver_attr_uevent);
if (error) {
printk(KERN_ERR "%s: uevent attr (%s) failed\n",
__func__, drv->name);
}
error = driver_add_groups(drv, bus->drv_groups);
if (error) {
/* How the hell do we get out of this pickle? Give up */
printk(KERN_ERR "%s: driver_create_groups(%s) failed\n",
__func__, drv->name);
}
if (!drv->suppress_bind_attrs) {
error = add_bind_files(drv);
if (error) {
/* Ditto */
printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
__func__, drv->name);
}
}
return 0;
out_unregister:
kobject_put(&priv->kobj);
kfree(drv->p);
drv->p = NULL;
out_put_bus:
bus_put(bus);
return error;
}
函数首先为drv分配了一块类型为struct driver_private的空间对象p前,然后将其与drv对象建立了关联,同时调用kobject_init_and_add把drv所对应的内核对象加入到sysfs文件树中,如此将在/sys/bus/drivers目录下新建一目录,其名称为drv->name。
如果drv所在的bus对应的dnvers_autoprobe属性值为1,将调用dnver_attach将当前注册的drv与该bus上所属的设备进行绑定。绑定的过程将遍历bus上的所有设备,对于其中的每个设备dev,将调用really_probe(dev,rv)进行实际的绑定操作。如果此前bus上定义了match方法,则它将被首先调用以确定drv与dev是否match,如果不match,那么将继续遍历下一个设备,否则调用really_probe进行实际的绑定操作。really_probe(dev,drv)函数如能将drv与dev成功绑定,则将在sysfs文件树中通过链接文件为dev和drv所对应的内核对象建立拓扑关系。同时,如果所在bus上定义有probe函数,将调用之,否则如果当前要注册的drv定义有probe函数,那么将调用之。
在bus_add-driver函数中,也会通过调用driver_create_file函数在新建的drv目录中生成属性文件,比如driver_create_file(drv,&driver_attru_event)等。驱动的属性由宏DRIVER_ATTR来定义:
#define DRIVER_ATTR(_name, _mode, _show, _store) \
struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store)
- driver_unregister
该函数用来将某一指定的驱动从系统中注销掉。函数原型为:
void driver_unregister(struct device_driver *drv)
{
if (!drv || !drv->p) {
WARN(1, "Unexpected driver unregister!\n");
return;
}
driver_remove_groups(drv, drv->groups);
bus_remove_driver(drv);
}
参数drv用于指定要注销的某一驱动对象。函数基本上是做的反向工作,其主要的工作是在bus_remove_driver(drv)函数中完成的。需要注意的是,在注销一个驱动对象的过程中,如果其所在的总线定义了remove方法,那么内核会调用它,否则要看驱动所在的驱动程序中有没有实现该方法,如果实现了的话内核会调用该函数。
class
Linux设备驱动模型中的另一个比较重要的概念是类class,相对于设备device,class是一种更高层次的抽象,用于对设备进行功能上的划分,有时候也被称为设备类。Linux的设备模型引入类,是将其用来作为具有同类型功能设备的一个容器。
struct class {
const char *name;//类的名称。
struct module *owner;//拥有该类的模块的指针。
struct class_attribute *class_attrs;//类的属性。
const struct attribute_group **dev_groups;//设备的属性。
struct kobject *dev_kobj;//代表当前类中设备的内核对象。
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, umode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;//类的私有数据区,用于处理类的子系统及其所包含的设备链表。
};
内核针对类对象定义的主要操作有:
- classes_init
系统中类的起源函数,在系统初始化期间调用,主要作用是产生类对象的顶层kset—-—class_kset:
int __init classes_init(void)
{
class_kset = kset_create_and_add("class", NULL, NULL);
if (!class_kset)
return -ENOMEM;
return 0;
}
函数中对kset_create_and_add(“class”,NULL,NULL)的调用将导致在s目录下新生成一个"class”目录(/sy/class),在以后的class相关的操作中,class_kset将作为系统中所有class内核对象的顶层kset
- __class_create
其用途主要是将同类型的设备添加其中。
struct class *__class_create(struct module *owner, const char *name,
struct lock_class_key *key)
{
struct class *cls;
int retval;
cls = kzalloc(sizeof(*cls), GFP_KERNEL);
if (!cls) {
retval = -ENOMEM;
goto error;
}
cls->name = name;
cls->owner = owner;
cls->class_release = class_create_release;
retval = __class_register(cls, key);
if (retval)
goto error;
return cls;
error:
kfree(cls);
return ERR_PTR(retval);
}
函数会动态生成一个class对象,经过一些初步的初始化之后,调用class_register向系统注册该新生成的类对象。限于篇幅,此处将不再列出class_regster函数的源代码,而是直接介绍其主要功能。class_register会首先为class_create函数中生成的新的类对象分配私有数据空间(structcs*cp=kzalloc(sizeof(*cp),GFPKERNEL)),代表class内核对象的kobject内嵌在struct class_private数据结构的class_subsys成员中(class_subsys.kobJ)。
函数将类对象的name成员赋值给代表类的kobJect对象名称(kobject_set_name(&cp->class_subsys.kobJ,“%s”,cls->name)),同时为类的kobJ指定kset和ktype:
int __class_register(struct class *cls, struct lock_class_key *key)
{
struct subsys_private *cp;
int error;
pr_debug("device class '%s': registering\n", cls->name);
cp = kzalloc(sizeof(*cp), GFP_KERNEL);
if (!cp)
return -ENOMEM;
klist_init(&cp->klist_devices, klist_class_dev_get, klist_class_dev_put);
INIT_LIST_HEAD(&cp->interfaces);
kset_init(&cp->glue_dirs);
__mutex_init(&cp->mutex, "subsys mutex", key);
error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
if (error) {
kfree(cp);
return error;
}
/* set the default /sys/dev directory for devices of this class */
if (!cls->dev_kobj)
cls->dev_kobj = sysfs_dev_char_kobj;
#if defined(CONFIG_BLOCK)
/* let the block class directory show up in the root of sysfs */
if (!sysfs_deprecated || cls != &block_class)
cp->subsys.kobj.kset = class_kset;
#else
cp->subsys.kobj.kset = class_kset;
#endif
cp->subsys.kobj.ktype = &class_ktype;
cp->class = cls;
cls->p = cp;
error = kset_register(&cp->subsys);
if (error) {
kfree(cp);
return error;
}
error = add_class_attrs(class_get(cls));
class_put(cls);
return error;
}
之前讨论过class——kset为系统中所有class对象的顶层kset,此处将当前class对象的kobj.kset指向class_kset,意味着通过class_create生成的class,在sysfs文件系统中的入口点(目录)将在/sys/class目录下产生。
函数接下来调用kset_register将之前产生的class加入到系统中:
kset_regtster(&cp->classsubsys);
这样将会在/sys目录下生成一个新的目录。
- device_create
本来这个函数应该是属于设备相关的操作范畴,但是因为class的引入,使得设备的创建与class产生了相关性,所以我们把这个创建设备的函数放到class一节中讲解。
int device_create(struct class *class, struct device *parent, dev_t devt, const char *fmt, ...)
{
va_list vargs;
struct class_device *class_dev;
int err;
va_start(vargs, fmt);
vsnprintf(devfs_name, sizeof(devfs_name), fmt, vargs);
va_end(vargs);
class_dev = class_device_create(class, devt, parent, "%s", devfs_name);
if (IS_ERR(class_dev)) {
err = PTR_ERR(class_dev);
goto out;
}
err = devfs_mk_cdev(devt, S_IFCHR|S_IRUSR|S_IWUSR|S_IRGRP, devfs_name);
if (err) {
class_device_destroy(class, devt);
goto out;
}
return 0;
out:
return err;
}
函数的核心在device_create_vargs调用中,其功能基本和device_regster相同,但是
device_create_vargs会为设备指定class:
dev->class=class
之前在讨论device_register函数时曾讨论过“在sysfs文件系统中建立系统硬件拓扑关系结构图”的四种情况,如果用devicec爬№向系统中增加设备,显然属于这四种情况中的后两种。
- device_destroy
函数原型为:
void device_destroy(struct class *class, dev_t devt)
{
class_device_destroy(class, devt);
devfs_remove(devfs_name);
}
该函数用于从系统中移除通过device_create增加的设备device。
总结
这三章第二十五章 linux-总线,设备,驱动与class
第二十四章 linux-kset与热插拔中的uevent和call_usermodehelper
第二十三章 linux-sysfs文件系统与kobject讨论了Linux的设备驱动模型,该模型是个非常复杂的系统,从一个比较高的层次来看,主要由总线、设备和驱动构成。内核为了实现这些组件间的相关关系,定义了kobject和kset这样的基础底层数据结构,然后通过sysfs文件系统向用户空间展示发生在内核空间
中的各组件间的互联层次关系,并以文件系统接口的方式为用户空间程序提供了访问内核对象属性信息的简易方法。Linux设备模型通过总线将系统中的设备和驱动关联起来,由于设备和驱动的分离,增加了系统设计的灵活性,伴随而来的代价就是增加了复杂度。