The Linux Device Model [LDD3 14]

16 篇文章 0 订阅
14 篇文章 1 订阅

kernel中为了更好的支持更多更复杂的device拓扑结构,引入了设备模型的概念。kernel中的设备模型分为如下几种:

Power management and system shutdown

要求对系统结构有较深入的理解,比如USB adaptor在关闭之前,必须处理完和它相连的USB device。所以电源管理和关机系统要正确处理类似这样的设备管理顺序。

Communications with user space

系统的虚拟文件系统和device module联系非常紧密,通过虚拟文件系统,user space可以控制kernel中device的行为。

Hotpluggable devices

系统中有越来越多的hotplug device,kernel需要和user space一起处理hotplug事件,而user space就需要知道kernel的hotplug device module。

Device classes

和设备如何相连相比,系统更经常关心哪些设备可用,使用class来管理device,可以向user space提供统一的访问device的方式。

Object lifecycles

上面列出来的各个设备模型,都需要管理生命周期,处理他们之间的关系等,通过object 设备模型就可以做到。

设备模型是一个非常复杂的数据结构,以一个鼠标为例,看一下它的简化的设备模型:

这是一个简化的模型,设备模型本身比较复杂,kernel内部已经自行做了处理,对于driver来说,大多数情况下不会直接和这些设备模型交互,比如bus,class等,因为这些kernel自身或者它的subsystem已经帮driver处理掉了。

即便如此,了解了kernel的设备模型,对于driver developer来说有很大的好处,而且,在某些情况下,driver需要直接和某些设备模型交互,使用他们提供的部分功能。

14.1. Kobjects, Ksets, and Subsystems


在kernel中,kobj是一个非常基础的东西,它可以把很多的device module组合在一起。kobj最开始只是一个reference counter,后来它的任务开始扩展,现在主要由下面几个部分组成:

Reference counting of objects

kernel中创建的很多东西都有生命周期,何时创建,何时释放,都要track。所以引入了ref counter,当ref counter变成了0,就表示没有人再使用它,就可以被释放了。

Sysfs representation

sysfs中的任何一个object,在kernel内部都有一个kobject对应,负责和kernel交互,并向sysfs提供功能。

Data structure glue

kenrel中有各种各样的结构体,kobject起到glue的作用,将他们链接在一起。

Hotplug event handling

kobject可以根据device的添加和删除产生各种各样的event,这些event用来通知user space,device发生了什么事。

14.1.1. Kobject Basics

<linux/kobject.h>
struct kobject;

14.1.1.1 Embedding kobjects

kobject极少单独使用,一般都是内嵌到别的结构体中使用,这个和stuct list_head用法是一样的,kobject的存在就是方便管理其他的数据结构。以cdev为例:

struct cdev {
         struct kobject kobj;
         struct module *owner;
         struct file_operations *ops;
         struct list_head list;
         dev_t dev;
         unsigned int count;
};

一个cdev对应一个kobject,通过kobject来管理cdev。如果你有一个kobject的指针,并且确定它是来自于cdev,那么可以通过container_of拿到cdev的指针:

struct cdev *device = container_of(kp, struct cdev, kobj);

14.1.1.2 Kobject initialization

因为kobject内嵌到别的数据结构,当这个大的数据结构初始化的时候,一般会把kobject全部清零,这个是必须做的,然后再调用kobj的函数:

//初始化kobject结构体,并设置必要的member,比如ref counter
void kobject_init(struct kobject *kobj);
//设置kobject的名字,sysfs中或许会用到
int kobject_set_name(struct kobject *kobj, const char *format, ...);

kobject_init会把这个kobject的ref counter设置为1,还要调用kobject_set_name给这个kobject设置名字。其他需要设置的member,等到后面我们再看。

14.1.1.3 Reference count manipulation

kobject非常重要的一个功能就是它的ref counter,也就是内嵌kobject的object的reference counter,只要这个counter不是0,就代表这个object仍然存在。操作reference counter的函数有:

//增加kobject的ref counter,并返回kobj的指针。如果kobj正在被destroy,操作失败,并返回NULL。
struct kobject *kobject_get(struct kobject *kobj);
//减少kobject的ref counter,如果没有人引用了,就会free。
void kobject_put(struct kobject *kobj);

要注意的是,kobject在init的时候初始值ref counter就是1,所以最后要额外调用kobject_put来释放kobject。

这里有一个例子:

struct kobject *cdev_get(struct cdev *p)
{
    struct module *owner = p->owner;
    struct kobject *kobj;

    if (owner && !try_module_get(owner))
        return NULL;
    kobj = kobject_get(&p->kobj);
    if (!kobj)
        module_put(owner);
    return kobj;
}

因为kobject要求创建kobject的那个module也存在,所以这个例子中对module是否存在做了check。当module已经不存在时,就返回NULL,如果kobject_get失败了,在返回之前,要把module的reference counter再减回去。

14.1.1.4 Release functions and kobject types

当kobject的reference counter变为0,这个kobject就会被free。kobject的释放是通过自己定义的release函数来实现的:

void my_object_release(struct kobject *kobj){
    struct my_object *mine = container_of(kobj, struct my_object, kobj);
    /* Perform any additional cleanup on this object, then... */
    kfree(mine);
}

所有的kobject必须有一个release函数,并且保证在release函数调用前,kobject一定存在。有意思的是,kobject的release函数记在kobj_type(简称ktype)里,而不是kobject里面:

struct kobj_type {
    void (*release)(struct kobject *);
    struct sysfs_ops *sysfs_ops;
    struct attribute **default_attrs;
};

每一个kobject都会对应一个ktype,但是这个ktype指针在两个地方会看到:第一个地方是kobject本身有一个ktype指针;第二个地方是在kset,如果kobject是位于kset中的kobject,那么就使用kset里的ktype。通过kernel提供的接口可以通过kobj找到它的kobj_type:

struct kobj_type *get_ktype(struct kobject *kobj);

14.1.2. Kobject Hierarchies, Ksets, and Subsystems

kernel中的很多数据结构最后会组成hierarchical结构,可以通过parent和kset两种方式来实现。比如一个USB的kobject,它的parent kobject可能就是USB hub,通过一层层的parent,各个数据结构就组成了层次结构,parent这种形式多用于sysfs中。

14.1.2.1 Ksets

kset从很多方面看,都像是kobj_type的扩展版本,kset中有一个kobject的集合,这里面的每一个kobject外面的结构体都是同样的类型。唯一的区别在于kobj type的操作对象是一个kobject,而kset是很多kobject的集合,或者说kobj_type更侧重于某一个kobject,而kset侧重于对同一类型的kobject的管理或者维护,可以看做是top-level的container class。

实际上,kset里面也有一个kobject,所以kset本身也是通过kobject来管理的。和kobject的区别在于,kset在sysfs中一定是有文件存在的,一旦kset被创建并被添加到系统中,sysfs中就会有对应的文件节点;kobject不一定,除非被添加进kset,否则不会有sysfs文件节点。kernel提供了函数,用于把kobject添加到kset之中:

//将kobject加到对应的kset,kset已经记录在kobj->kset里。函数有可能返回fail,需要check返回值。
int kobject_add(struct kobject *kobj);
//kobj_init + kobj_add
extern int kobject_register(struct kobject *kobj);
//从kset里减一个ref counter
void kobject_del(struct kobject *kobj);

在通过kobject_add把kobject添加到kset之前,需要设置kobject里的kset成员。而kobject_register把kobject_init和kobject_add做了封装,driver使用起来更加方便。kobject_add会把kobject的ref counter加上,如果kobject从kset里移除,需要del来减掉。调用了kobject_register的需要调用kobject_unregister。kobject和kset的关系如下所示:

kset中的每一个kobject都是被内嵌在别的结构体之中,这个结构体可以是任意的结构体,也可能是某一个kset。kobject->parent一般情况下不是它所在的kset。

14.1.2.2 Operations on ksets

关于kset的操作:

//这四个function直接会调用kobject_xxx,object就是kset中内嵌的kobject。
void kset_init(struct kset *kset);
int kset_add(struct kset *kset);
int kset_register(struct kset *kset);
void kset_unregister(struct kset *kset);

//管理kset的reference counter
struct kset *kset_get(struct kset *kset);
void kset_put(struct kset *kset);

//kset也可以设置name
kobject_set_name(&my_set->kobj, "The name");

kset中有一个kobj_type(成员名字是ktype),来表述kset中的kobject,在需要的时候,会优先使用kset中的ktype,而不是kset里面各个kobject中的ktype。因此,很多情况下,kobject中的kobj_type是NULL,因为真正使用的不是它。另外,kset中还有一个指针subsys,这里要讲一下。

14.1.2.3 Subsystems

这个subsystem是指的kernel中某一类子系统,比如block_subsys/device_subsys等,分别对应block的subsystem和device subsystm。kenrel中每一个具体的bus,都有一个对应的sub system,而每一个sub system都有一个结构体与之对应:

struct subsystem {
    struct kset kset;
    struct rw_semaphore rwsem;
};

可以看到,subsystem里只是包含一个kset,以及一个semaphore。semaphore是为了保证对kset中list的访问是串行的。

每一个kset必须属于某一个sub system,各个sub system的层次关系决定了kset在其中的位置,kset对应的sub system就存储在kset的subsys成员里,你可以通过kset找到它所从属的sub system,但是不能从sub system找到它对应的kset。

声明一个subsystem:

decl_subsys(name, struct kobj_type *type, struct kset_hotplug_ops *hotplug_ops);

kernel通过这个宏就会生成一个新的subsystem:name_subsys,并且使用传进来的type,和hotplug_ops来初始化结构体subsystem中的kset。subsystem支持的函数:

void subsystem_init(struct subsystem *subsys);
int subsystem_register(struct subsystem *subsys);
void subsystem_unregister(struct subsystem *subsys);
struct subsystem *subsys_get(struct subsystem *subsys)
void subsys_put(struct subsystem *subsys);

14.2. Low-Level Sysfs Operations


sysfs中每一个目录,都会有一个kobject和它对应,目录下的每个文件节点,都对应kobject的一个属性。那么kobject和sysfs是怎么在kernel底层交互的呢?这就是这一节要讲的内容。

简单的说,调用kobject_add就可以了,但是里面还有一些细节:

1, 创建的kobject总是对应一个目录,它的attribute才是目录下的文件;

2, kobject 的名字就是文件夹的名字,要求同一个sysfs下名字唯一,命名要规范;

3,kobject的目录位于它的parent下,如果parent为NULL,就是它的kset里的kobject下,如果kset也为NULL,就位于top dir了。

如果不考虑attribute,只是调用kobject_add,我们就可以创建一个空的文件夹。但是空目录没有用啊,还是得用attribute,下面看一下kobject的attribute。

14.2.1. Default Attributes

每个kobject被创建出来的时候,kernel都会自动给它创建几个default attribute,这些attribute都是在struct kobj_type里指定:

struct kobj_type {
    void (*release)(struct kobject *);
    struct sysfs_ops *sysfs_ops;
    struct attribute **default_attrs;
};

default_attrs就是kobject type支持的所有default attributes,sysfs_ops里则是对这些attrubutes的实现。看一下attribute这个结构体:

struct attribute {
    char *name;            //attribute name,就是在sysfs中显示的文件名
    struct module *owner;  //指向负责实现这个attribute的module
    mode_t mode;           //文件的访问权限
};

其中的mode,可以是S_IRUGO表示只读,也可以是S_IWUSR表示只有root用户可以写。kobj_type中的default_attrs就是指向attribute的指针数组,最后一个attribute必须是NULL。default_attrs只能说明有哪些attribute,但是并不表示如何实现,实现attributes的任务归sysfs_ops管。看一下sysfs_ops:

struct sysfs_ops {
    ssize_t (*show)(struct kobject *kobj, struct attribute *attr, char *buffer);
    ssize_t (*store)(struct kobject *kobj, struct attribute *attr, const char *buffer, size_t size);
};

当某一个attrubute被读时,show这个callback就会被调用,传递进来的参数中,kobject就是当前文件夹代表的kobject,attr就是文件节点对应的attribute,buffer就是show这个函数需要写进去的buffer,在写buffer的时候,要注意buffer的size是一个PAGE_SIZE,不要写越界,如果有很多的信息需要返回,就分成多个attribute来实现。这个kobject的所有attribute的读都会调用这个show,为了方便的区分是哪个attribute,通用的做法是把attribute内嵌到driver自定义的attribute结构体,然后在show里通过container_of拿到这个自定义的结构体。

当某个attribute被写时,store这个callback就会被调用,传递进来的参数中,buffer是写给这个attribute的内容,size是实际的buffer size,不会超过一个PAGE_SIZE。处理完以后,返回值是成功处理的size。如果store执行的过程中发现任何问题,就返回error code。

14.2.2. Nondefault Attributes

当然,kobject也支持driver自己添加attribute,通过下面的接口来实现:

int sysfs_create_file(struct kobject *kobj, struct attribute *attr);

attr就是driver自己定义好的attribute,如果创建成功,会返回0,否则返回负值。在添加新的attribute的时候,要注意,对新的attribute的读写也是调用同样的callback,也就是show和store,因此在show和store中注意添加对新的attribute的支持。如果需要remove attribute:

int sysfs_remove_file(struct kobject *kobj, struct attribute *attr);

sysfs_remove_file调用了以后,sysfs中的文件节点就会被删除,但是user space之前open file拿到的fd仍然存在,因此有可能在attribute被删除以后,仍然有来自user space的show或者store调用。

14.2.3. Binary Attributes

kernel中的绝大部分文件节点,也就是attribute,都只支持单个value的写,很少碰到需要user space写很多东西给kernel,除非是user space通过sysfs upload firmware,kernel也支持添加binary的attribute,对应的数据结构就是bin_attribute:

struct bin_attribute {
    struct attribute attr;
    size_t size;
    ssize_t (*read)(struct kobject *kobj, char *buffer, loff_t pos, size_t size);
    ssize_t (*write)(struct kobject *kobj, char *buffer, loff_t pos, size_t size);
};

//创建binary attribute
int sysfs_create_bin_file(struct kobject *kobj, struct bin_attribute *attr);
//删除binary attribute
int sysfs_remove_bin_file(struct kobject *kobj,struct bin_attribute *attr);

bin_attribute里,attr就是普通的attribute,需要填写name,owner,以及permission;size就是binary attribute的最大size,0表示没有限制;read和write和普通的字符型设备的读写函数类似,如果data过多,可能会分好多次读写,每次一个PAGE_SIZE。要注意的是,sysfs不知道写操作什么时候结束,需要driver自己想办法解决。binary的attribute不能是default attribute,只能driver手动创建,使用sysfs_create_bin_file,要删除的话调用sysfs_remove_bin_file。

14.2.4. Symbolic Links

kernel中也可以创建symbol link,也就是软连接,通过这种方式可以把driver和device关联起来,这样从sysfs就能清楚的看到组织架构:

//创建symbol link
int sysfs_create_link(struct kobject *kobj, struct kobject *target, char *name);
//销毁symbol link
void sysfs_remove_link(struct kobject *kobj, char *name);

sysfs_create_link创建一个软连接,名字是name,这个软连接属于kobj的一个attribute,并指向target这个kobject。这里创建的是相对路径的链接,因此即便sysfs被挂载在别的位置,也不会破坏链接关系。注意,symbol link不会随着它指向的kobject的销毁而销毁,所以需要driver自己解决这个问题。销毁symbol link需要调用sysfs_remove_link这个接口。

14.3. Hotplug Event Generation


当kernel的某些配置发生变化时,会产生hotplug event通知user space。当kobject创建或者销毁时,也会产生hotplug event。比如数码相机插入电脑,或者切换console mode,或者disk被挂载等等。kernel发出的hotplug event,最终会调用/sbin/hotplug这个程序,这个程序根据event的类型做相应的处理,比如load driver等等。

kobject产生hotplug event的具体时间点是kobj_add和kobj_del被调用时。在把event传递给user space之前,kobject对应的kset有机会先处理这些event,甚至可以disable这个event而不让user space知道。

14.3.1. Hotplug Operations

kset处理这些event,是通过kset里的hotplug_ops来做的,这个hotplug_ops指向一个struct kset_hotplug_ops:

struct kset_hotplug_ops {
    int (*filter)(struct kset *kset, struct kobject *kobj);
    char *(*name)(struct kset *kset, struct kobject *kobj);
    int (*hotplug)(struct kset *kset, struct kobject *kobj, char **envp, int num_envp, char *buffer,int buffer_size);
};

如果这个kobject没有被包含在任何的kset里,kernel就会通过parent一直向上找,直到找到一个kobject里包含了kset,然后调用它的hotplug ops。filter这个callback是kernel最先调用的callback,如果返回了0,表明kernel不需要告诉user space这个event。比如这里有个例子:

static int block_hotplug_filter(struct kset *kset, struct kobject *kobj) {
    struct kobj_type *ktype = get_ktype(kobj);
    return ((ktype == &ktype_block) || (ktype == &ktype_part));
}

如果是block/part type的kobject产生了event,才会告诉user space,其他的都被忽略。

kset_hotplug_ops里,name这个callback则会把相应的subsystem的名字传递给上层的hotplug,作为/sbin/hotplug唯一的参数。hotplug这个callback则会把其他上层hotplug需要的东西通过环境变量的方式告诉user space。

int (*hotplug)(struct kset *kset, struct kobject *kobj, 
               char **envp, int num_envp, char *buffer, 
               int buffer_size);

kset和kobj代表了产生event的主体,envp用来存放要传递给user space的环境变量(使用NAME=value的格式),并用num_envp表示envp里环境变量的长度。这些环境变量还需要encode到buffer里,长度是buffer_size。要注意的是,envp要以一个NULL结尾,这样可以让kernel知道什么时候结束。hotplug的返回值正常的话为0,返回任何非零值表明发生了错误,会导致event取消,不会发给user space。

产生hotplug event的逻辑位于bus driver这一层,device driver不需要关心。

14.4. Buses, Devices, and Drivers


上面讲得都是low-level的数据结构,下面会讲一些high-level的数据结构,比如kernel中的bus,devcie以及drivers。对于大部分人而言,只需要接触device和driver,一般不会需要涉及到bus。

14.4.1. Buses

总线是CPU和其他设备之间的数据通路,无论是什么device,最后必然需要连接到一个bus上,可能是真实的总线,也可能是虚拟的总线。总线之间可以相互链接,比如USB controller也链接到PCI bus。kernel中的设备模型,可以表述bus和device之间的连接关系。kernel中的bus用struct bus_type来表示:

#include <linux/device.h>

struct bus_type {
    char *name;
    struct subsystem subsys;
    struct kset drivers;
    struct kset devices;
    int (*match)(struct device *dev, struct device_driver *drv);
    struct device *(*add)(struct device * parent, char * bus_id);
    int (*hotplug) (struct device *dev, char **envp,
    int num_envp, char *buffer, int buffer_size);
    /* Some fields omitted */
};

name就是bus名称,比如pci等;subsys是bus所属的sub system,这些sub system一般位于sysfs里的/sys/bus下面;一个bus_type下有两个kset,一个是bus driver,一个是挂在这个bus上的所有device。

14.4.1.1 Bus registration

这是一个例子,比如有一个virtual bus叫lddbus,注册过程如下:

struct bus_type ldd_bus_type = {
    .name = "ldd",
    .match = ldd_match,
    .hotplug = ldd_hotplug,
};

//有可能fail,所以要check返回值。
ret = bus_register(&ldd_bus_type);
if (ret)
    return ret;

//remove bus
void bus_unregister(struct bus_type *bus);

很少有bus需要初始化,大部分都是device model core完成的,我们这里因为是自己定义的bus,需要设置name,以及一些callback。调用bus_register可以把自己的bus注册给kernel,在bus_register完成以后,在/sys/bus/下就可以看到这个bus了,之后可以向bus添加device。如果要移除bus,调用bus_unregister。

14.4.1.2 Bus methods

在struct bus_type中有一些callback,这些callback就是bus methods。他们作为device core和device driver的中间层提供服务。

//如果有bus driver或者device添加进来,就会调用match callback。由bus自己来检查能否handle这个device或者driver。
int (*match)(struct device *device, struct device_driver *driver);
//允许bus通过hotplug export一些环境变量,这样user mode可以拿到环境变量来处理hotplug event
int (*hotplug) (struct device *device, char **envp, int num_envp, char *buffer, int buffer_size);

match函数可能会被调用不止一次,每次有新的device或者driver添加到bus,就会调用。如果发现device可以被这个driver handler,就返回非零值。hotplug函数的作用,在于让bus有机会在user space拿到hotplug event之前设置一些环境变量。这个函数的参数和kset的hotplug是一样的。例子里lddbus的match函数定义如下:

static int ldd_match(struct device *dev, struct device_driver *driver)
{
    return !strncmp(dev->bus_id, driver->name, strlen(driver->name));
}

一般而言,判断device和driver是否match,需要比较他们的device的bus id和driver支持的hardware id,一致就是匹配的。

例子里lddbus的hotplug函数定义如下:

static int ldd_hotplug(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size)
{
    envp[0] = buffer;
    if (snprintf(buffer, buffer_size, "LDDBUS_VERSION=%s", Version) >= buffer_size)
        return -ENOMEM;
    envp[1] = NULL;
    return 0;
}

这个ldd_hotplug只是设置一个bus version的环境变量。

14.4.1.3 Iterating over devices and drivers

某些情况下,bus driver需要loop当前bus下的所有device或driver,kernel提供了helper function:

//loop所有的device
int bus_for_each_dev(struct bus_type *bus, struct device *start, 
            void *data, int (*fn)(struct device *, void *));

//loop所有的driver
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
            void *data, int (*fn)(struct device_driver *, void *));

bus_for_each_dev会loop bus中从start开始的所有device,并调用fn函数,调用时传递的参数是data。如果fn返回了错误,错误值从bus_for_each_dev返回。如果start为NULL,从bus里的第一个device开始loop。

bus_for_each_drv的功能和作用与bus_for_each_dev类似。注意,上面的两个helper function里面都需要获取同样的锁,所以不能在一个里调用另一个,否则会死锁。

14.4.1.4 Bus attributes

kernel的device model中,每一层都提供了管理attribute的接口,bus也不例外。bus type自己的attribute:

#include <linux/device.h>
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);
};

bus_attribute和之前的attribute都比较类似,包括里面的数据结构,或者method。创建attribute:

//编译时创建并初始化bus attribute。生成bus_attr_##name的attribute。
BUS_ATTR(name, mode, show, store);
//为bus创建attribute
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);
//删除bus的attribute
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);

例子:

//attribute version的show函数
static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
    return snprintf(buf, PAGE_SIZE, "%s\n", Version);
}

//创建bus_attr_version这个attribute
static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);

//在module load的阶段,可以将bus_attr_version添加到sysfs中去。
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
    printk(KERN_NOTICE "Unable to create version attribute\n");

上面的code就会创建一个version的sysfs attribute,位置是/sys/bus/ldd/version。

14.4.2. Devices

上面讲的都是bus,下面讲device。在kernel中,最底层的设备用struct device结构体表示:

struct device {
    struct device *parent;
    struct kobject kobj;
    char bus_id[BUS_ID_SIZE];
    struct bus_type *bus;
    struct device_driver *driver;
    void *driver_data;
    void (*release)(struct device *dev);
    /* Several fields omitted */
};

parent是指device属于哪个parent device,一般情况下,这个parent是某个bus device或者bus controller。如果parent是NULL,说明这是个top level的device,一般很少是这种情况。

kobj就是这个device对应的kobject,通过这个kobj可以把这个device组织到kernel device层次结构中去。这里有一点:device->kobj->parent 一般和 &device->parent->kobj是同一个。

char bus_id[BUS_ID_SIZE],可以唯一标志这个device的字符串,比如PCI设备,就是标准的domain-bus-device-function这样的字符串。

struct bus_type *bus,表明这个device挂在哪个bus上。比如PCI设备,bus就是pci_bus。

struct device_driver *driver,管理这个device的driver结构。

void *driver_data,driver的私有数据。

void (*release)(struct device *dev),当device的最后一个reference counter被减掉之后,就会调用device的release。注意,因为device通过kobject管理,所以这个release是由kobject的release调进来。所有的device必须要有release函数。

在注册device时,parent, bus_id, bus,和release 这几个是必须要有的。

14.4.2.1 Device registration

两个接口:

//register device
int device_register(struct device *dev);
//unregister device
void device_unregister(struct device *dev);

 

上面提到的lddbus这个例子,lddbus本身也是device,也要先注册进去:

static void ldd_bus_release(struct device *dev)
{
    printk(KERN_DEBUG "lddbus release\n");
}

struct device ldd_bus = {
    .bus_id = "ldd0",
    .release = ldd_bus_release
};

ret = device_register(&ldd_bus);
if (ret)
    printk(KERN_NOTICE "Unable to register ldd0\n");

在上面的例子中,ldd_bus没有parent,bus也设置为NULL,表明它是一个top-level的bus type。当device_register执行完成以后,就可以在sysfs里看到ldd0这个bus,位置是在/sys/devices/ldd0/。

14.4.2.2 Device attributes

相应的,device也有device_attribute:

struct device_attribute {
    struct attribute attr;
    ssize_t (*show)(struct device *dev, char *buf);
    ssize_t (*store)(struct device *dev, const char *buf, size_t count);
};

//编译时创建名字为name的device attibute(dev_attr_##name)
DEVICE_ATTR(name, mode, show, store);
//创建attribute
int device_create_file(struct device *device, struct device_attribute *entry);
//删除attribute
void device_remove_file(struct device *dev, struct device_attribute *attr);

struct bus_type中有一个dev_attrs,指向bus当前挂的所有device的default attributes。

14.4.2.3 Device structure embedding

和kobject的使用方法类似,struct device一般也不单独使用,而是内嵌到大的某某device当中。比如kernel中的pci_dev以及usb_dev,里面就是内嵌的struct device。pci_dev/usb_dev通过内嵌的struct device注册到kernel中,kernel通过struct device就可以管理pci_dev/usb_dev。

上面提到的例子lddbus,创建了自己的ldd_device:

struct ldd_device {
    char *name;
    struct ldd_driver *driver;
    struct device dev;
};
#define to_ldd_device(dev) container_of(dev, struct ldd_device, dev);

driver自己定义的device通常还包含更多的东西,比如vendor,name,resource等等,pci_dev就是一个例子。这里的name和struct device里的name不一样,这里只是ldd_device的名字,可以随意写,但是struct device里的name是device的BDF(如果是PCI device),是无法修改的。定义了一个to_ldd_device,通过to_ldd_device就可以根据struct device的指针获取到ldd_device指针。

lddbus可以定义自己的device register函数:

int register_ldd_device(struct ldd_device *ldddev)
{
    ldddev->dev.bus = &ldd_bus_type;
    ldddev->dev.parent = &ldd_bus;
    ldddev->dev.release = ldd_dev_release;
    strncpy(ldddev->dev.bus_id, ldddev->name,BUS_ID_SIZE);
    return device_register(&ldddev->dev);
}

EXPORT_SYMBOL(register_ldd_device);

register_ldd_device里面,只是初始化一些struct device需要的东西,这些东西在ldd_device外面的driver根本不需要知道。如果device需要创建自己的一些attribute,这里就可以做。这里有个简单的例子,sculld这个device要挂载在ldd bus上,同时创建一个名为dev的attribute:

static ssize_t sculld_show_dev(struct device *ddev, char *buf)
{
    struct sculld_dev *dev = ddev->driver_data;
    return print_dev_t(buf, dev->cdev.dev);
}
static DEVICE_ATTR(dev, S_IRUGO, sculld_show_dev, NULL);

//初始化时,注册device,同时会创建dev这个attribute。struct device里的private存储了ldd_dev。
static void sculld_register_dev(struct sculld_dev *dev, int index)
{
    sprintf(dev->devname, "sculld%d", index);
    dev->ldev.name = dev->devname;
    dev->ldev.driver = &sculld_driver;
    dev->ldev.dev.driver_data = dev;
    register_ldd_device(&dev->ldev);
    device_create_file(&dev->ldev.dev, &dev_attr_dev);
}

14.4.3. Device Drivers

kernel中记录着所有注册进来的device driver,当对应的device出现在kernel中,匹配的driver就会被load起来。device driver使用结构体struct device_driver来表示:

struct device_driver {
         char *name;
         struct bus_type *bus;
         struct kobject kobj;
         struct list_head devices;
         int (*probe)(struct device *dev);
         int (*remove)(struct device *dev);
         void (*shutdown) (struct device *dev);
         /*some fields omitted*/
};

name就是driver的名字,出现在sysyfs中;bus是driver能够适配的设备的bus 类型;kobj就是管理driver用的kobject;devices是当前和driver绑定的所有device;probe用来检查device是否存在,并且能够被这个driver驱动;remove是在device被从系统中remove的时候调用;shutdown是device被关闭的时候被调用。driver也需要通过接口注册给kernel,不用的时候解除注册:

int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);

相应的,driver也有对应的driver_attribute,driver_attribute的使用方法和之前别的attribute是一样的,这里不再赘述。

struct driver_attribute {
        struct attribute attr;
        ssize_t (*show)(struct device_driver *drv, char *buf);
        ssize_t (*store)(struct device_driver *drv, const char *buf, size_t count);
    };
DRIVER_ATTR(name, mode, show, store);

int driver_create_file(struct device_driver *drv, struct driver_attribute *attr);
void driver_remove_file(struct device_driver *drv, struct driver_attribute *attr);

另外,bus_type里也有一个drv_attrs,存放了所有bus绑定的driver的default attributes。(device也有对应的dev_attrs)

14.4.3.1 Driver structure embedding

和struct device内嵌的用法类似,driver也是使用内嵌的用法,比如ldd 这个sample bus,创建了一个ldd_driver的类型,里面就包含了一个struct device_driver的结构体:

struct ldd_driver {
         char *version;
         struct module *module;
         struct device_driver driver;
         struct driver_attribute version_attr;
};
#define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver);

int register_ldd_driver(struct ldd_driver *driver)
{
    int ret;
    driver->driver.bus = &ldd_bus_type;
    ret = driver_register(&driver->driver);
    if (ret)
        return ret;
    driver->version_attr.attr.name = "version";
    driver->version_attr.attr.owner = driver->module;
    driver->version_attr.attr.mode = S_IRUGO;
    driver->version_attr.show = show_version;
    driver->version_attr.store = NULL;
    return driver_create_file(&driver->driver, &driver->version_attr);
}

static ssize_t show_version(struct device_driver *driver, char *buf)
{
    struct ldd_driver *ldriver = to_ldd_driver(driver);
    sprintf(buf, "%s\n", ldriver->version);
    return strlen(buf);
}

上面的例子中,register_ldd_driver的前半部分是注册struct device_driver,后面的部分就是处理version attribute相关的内容,version是这个sample code特别需要的。driver_register会把我们的driver注册到kernel core里面去,之后我们可以这样创建一个ldd_driver:

static struct ldd_driver sculld_driver = {
    .version = "$Revision: 1.1 $",
    .module = THIS_MODULE,
    .driver = {
        .name = "sculld",
    },
};

ldd_driver创建完以后,ldd driver module调用register_ldd_driver把自己的driver注册进去,然后在sysfs中就可以看到相关的节点:

$ tree /sys/bus/ldd/drivers
/sys/bus/ldd/drivers
`-- sculld
    |-- sculld0 -> ../../../../devices/ldd0/sculld0
    |-- sculld1 -> ../../../../devices/ldd0/sculld1
    |-- sculld2 -> ../../../../devices/ldd0/sculld2
    |-- sculld3 -> ../../../../devices/ldd0/sculld3
    `-- version

14.5. Classes


class是本章最后一个要讲的设备类型的概念,它本身不是某个bus,device,而是对底层device的抽象,抽象之后提供了更高等级的一种视角。driver也许知道某个disk是SCSI disk,还是ATA disk,但是无论是哪种,他们都属于simple disk这个class。因此,class的作用就很简单,把所有能提供相同功能的device归为一类class,也就说,class只关心设备能做什么,而不关心如何做,或者如何和bus相连。

kernel中,基本上所有的class都在/sys/class下,比如所有的网卡设备都在/sys/class/net下,所有的输入设备都在/sys/class/input下等,串口设备都在/sys/class/tty下。只有block设备是个例外,在/sys/block,这个是历史原因。

这种class关系都是kernel core来管理,不需要driver显示参与,比如device driver可能只是注册了,但是kernel会自动帮driver找到对应的class,并创建sysfs。但在某些情况下,driver需要和class直接打交道。

class在很多情况下,都是给user space提供信息最方便的方式。当某个subsystem创建了class,那么这个class就完全属于这个sub system,不用担心module owner的问题,而且不涉及某个具体的device,在sysfs的路径是在/sys/class/下,也更容易操作。

kernel的driver core提供了两种不同的接口给driver来操作class。一种是class_simple,可以让添加新的class 变得很容易,主要的目的是通过export包含device number的attribute,使得device node可以自动创建;另外一种interface是regular interface,这种方式比较复杂,同时也比较灵活,放到后面讲。

14.5.1. The class_simple Interface

这种接口比较简单,只需要调用几个简单的kernel function,就能创建包含default attribute的class。第一步,创建class本身:

//创建一个class,名字为name
struct class_simple *class_simple_create(struct module *owner, char *name);
//销毁class
void class_simple_destroy(struct class_simple *cs);

class_simple_create可能会失败,所以需要使用IS_ERR检查返回值。使用clas_simple_destroy来销毁之前创建的class_simple。

创建class的目的,是为了向class中添加device。在class创建完成以后,通过以下方式添加device:

struct class_device *class_simple_device_add(struct class_simple *cs,
                                             dev_t devnum,
                                             struct device *device,
                                             const char *fmt, ...);

void class_simple_device_remove(dev_t dev);

class_simple_device_add中,cs表示之前创建的simple class;devnum表示要添加的device的number;device指针代表了要添加的device本身;剩下的参数是printk风格的参数,用来指定device name。使用class_simple_device_add可以向class中添加一个device,并且自动创建dev这样一个attribute,如果device为NULL,就创建一个symbol link,名字为device,指向/sys/devices入口。当然,可以加入自己定义的attribute,使用class_device_create_file接口就可以,我们放在后面讲。当device需要从class中移除时,调用class_simple_device_remove,要注意的是,remove的时候不需要cs参数,因为device number本身就是唯一的了。

当class发现设备添加或者删除时,就会产生event,并报告给user space。如果driver需要在event送到user space之前,自己添加一些环境变量,可以使用如下接口设置一个hotplug callback:

int class_simple_set_hotplug(struct class_simple *cs,
                             int (*hotplug)(struct class_device *dev,
                                            char **envp, int num_envp,
                                            char *buffer, int buffer_size));

14.5.2. The Full Class Interface

和simple class相比,full class更加灵活,功能更加全面。因为regular class要和其他的设备模型交互,因此会略微复杂。

14.5.2.1 Managing classes

我们先看class的结构体:

struct class {
    char *name;
    struct class_attribute *class_attrs;
    struct class_device_attribute *class_dev_attrs;
    int (*hotplug)(struct class_device *dev, char **envp, int num_envp, char *buffer, int buffer_size);
    void (*release)(struct class_device *dev);
    void (*class_release)(struct class *class);
    /* Some fields omitted */
};

name就是这个class的名字,显示在/sys/class下,每个class都要有并且必须唯一。当class被注册时,class_attrs里的所有attribute对应的sysfs节点都会被自动创建。此外,当有device被添加进来时,所有的device都会有一些默认的attribute,这些attributre就是class_dev_attrs;class中有一个自己的hotplug,当event产生时,class通过这个hotplug向user space发送环境变量作为参数;release函数是给class中的device用的,当有device从class中remove时,调用release;class_release是给class用的,当class被destroy时,调用class_release。下面是和class相关的接口:

int class_register(struct class *cls);
void class_unregister(struct class *cls);

struct class_attribute {
    struct attribute attr;
    ssize_t (*show)(struct class *cls, char *buf);
    ssize_t (*store)(struct class *cls, const char *buf, size_t count);
};

CLASS_ATTR(name, mode, show, store);
int class_create_file(struct class *cls, const struct class_attribute *attr);
void class_remove_file(struct class *cls, const struct class_attribute *attr);

14.5.2.2 Class devices

class存在的最主要目的就是管理功能类似的device,因此就有对应的class device:

struct class_device {
    struct kobject kobj;
    struct class *class;
    struct device *dev;
    void *class_data;
    char class_id[BUS_ID_SIZE];
};

class_id里存储的就是device在sysfs里显示的名字;class就指向这个device所属的class;dev指向这个device对应的struct device,dev指针是可选的,如果设为非NULL,就可以在class中创建symbol link,从class entry指向/sys/devices下真实的device entry,这样可以在class中也可以方便的找到device;class_data可以用来存储class的私有数据。

管理class_device对应的接口:

//当class device register时,class会为其自动创建defaut attribute。
int class_device_register(struct class_device *cd);
void class_device_unregister(struct class_device *cd);
//可以对已经存在的class device进行rename
int class_device_rename(struct class_device *cd, char *new_name);

class device对应的attribute结构体:

struct class_device_attribute {
    struct attribute attr;
    ssize_t (*show)(struct class_device *cls, char *buf);
    ssize_t (*store)(struct class_device *cls, const char *buf,
    size_t count);
};

CLASS_DEVICE_ATTR(name, mode, show, store);
int class_device_create_file(struct class_device *cls, const struct class_device_attribute *attr);
void class_device_remove_file(struct class_device *cls, const struct class_device_attribute *attr);

上面已经讲过,在class中有一个class_dev_attrs的指针,它记录的就是class里每个device都会有的default attributes,当设备被添加到class里时,就会在sysfs中为这些devcie创建文件节点。

14.5.2.3 Class interfaces

class子系统和kernel中的其他子系统有一些区别,就是class 子系统有interface。这个interface,可以看作是一种trigger机制,当device添加或者移除时,通过interface可以获取通知。class interface定义:

struct class_interface {
    struct class *class;
    int (*add) (struct class_device *cd);
    void (*remove) (struct class_device *cd);
};

int class_interface_register(struct class_interface *intf);
void class_interface_unregister(struct class_interface *intf);

class interface的用法也很简单,实现一个add和remove callback,指定需要监听的class,然后注册进去,当这个class有设备添加或者移除时,add或remove callback就会被调用。

14.6. Putting It All Together


为了更好的理解kernel中的设备模型,我们看一下device在kernel的生命周期。以PCI device和driver为例,把上面讲述的过程串起来,比如device如何被添加和移除,driver如何被添加和移除。虽然以PCI为例,但是思想和方法与其他的设备类型没有大的区别。

PCI core,driver core,以及individual driver之间的关系如下所示:

14.6.1. Add a Device

PCI subsystem创建了一个pci_bus_type,用来代表pci这种bus:

struct bus_type pci_bus_type = {
    .name = "pci",
    .match = pci_bus_match,
    .hotplug = pci_hotplug,
    .suspend = pci_device_suspend,
    .resume = pci_device_resume,
    .dev_attrs = pci_dev_attrs,
};

当kernel boot起来,并发现pci bus type的时候,就会通过bus_register把pci_bus_type注册到driver core中去,注册完成时,kernel就会创建/sys/bus/pci,并在这个目录下创建两个目录:devices和drivers。

而device driver就需要在自己的driver中包含struct pci_driver结构体,里面可以设置device driver实现或者支持的功能。当device driver通过PCI core注册的时候,PCI core就会初始化其中的struct pci_driver:

/* initialize common driver fields */
drv->driver.name = drv->name;
drv->driver.bus = &pci_bus_type;
drv->driver.probe = pci_device_probe;
drv->driver.remove = pci_device_remove;
drv->driver.kobj.ktype = &pci_driver_kobj_type;

其实就是把struct device_driver里的bus相关的东西都初始化为pci,然后提供PCI的probe、remove函数。然后pci core就会注册pci driver到driver core:

/* register with core */
error = driver_register(&drv->driver);

当注册完成以后,device driver就可以和它支持的任意设备进行绑定。

PCI core做的事情就是架构相关的了,它需要和PCI bus通信,probe PCI的start address,查找所有的PCI设备,当找到某个PCI设备的时候,就会创建一个struct pci_dev表示它:

struct pci_dev {
    /* ... */
    unsigned int devfn;
    unsigned short vendor;
    unsigned short device;
    unsigned short subsystem_vendor;
    unsigned short subsystem_device;
    unsigned int class;
    /* ... */
    struct pci_driver *driver;
    /* ... */
    struct device dev;
    /* ... */
};

总线相关的一些成员,由PCI core driver来初始化,比如devfn, vendor, device以及一些其他的成员;其中struct device dev的parent也被设置为这个pci bus的device(bus本身也是一种device);bus变量会被设置为pci_bus_type;name和bus_id等会根据pci设备的name和id来设置。初始化这个pci_dev之后,通过device_reigster将设备注册给device core。

device_register(&dev->dev);

这个函数调用时,driver core会给device设置很多的field,以及把device的kobj注册给kobj core,然后会产生hotplug等,再把device添加到parent的device list。所有的device都按照这个流程,这样系统里所有的device就组成了一个层次结构,通过遍历就可以找到所有的device。

与此同时,device还会被添加到所在bus的device list中,这里是一个pci 的device,就被添加到pci_bus_type的list中去,然后遍历pci bus中的driver list,通过match函数,找到和device适配的driver,pci的match函数就是pci_bus_match,参数是struct device和struct device_driver,然后在pci match函数中,把struct device转换成struct pci_dev,device driver转换成struct pci_driver,如果device和driver可以适配,就返回非0值,否则返回0。如果pci core发现match函数返回了0,就继续检查下一个driver。

一旦pci core给device找到了匹配的driver,就把这个device中的driver指针设为找到的那个driver,然后调用driver中的probe函数,这个probe函数就是pci_device_probe。在pci probe中,driver也会检查当前的device是否适配,如果是,增加device的reference counter,然后调用individual pci device driver的probe函数,初始化这个device。初始化完成以后返回0,kernel就会把devcie添加到这个driver支持的device list中去,并且在driver的sysfs下创建device的symbol link;如果individual pci device driver的probe返回了失败,那么pci probe就会寻找下一个能够支持的driver,并按照上面的流程在走一遍。当整个流程走完以后,sysfs中就会创建层次结构:

$ tree /sys/bus/pci
/sys/bus/pci/
|-- devices
|   |-- 0000:00:00.0 -> ../../../devices/pci0000:00/0000:00:00.0
|   |-- 0000:00:00.1 -> ../../../devices/pci0000:00/0000:00:00.1
|   |-- 0000:00:00.2 -> ../../../devices/pci0000:00/0000:00:00.2
|   |-- 0000:00:02.0 -> ../../../devices/pci0000:00/0000:00:02.0
|   |-- 0000:00:04.0 -> ../../../devices/pci0000:00/0000:00:04.0
|   |-- 0000:00:06.0 -> ../../../devices/pci0000:00/0000:00:06.0
|   |-- 0000:00:07.0 -> ../../../devices/pci0000:00/0000:00:07.0
|   |-- 0000:00:09.0 -> ../../../devices/pci0000:00/0000:00:09.0
|   |-- 0000:00:09.1 -> ../../../devices/pci0000:00/0000:00:09.1
|   |-- 0000:00:09.2 -> ../../../devices/pci0000:00/0000:00:09.2
|   |-- 0000:00:0c.0 -> ../../../devices/pci0000:00/0000:00:0c.0
|   |-- 0000:00:0f.0 -> ../../../devices/pci0000:00/0000:00:0f.0
|   |-- 0000:00:10.0 -> ../../../devices/pci0000:00/0000:00:10.0
|   |-- 0000:00:12.0 -> ../../../devices/pci0000:00/0000:00:12.0
|   |-- 0000:00:13.0 -> ../../../devices/pci0000:00/0000:00:13.0
|   `-- 0000:00:14.0 -> ../../../devices/pci0000:00/0000:00:14.0
`-- drivers
    |-- ALI15x3_IDE
    |   `-- 0000:00:0f.0 -> ../../../../devices/pci0000:00/0000:00:0f.0
    |-- ehci_hcd
    |   `-- 0000:00:09.2 -> ../../../../devices/pci0000:00/0000:00:09.2
    |-- ohci_hcd
    |   |-- 0000:00:02.0 -> ../../../../devices/pci0000:00/0000:00:02.0
    |   |-- 0000:00:09.0 -> ../../../../devices/pci0000:00/0000:00:09.0
    |   `-- 0000:00:09.1 -> ../../../../devices/pci0000:00/0000:00:09.1
    |-- orinoco_pci
    |   `-- 0000:00:12.0 -> ../../../../devices/pci0000:00/0000:00:12.0
    |-- radeonfb
    |   `-- 0000:00:14.0 -> ../../../../devices/pci0000:00/0000:00:14.0
    |-- serial
    `-- trident
        `-- 0000:00:04.0 -> ../../../../devices/pci0000:00/0000:00:04.0

14.6.2. Remove a Device

从kernel中移除一个PCI device有多种不同的方法,在系统还在运行时添加或移除PCI设备越来越流行,Linux Kernel也支持这种操作,其中就有一个fake的PCI hotplug driver,可以让driver developer测试自己的PCI device driver是否支持hotplug,这个fake的PCI hotplug driver名字叫做fakephp,它的作用就让kernel以为某个PCI device被移除了。但是,它并不允许用户真的把PCI设备从机器中拔掉,除非额外的硬件支持这样做。developer可以查阅相关的文档,了解如何使用fakephp。

当PCI device移除时,PCI core就会调用pci_remove_bus_device,这个函数会做一些PCI bus-specific的cleanup,然后调用device_unregister,在这个函数里面,driver core会解除driver和这个device sysfs下的link,把device从driver core的list中删除,调用kobject_del将device的kobject ref减1,kobject_del会产生hotplug event,并报给user space,然后device kobject对应的sysfs等都会被删除。如果pci device对应的kobject_del减为0,就会调用pci_release_dev函数,这个pci_release_dev会把struct pci_device之前分配的内存释放掉。

14.6.3. Add a Driver

PCI device module driver通过pci_register_driver把自己注册到PCI core中,pci_register_driver会把pci_driver中的struct device_driver初始化,然后调用driver_register把自己注册到driver core,driver_register会初始化struct device_driver中的一些锁,然后调用bus_add_driver,bus_add_driver又干了这些事情:

1,查找driver要关联的bus是否存在,如果不存在,直接返回。

2, 根据关联的bus类型和driver的属性,创建driver对应的sysfs。

3, 获取bus的锁,然后loop查找bus下的所有device,如果发现和driver匹配的device,开始初始化并probe device。

14.6.4. Remove a Driver

Remove driver是非常简单的事情,直接调用pci_unregister_driver就可以了,它又会调用driver_unregister,这个函数就会把driver之前创建的sysfs删除,然后遍历和这个driver绑定的device,调用他们的release。

down(&drv->unload_sem);
up(&drv->unload_sem);

在所有的device都和driver unbind之后,driver code还需要做上面的这个事情,也就是等待module driver被别人使用完,否则被人还在使用你的module driver,你就free了,那就会发生kernel crash。

14.7. Hotplug


可以从两个角度看待hotplug,第一个角度kernel,kernel认为hotplug是hardware,kernel,kernel driver三者之间的交互;第二个角度是user space,user space看到的hotplug是kernel和user space之间通过/sbin/hotplug的交互,当kernel需要通知user space hotplug event发生时,就调用/sbin/hotplug程序。

14.7.1. Dynamic Devices

通常意义上说的hotplug,是指在system power on的状态下,外设添加进来或者从系统中移除。在之前的系统中,并不支持hotplug,所以developer不需要关心设备的hotplug,因为这种事情不会发生,从系统启动开始,设备就在那里,直到系统关机。新的系统开始支持外设的hotplug,kernel本身要能够处理这种事件,而device driver也要处理device的hotplug。

每一种总线类型都有自己处理hotplug的方法,比如PCI总线设备。PCI外设在移除时,不会立刻就能通知到driver(调用driver的remove函数),在此期间driver仍然在工作,但是设备已经不存在,这个时候所有对PCI外设的访问都会返回0xff,因此device driver要注意检查从PCI总线返回的值,以应对外设的hotplug。比如drivers/usb/host/ehci-hcd.c :

result = readl(ptr);
if (result =  = ~(u32)0)    /* card removed */
    return -ENODEV;

14.7.2. The /sbin/hotplug Utility

前面已经提到过,当kernel发现有外设的hotplug时,就会产生event,并调用/sbin/hotplug通知user space,而这个/sbin/hotplug只是一个shell脚本,它的作用就是在/etc/hotplug.d/目录下找到合适的应用程序,并调用它。在大多数linux发行版中,/sbin/hotplug长这个样子:

DIR="/etc/hotplug.d"
for I in "${DIR}/$1/"*.hotplug "${DIR}/"default/*.hotplug ; do
if [ -f $I ]; then
    test -x $I && $I $1 ;
fi done
exit 1

这个脚本loop所有/etc/hotplug.d目录下,以.hotplug后缀的程序,并测试他们能否处理这个event,如果可以,就调用对应的程序处理hotplug event。之前我们也提到过,每当kernel有kobject的增减或删除,就会产生hotplug event,hotplug应用处理程序被调用时,只是传递一个command line 参数,这个参数就是kernel报上来的event name,kernel除了把event name报上来,也会在环境变量中存储更多hotplug应用程序需要的参数,这些参数可以用来判断kernel发生了什么事情。

再来说一下kernel报上来的event name,这个name由kobject对用的kset来决定,如果kset的hotplug_ops里实现了name这个函数,就使用这个函数报出去的name,如果没有实现这个函数,就直接使用kset的name。

再来说kernel报上去的环境变量,kernel默认设置的环境变量主要有:

ACTION

有两个值,add和remove,取决于kernel中的kobject是create还是destroy。

DEVPATH

指向产生event的kobject所在的sysfs,其中没有包含sysfs mount的位置,这个需要user space自己确定。

SEQNUM

hotplug的sequence number,是64bit的值,每次产生hotplug event都会增加.,通过这个值可以对kernel报上来的event排序。

SUBSYSTEM

其实就是bus type,比如pci等。

当某个bus上的device 添加或移除时,对应的bus都会在hotplug环境变量里设置bus自己的值,实现的方式就是调用bus里struct kset_hotplug_ops里面的hotplug callback。通过设置bus-specific的环境变量,hotplug应用程序可以知道哪个bus上发生了hotplug event,然后据此load对应的module driver。后面就列举了一些常见的bus会设置的环境变量,我们只关心PCI:

PCI_CLASS

16进制的pci class number。

PCI_ID

vendor和device ID,vendor:device,16进制。

PCI_SUBSYS_ID

subsys_vendor:subsys_device,16进制。

PCI_SLOT_NAME

domain:bus:slot:function,example:0000:00:0d.0

14.7.3. Using /sbin/hotplug

每次有device的add和remove,kernel都会调用/sbin/hotplug,利用这个性质,产生了一些很有用的user space tools,比如hotplug sripts和udev。

14.7.3.1 Linux hotplug scripts

linux hotplug scripts是第一个使用/sbin/hotplug的,这些script通过kernel设置的环境变量确定是哪些device产生的event,然后load对应的module driver。之前也已经提到过,当driver中定义了MODULE_DEVICE_TABLE这个宏,depmod程序就会提取driver的这些信息,然后记在/lib/module/KERNEL_VERSION/modules.*map,这些map文件都按照bus类型做了分类,比如PCI,USB等。

hotplug scripts就是利用这些map文件,找到和device对应的module driver,然后load进kernel,如果有多个module driver和这个device匹配,script也不会停止,它都会load,让kernel自己决定哪个driver是最匹配的。此外,这些script不会unload module driver,因为当前device虽然被destroy,但是和他对应的module driver也许还驱动了其他的device,如果driver此时被unload,是会发生错误的。

除了使用/lib/module/KERNEL_VERSION/modules.*map找到对应的module driver,也可以使用modprobe,modprobe直接从module driver中读取MODULE_DEVICE_TABLE,这样的话,hotplug script其实就简化为modprobe的一个warpper即可。

14.7.3.2 udev

kernel之所以要创建一个统一的设备模型,目的之一就是希望user space能够动态的管理这些device,devfs可以做这个事情,但是bug较多,而且命名不规范,难以使用,所以产生了udev。udev依赖于kernel通过sysfs暴露的device信息,以及/sbin/hotplug产生的hotplug event。而类似于策略这种事情,比如device的命名等,都交给user space去做。

如果要支持udev,device driver唯一需要做的事情就是通过sysfs,把自己的device暴露出去,其中要指明device major/minor number,如果device driver使用了kernel自己的subsystem来获取major/minor number,这个事情kernel已经替你做好了,那device driver就什么也不需要做,就能够支持udev了。

udev会在/sys/class/下面寻找dev,用于确定产生hotplug event的devcie对应的major/minor number是几,device driver只需要创建这个dev文件即可,而class_simple就可以做这个事情。

class_simple之前我们已经提到过,这里再重复一下。

使用class_simple的第一步是要通过class_simple_create创建class_simple:

static struct class_simple *foo_class;
...
foo_class = class_simple_create(THIS_MODULE, "foo");
if (IS_ERR(foo_class)) {
    printk(KERN_ERR "Error creating foo class.\n");
    goto error;
}

比如上面的code,就会在/sys/class/下创建一个目录foo,当device driver找到了一个新的device,就通过class_simple_device_add把新的device 添加到class,同时会指定minor number:

class_simple_device_add(foo_class, MKDEV(FOO_MAJOR, minor), NULL, "foo%d", minor);

上面的code,就会在/sys/class/foo下,创建一个目录fooN,N代表device的minor number。在fooN目录下,只有一个文件——dev,这个就是udev需要使用的包含了device major/minor number的dev文件。

当device driver和device unbind,并且你需要释放之前分配的minor number,driver需要调用class_simple_device_remove:

class_simple_device_remove(MKDEV(FOO_MAJOR, minor));

当device driver被shutdown的时候,driver需要调用class_simple_destroy来释放之前创建的class_simple:

class_simple_destroy(foo_class);

14.8. Dealing with Firmware


有些device在能够正常工作之前,需要提供firmware,而这个firmware因为其特殊性,不能包含在driver中的code当中,比如你的code是GPL的,但是firmware因为是vendor私有的,所以不会是GPL license,这就比较麻烦。

14.8.1. The Kernel Firmware Interface

最合适的解决办法,就是通过user space来处理。kernel提供了这样的接口,用来从user space获取firmware:

#include <linux/firmware.h>
int request_firmware(const struct firmware **fw, char *name,
                     struct device *device);

上面这个函数的功能,就是要求user space提供device需要的firmware。name是一个ID,用来表明firmware,通常是vendor自己确定的firmware 文件的名字,比如my_firmware.bin;如果firmware load成功,就返回0;fw指向这样的一个结构体:

struct firmware {
        size_t size;
        u8 *data;
};

这个结构体里就包含真正的firmware数据,可以被download进device,但是因为来自于user space,所以kernel driver还需要对这里面的data进行check,firmware里通常也会包含识别信息,或者checksum,kernel driver同样需要进行检查和确认。

如果driver已经把firmware发送给了device,就需要把firmware释放掉:

void release_firmware(struct firmware *fw);

当device driver需要user space 提供firmware时,需要调用request_firmware,这个函数需要user space的帮助,因此极有可能会休眠,如果device driver不能休眠,可以调用另外一个接口:

int request_firmware_nowait(struct module *module, 
                            char *name, struct device *device, void *context,
                            void (*cont)(const struct firmware *fw, void *context));

这里面新增加的一些参数有,module——通常就是THIS_MODULE,context——一个私有指针,firmware subsystem不会使用;以及一个cont。如果一切运转良好,request_firmware_nowait会开始load过程并返回0,在将来的某个时间,cont被调用,来告诉driver结果,如果load fail了,fw就是NULL。

14.8.2. How It Works

firmware subsystem利用了sysfs和hotplug机制,当kernel driver调用了request_firmware时,/sys/class/firmware下会创建一个新的目录,名字就是device的name,这个目录包含三个attributes:

loading

当user space开始load firmware时,进程把loading设置为1;loading完成以后设置为0;如果设置为-1,就会取消loading。

data

data是一个二进制attribute,里面就是firmware本身,当user space进程把loading设置为1,以后,就开始向data中写入firmware。

device

这是一个软连接,指向/sys/devices。

当device被创建,sysfs也创建了device file时,kernel产生了hotplug event,传递给/sbin/hotplug的环境变量中有一个是FIMWARE

,里面就是request_firmware需要使用的firmware name,hotplug 的handler需要load这个firmwre,并使用上面提到的attribute把fimware copy给kernel。如果找不到文件,就把loading设置为-1.

如果firmware的request查过10s没有完成,kernel就会give up并把fail报告给driver,这个值可以修改(/sys/class/firmware/timeout)。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值