现在开始linux设备模型高级部分。
总线
总线是处理器与一个或多个设备之间的通道。在设备模型中,所有的设备都通过总线相连,甚至是那些内部的虚拟“平台总线”。总线可以互相插入,比如一个USB控制器通常是一个PCI设备。设备模型展示了总线和它们所控制的设备之间的链接。
在linux设备模型中,用bus_type结构表示总线,它的定义包含在<linux/devices.h>中,结构如下:
struct bus_type {const char* name;/*总线类型名称,比如PCI等*/struct module* owner;/*指向模块的指针(如果有), 此模块负责操作这个总线*/struct ksetsubsys;/*与该总线相关的子系统*/struct ksetdrivers;/*总线驱动程序的kset*/struct ksetdevices;/* 挂在该总线的所有设备的kset*/struct klistklist_devices;/*挂接在该总线上的设备链表*/struct klistklist_drivers;/*与该总线相关的设备驱动链表*/struct blocking_notifier_head bus_notifier;struct bus_attribute* bus_attrs; /*总线属性*/struct device_attribute * dev_attrs; /*设备属性,指向为每个加入总线的设备建立的默认属性链表*/struct driver_attribute * drv_attrs; /*驱动程序属性*/struct bus_attribute drivers_autoprobe_attr;/*驱动自动探测属性*/struct bus_attribute drivers_probe_attr;/*驱动探测属性*/int(*match)(struct device * dev, struct device_driver * drv);int(*uevent)(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size);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 (*suspend_late)(struct device * dev, pm_message_t state);int (*resume_early)(struct device * dev);nt (*resume)(struct device * dev);/*处理热插拔、电源管理、探测和移除等事件的方法*/unsigned int drivers_autoprobe:1;};
在更新的内核里,这个结构体变得更简洁了,隐藏了无需驱动编程人员知道的一些成员:
/*in Linux 2.6.26.5*/
structbus_type{constchar*name;structbus_attribute*bus_attrs;structdevice_attribute*dev_attrs;structdriver_attribute*drv_attrs;int(*match)(structdevice*dev,structdevice_driver*drv);int(*uevent)(structdevice*dev,structkobj_uevent_env*env);int(*probe)(structdevice*dev);int(*remove)(structdevice*dev);void(*shutdown)(structdevice*dev);int(*suspend)(structdevice*dev,pm_message_t state);int(*suspend_late)(structdevice*dev,pm_message_t state);int(*resume_early)(structdevice*dev);int(*resume)(structdevice*dev);structbus_type_private*p;};structbus_type_private{structkset subsys;structkset*drivers_kset;structkset*devices_kset;structklist klist_devices;structklist klist_drivers;structblocking_notifier_head bus_notifier;unsignedintdrivers_autoprobe:1;structbus_type*bus;};
总线的注册
(1)声明和初始化bus_type结构体:
struct bus_type ldd_bus_type = {
.name = ldd,
.match = ldd_match,
.uevent = ldd_uevent,
};
只有很少的bus_type成员需要初始化,它们中的大多数成员有设备模型核心所控制。但是,我们必须为总线指定名字以及一些必要的方法。
(2)注册总线
ret = bus_register(&ldd_bus_type);
if(ret)
return ret;
这个调用可能会失败,因此必须检查它的返回值。如果成功,新的总线子系统将被添加到系统中,可以在sys/bus目录下看到它。然后,我们可以像这个总线添加设备。
当要删除一个总线的时候(比如相应的模块被删除),要使用bus_unregister函数:
void bus_unregister(struct bus_tye *bus);
总线方法
在bus_type结构中,定义了许多方法,这些方法允许总线核心作为中间介质在设备核心与单独的程序之间提供服务。
int (*match)(struct device *device,struct device_driver *driver);
当一个总线上的新设备或者新驱动程序被添加时,会一次或多次调用这个函数。如果指定的驱动程序能够处理指定的设备,该函数返回非零值
。必须在总线层上使用该函数,因为那里存在着正确的逻辑。核心内核不知道如何为每个总线类型匹配设备和驱动程序。
int (*uevent)(struct device *device,char **envp,int num_envp,char *buffer,int buffer_size);
在为用户空间产生热插拔事件前,这个方法允许总线添加环境变量。其参数与kset的uevent方法相同。
lddbus的match和uevent方法
match它只是简单地比较了驱动程序和设备的名字:
static int ldd_match(struct device *dev,struct device_driver *driver)
{
return !strncmp(dev->bus_id,driver->name,strlen(driver->name));
}
在调用真是的硬件时,match函数通常对设备提供的硬件ID和驱动所支持的ID做某种类型的比较。
uevent函数
static int ldd_uevent(struct device *device,char **envp,int num_envp,char *buffer,int buffer_size)
{
envp[0] = buffer;
if(snprintf(buffer,buffer_size,”LDDBUS_VERSIB = %s”,Version)>= buffer_size)
return -ENOMEM;
envp[1] = NULL;
return 0;
}
对设备和驱动程序的迭代
如果要编写总线层的代码,可能会发现不得不对注册到总线的所有设备和驱动程序执行某些操作,这可能需要仔细研究嵌入到bus_type结构中的其他数据结构(借助内核提供的函数更好)。
为了操作注册到总线上的每个设备,可使用:
int bus_for_each_dev(struct bus_type *bus,struct device *start,void *data,int (*fn)(struct device *,void *));
int bus_for_each_dev(struct bus_type *bus,struct device *start,void *data,int (*fn)(struct device *,void *));
该函数迭代了在总线上的每个设备,将相关的device结构传递给fn,同时传递data值。如果start是NULL,将从总线上的第一个设备开始迭代;否则将从start后的第一个设备开始迭代。如果fn返回一个非零值,将停止迭代,而这个值也会从bus_for_each_dev返回。
相似的函数也可用于驱动程序的迭代:
int bus_for_each_drv(struct bus_type *bus,struct device_driver *start,void *data,int (*fn)(struct device_driver *,void *));
int bus_for_each_drv(strict bus_type *bus,struct device_driver *start,void *data,int (*fn)(struct device_driver *,void *));
这两个函数在工作期间,都会拥有总线子系统的读取者/写入者信号量。因此,同时使用这两个函数会发生死锁—它们中的任何一个函数都试图获得相同的信号量。修改总线的操作(比如注销设备)也有同样的问题。因此使用bus_for_each函数要小心。
总线属性
几乎在Linux设备模型的每一层都提供了添加属性的函数,总线层也不例外。bus_attribute定义在<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);
};
struct attribute {char *name;/*属性的名字( 在 kobject 的 sysfs 目录中显示)*/struct module *owner;/*指向模块的指针(如果有), 此模块负责实现这个属性*/mode_t mode; /*属性的保护位,modes 的宏定义在 <linux/stat.h>:例如S_IRUGO 为只读属性等等*/}; /*default_attrs 列表中的最后一个元素必须用 0 填充*/
bus_attribute类型也包括了两个用来显示和设置属性的函数。大多数在kobject级以上的设备模型都是按此种方式工作的。
有一个宏,可在编译时刻创建和初始化bus_attribute结构
BUS_ATTR(name,mod,show,store);这个宏声明了一个结构,它将bus_attr_作为给定name的前缀来创建总线的真正名称。
创建总线的任何属性,都需要显示调用bus_create_file函数:
int bus_create_file(struct bus_type *bus,struct bus_attribute *attr);
使用如下函数删除属性:
int bus_remove_file(struct bus_type *bus,struct bus_attribute *attr);
实例代码:
static ssize_t show_bus_version(struct bus_type *bus,char *buf)
{
return snprintf(buf,PAGE_SIZE,”%s/n”,Version);
}
static BUS_ATTR(version,S_IRUGO,show_bus_version,NULL);
在模块的装载阶段创建属性文件:
if(bus_create_file(&ldd_bus_type,&bus_attr_version)){
printk(KERN_NOTICE “Unable to create version attribute/n”);
}
上面的语句会创建一个包含版本号的文件(/sys/bus/ldd/version).
设备
在最底层,linux系统中的每一个设备都用device结构的一个实例来表示:
struct device {struct klistklist_children;struct klist_nodeknode_parent;/* node in sibling list */struct klist_nodeknode_driver;struct klist_nodeknode_bus;struct device*parent;/* 设备的 "父" 设备,该设备所属的设备,通常一个父设备是某种总线或者主控制器. 如果 parent 是 NULL, 则该设备是顶层设备,较少见 */struct kobject kobj;/* 代表该设备并将其连接到结构体系中的 kobject; 注意:作为通用的规则, device->kobj->parent 应等于 device->parent->kobj */charbus_id[BUS_ID_SIZE];/*在总线上唯一标识该设备的字符串;例如: PCI 设备使用标准的 PCI ID 格式, 包含:域, 总线, 设备, 和功能号.*/struct device_type*type;unsignedis_registered:1;unsigneduevent_suppress:1;struct device_attribute uevent_attr;struct device_attribute *devt_attr;struct semaphoresem;/* semaphore to synchronize calls to its driver. */struct bus_type* bus;/*标识该设备连接在何种类型的总线上*/struct device_driver *driver;/*管理该设备的驱动程序*/void*driver_data;/*该设备驱动使用的私有数据成员*/void*platform_data;/* Platform specific data, devicecore doesn't touch it */struct dev_pm_infopower;#ifdef CONFIG_NUMAintnuma_node;/* NUMA node this device is close to */#endifu64*dma_mask;/* dma mask (if dma'able device) */u64coherent_dma_mask;/* Like dma_mask, but for alloc_coherent mappings as not all hardware supports 64 bit addresses for consistent allocations such descriptors. */struct list_headdma_pools;/* dma pools (if dma'ble) */struct dma_coherent_mem*dma_mem; /* internal for coherent mem override *//* arch specific additions */struct dev_archdataarchdata;spinlock_tdevres_lock;struct list_headdevres_head;/* class_device migration path */struct list_headnode;struct class*class;dev_tdevt;/* dev_t, creates the sysfs "dev" */struct attribute_group**groups;/* optional groups */void(*release)(struct device * dev);/*当这个设备的最后引用被删除时,内核调用该方法; 它从被嵌入的 kobject 的 release 方法中调用。所有注册到核心的设备结构必须有一个 release 方法, 否则内核将打印错误信息*/};/*在注册 struct device 前,最少要设置parent, bus_id, bus, 和 release 成员*/
设备注册
注册和注销函数int device_register(struct device *dev);
void device_unregister(struct device *dev);
一个实际的总线是一个设备,因此必须被单独注册。lddbus模块只支持单独的虚拟总线。因此,驱动程序在编译时构造它的设备:
static void bus_release(struct device *dev)
{
printk(KERN_NOTICE “lddbus release/n”);
}
struct device ldd_bus = {
.bus_id = ldd0,
.release = bus_release
};
这是一个顶层总线,因此parent和bus成员是NULL,而release不做任何实际的工作。作为第一个(也是唯一一个)总线,bus_id为ldd0。该总线下面的函数注册:
ret = device_register(&ldd_bus);
if(ret)
printk(KERN_NOTICE “Unable to register ldd0/n”);
完成这个调用后,我们可以在/sys/devices目录中看到它。任何添加到总线的设备都会在/sys/devices/ldd0中显示。
设备属性
sysfs中的设备入口可以有属性,相关的结构是
struct device_attribute {
struct attribute attr;
size_t (*show)(struct device *dev,char *buf);
size_t (*store)(struct device *dev,const char *buf,size_t count);
}
我们可以在编时刻用一下宏来构造这些attribute结构:
DEVICE_ATTR(name,mode,show,store);该结构将dev_attr_作为指定名字的前缀来构造结构的名称。用下面的两个函数实现对属性文件的实际处理:
int device_create_file(struct device *device,struct device_attribute *entry);
void device_remove_file(struct device *device,struct device_attribute *attr);
bus_type结构中的dev_attr成员,指向一个为每个加入总线的设备建立的默认属性链表。
设备结构的嵌入
device结构中包含了设备模型核心用来模拟系统的信息。然而,大多数子系统记录了他们所拥有设备的其他信息。因此单纯用device结构表示的设备时很少见的,而是通常把类似kobject这样的结构内嵌在设备的高层表示之中。底层驱动程序并不知道device结构。
lddbus驱动程序创建了自己的device类型(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);
该结构允许驱动程序为设备提供实际的名字(它与保存在device结构中的总线ID不同),还提供一个指向驱动程序信息的指针。真实设备的结构通常包含供应商的信息、设备模型、设备配置,使用的资源等信息。我们为ldd_device定义了一个宏,以便于将嵌入的device结构指针转化为ldd_device结构指针。
lddbus导出的注册接口:
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->bus_id,ldddev->name,BUS_ID_SIZE);
return device_register(&ldddev->dev);
}
EXPORT_SYMBOL(register_ldd_device);
可以看出,只是简单地填充了嵌入的device结构中的一些成员(单独的驱动程序没有必要知道这些),并且向驱动程序核心注册了设备,我们也可以在这里添加总线专有的设备属性。
sculld驱动程序向它的设备入口添加了一个自己的属性,称之为dev,它只包含了相关的设备编号。这个属性可以由模块装载脚本,或者热插拔子系统使用,以便在设备添加到系统时自动创建设备节点。代码如下:
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);
然后在设备初始化时注册设备,并且用下面的函数创建dev属性
static void sculld_register_dev(struct sculld_dev *dev,int index)
{
sprintf(dev->devname,”sculld%d/n”,index);
dev->ldev.name = dev->devname;
dev->ldev.driver = &sculld_driver;
dev->ldev.dev.driver_data = dev;
register_ldd_device(&dev->ldev);
if(device_create_file(&dev->ldev.dev,&dev_attr_dev))
printk(“cannot create the attribute/n”);
}
设备驱动程序
设备模型跟踪所有系统所知道的设备。进行跟踪的主要原因是让驱动程序核心协调驱动程序与新设备之间的关系。一旦驱动程序是系统中的已知对象,就可能完成大量工作,如设备驱动程序可以导出信息和配置变量,而这些是独立于具体设备的。
结构定义如下:
struct device_driver {
char *name; //是驱动程序的名字,将在sysfs中显示
struct kobject kobj; //kobj是必须的kobject
struct bus_type *bus;//bus是该驱动程序所操作的总线类型
struct list_head devices;//devices是当前驱动程序能操作的设备列表
int (*probe)(struct device *dev);//是用来探测特定设备是否存在的函数(以及这个驱动程序是否能操作它)
int (*remove)(struct device *dev); 当设备从系统中删除时调用remove
void (*shutdown)(struct device *dev); 在关机的时候调用 shutdown函数关闭设备
};
device_driver结构的函数注册和注销:
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);
常用的属性结构:
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_attr),它指向一组为属于该总线的所有设备创建的默认属性。
驱动程序结构的嵌入
对于大多数驱动程序核心结构来说,device_driver结构通常被包含在高层和总线相关的结构中。lddbus因此自己定义了一个ldd_driver结构。
struct ldd_driver {
char *version;
struct module *module;
struct device_driver driver;
struct driver_attribute version_attr;
};
#define to_ldd_driver contiainer_of(drv,struct ldd_driver,driver);
这里,我们要求每个驱动程序提供自己当前的软件版本号,lddbus为它所知道的每一个驱动程序导出这个版本字符串。,注册程序:
int register_ldd_driver(struct ldd_driver *driver)
{
int ret;
driver->driver.bus = &ldd_bus_type;//对driver中的bus成员复制,bus_type(总线初始化)
ret = driver_register(&driver->driver);
if(ret)
return ret;
driver->version_attr.attr.name = “version”;
driver->version_attr.attr.owner = driver->module; //注意这里将owner设置为驱动程序模块可防止用户空间打开属性文件时卸载模块,可以保证模块不会在不适当的时候被卸载。
driver->version_attr.attr.mod = S_IRUGO;
driver->version_attr.show =show_version;
driver->version_attr.store = NULL;
if(driver_create_file(&driver->driver,&driver->version_attr))
printk(“cannot create this attribute/n”);
}
该函数的前半部分只是简单地向核心注册了低层的device_driver结构,其余部分设置了版本号属性。由于该属性是在运行时建立的,因此不能使用DRIVER_ATTR宏,这样,必须手工填写driver_attribute结构。注意要将owner属性设置为module(驱动程序模块),而不是lddbus模块。
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);
}
sculld用下面的代码创建自己的ldd_driver结构:
static struct ldd_driver sculld_driver = {
.version = “..”,
.owner = THIS_MODULE,
.driver = {
.name = “sculld”,
},
};
一个register_ldd_driver调用将它添加到系统中。可以在sysfs 中看到驱动版本信息。
类
类是一个设备的高层视图, 它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能, 而不用关心设备是如何连接和工作的。类成员通常由上层代码所控制, 而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。
几乎所有的类都显示在 /sys/class 目录中。出于历史的原因,有一个例外:块设备显示在 /sys/block目录中。在许多情况, 类子系统是向用户空间导出信息的最好方法。当类子系统创建一个类时, 它将完全拥有这个类,根本不用担心哪个模块拥有那些属性,而且信息的表示也比较友好。
为了管理类,驱动程序核心导出了一些接口,其目的之一是提供包含设备号的属性以便自动创建设备节点,所以udev的使用离不开类。
class_simple接口
现在的这个接口已经删除了,这里只列出了几个函数了接下。
1:创建类本身
struct class_simple *class_simple_create(struct module *owner,char *name);
该函数使用给定的名字创建类。该函数应该始终检查他的返回值。
2:销毁一个简单类
void class_simple_destory(struct class_simple *cs);
创建一个简单类的真实目的是为它添加设备。我们可使用下面的函数达到这一目的:
struct class_device *class_simple_device_add(struct class_simple *cs,dev_t devnum,struct device *device,comst char *fmt);
cs是前面创建的类,devnum是创建的设备号,device表示这个设备的device结构。该调用向包含设备号属性(dev)的类中添加一个入口。如果device参数不是NULL,一个符号链接(称为device)将指向sys/devices下的设备入口。
要向设备入口添加其他属性,可使用class_device_create_file.
3:在热插拔设备时,类会产生热插拔事件。如果驱动程序需要为用户空间处理程序添加环境变量的话,可以使用下面的代码设置热插拔回调函数。
int class_simple_set_uevent(struct class_simple *cs,int (*uevent)(struct class_device *dev,char **envp,int num_envp,char *buffer,int buffer_size));
4:当设备拔出时,使用下面的函数来删除设备入口:
void class_simple_device_remove(dev_t dev); 注意这里并不需要class_simple_device_add返回的class_device结构,提供设备号(应该是唯一的)就足够了。
完整的类接口
管理类的接口:用class结构的一个实例来定义类
/** device classes*/struct class {const char* name;/*每个类需要一个唯一的名字, 它将显示在 /sys/class 中*/struct module* owner;struct ksetsubsys;struct list_headchildren;struct list_headdevices;struct list_headinterfaces;struct ksetclass_dirs;struct semaphoresem;/* locks both the children and interfaces lists */struct class_attribute* class_attrs;/* 指向类属性的指针(以NULL结尾) */struct class_device_attribute* class_dev_attrs;/* 指向类中每个设备的一组默认属性的指针 */struct device_attribute* dev_attrs;int(*uevent)(struct class_device *dev, char **envp, int num_envp, char *buffer, int buffer_size);/* 类热插拔产生时添加环境变量的函数 */int(*dev_uevent)(struct device *dev, char **envp, int num_envp,char *buffer, int buffer_size);/* 类中的设备热插拔时添加环境变量的函数 */void(*release)(struct class_device *dev);/* 把设备从类中删除的函数 */void(*class_release)(struct class *class);/* 删除类本身的函数 */void(*dev_release)(struct device *dev);int(*suspend)(struct device *, pm_message_t state);int(*resume)(struct device *);};/*类注册函数:*/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);
在更新的内核里,这个结构体变得简洁了,删除了一些成员:
/*in Linux 2.6.26.5*//** device classes*/structclass{constchar*name;structmodule*owner;structksetsubsys;structlist_headdevices;structlist_headinterfaces;structksetclass_dirs;structsemaphoresem;/* locks children, devices, interfaces */structclass_attribute*class_attrs;structdevice_attribute*dev_attrs;int(*dev_uevent)(structdevice*dev,structkobj_uevent_env*env);void(*class_release)(structclass*class);void(*dev_release)(structdevice*dev);int(*suspend)(structdevice*dev,pm_message_t state);int(*resume)(structdevice*dev);};
类设备
类存在的真正目的是,给作为类成员的各个设备提供一个容器。用class_device结构表示类的成员:
struct class_device {struct list_headnode;/*for internal use by the driver core only*/struct kobjectkobj;/*for internal use by the driver core only*/struct class* class;/* 指向该设备所属的类,必须*/dev_tdevt;/* dev_t, creates the sysfs "dev" ,for internal use by the driver core only*/struct class_device_attribute *devt_attr;/*for internal use by the driver core only*/struct class_device_attribute uevent_attr;struct device* dev;/* 指向此设备相关的 device 结构体,可选。若不为NULL,应是一个从类入口到/sys/devices 下相应入口的符号连接,以便用户空间查找设备入口*/void* class_data;/* 私有数据指针 */struct class_device*parent;/* parent of this child device, if there is one */struct attribute_group ** groups;/* optional groups */void(*release)(struct class_device *dev);int(*uevent)(struct class_device *dev, char **envp, int num_envp, char *buffer, int buffer_size);charclass_id[BUS_ID_SIZE];/* 此类中的唯一的名字 */};/*类设备注册函数:*/int class_device_register(struct class_device *cd);void class_device_unregister(struct class_device *cd);/*重命名一个已经注册的类设备入口:*/int class_device_rename(struct class_device *cd, char *new_name); /*类设备入口属性:*/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); /*创建和删除除struct class中设备默认属性外的属性*/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);
类接口
类子系统具有一个在linux设备模型其他部分找不到的其他概念。该机制被称为”接口“。可以把它理解为一种设备加入或则离开类时获得信息的触发机制。
一个接口由下面的结构表示:
struct class_interface {
struct class *class;
int (*add)(struct class_device *cd);
void (*remove)(struct class_device *cd);
};
可以用下面的函数注册和注销接口:
struct class_interface_register(struct class_interface *intf);
struct class_interface_unregister(struct class_iterface *intf);
无论何时把一个类设备添加到class_interface结构所指定的类中,都将调用调用(*add)函数,该函数能为设备作一些其他的必要设置,通常这些设置表现为添加更多的属性,但也能完成其他一些工作。在从类中删除设备时,调用remove函数来做必要的清理工作。
可以为一个类注册多个接口。
辽远或偏僻的地方,而会常常想起这一次的旅行,