您的位置:首页 > 运维架构 > Linux

Linux设备驱动程序学习之设备模型一

2010-06-25 15:14 260 查看
Linux设备驱动程序学习笔记系列文章原作者是:Tekkaman Ninja,他博客地址:


http://blog.chinaunix.net/u1/34474/showart_404278.html
在此向Tekkaman
Ninja表示感谢,写出这么好的文章,使我少走了很多弯路。

以《LDD3》的说法:Linux设备模型这部分内容可以认为是高级教材,对于多数程序作者来说是不必要的。但是我个人
认为:对于一个嵌入式Linux的底层程序员来说,这部分内容是很重要的。

我学习的ARM9为例,有很多总线(如SPI、IIC、IIS等等)在Linux下已经被编写成了子系统,无需自己写驱动;而这些总线又不像PCI、
USB等在《LDD3》上有教程,有时还要自己研究它的子系统构架,甚至要自己添加一个新的总线类型。
对于这方面的学习,我推荐几个网页,这些也是我这部分文章的参考资料:
(1)《 Linux

些事儿 之 我是Sysfs
》来源于复旦和交大三个牛人的
Linux技术博客:http://blog.csdn.net/fudan_abc



(复旦_abc)他们还分析了很多Linux的驱
动,值得珍藏!

(2)《linux设备模型详解》也是一个牛人的博客文章,博客网址:http://hi.baidu.com/csdeny/blog


(3)《s3c2410设备的注册》是一篇关于2410中linux内核实现设备模型的不可多得的好资料。网址:http://blog.chinaunix.net/u1/41638/showart_438078.html


(4)luofuchong的博客
,此人分析了一些2410中的Linux子系统(如SPI,input等),实力不凡,值得关注。网址:http://www.cnitblog.com/luofuchong/

在这部分的学习中,将会先研究linux设备模型的每个元素,最后将其一步一步整合,至底向上地分析。一开始会比较摸不
着头脑,到了整合阶段就柳暗花明了。我之所以没有先介绍整体,再分析每个部分是因为如果不对每个元素做认真分析,看了整体也会云里雾里(我试过了,恕小生
愚钝)。所以一开始要耐着性子看,到整合阶段就会豁然开朗。


Linux设备模型的目的是:为内核建立
起一个统一的设备模型,从而有一个对系统结构的一般性抽象描述。

现在内核使用设备模型支持多种不同的任务:

电源管理和系统关机
:这些需要对系统结构的理解,设备模型使OS能以正确顺序遍历系统硬件。
与用户空间的通讯

:sysfs 虚拟文件系统的实现与设备模型的紧密相关, 并向外界展示它所表述的结构。向用户空间提供系统信息、改变操作参数的接口正越来越多地通过
sysfs , 也就是设备模型来完成。
热插拔设备

设备类型:设备模型包括了将设备分类的机制,在一个更高的功能层上描述这些设备, 并使设备对用户空间可见。
对象生命周期
:设备模型的
实现需要创建一系列机制来处理对象的生命周期、对象间的关系和对象在用户空间的表示。
Linux 设备模型是一个复杂的数据结构。但对模型的大部分来说, Linux 设备模型代码会处理好这些关系,
而不是把他们强加于驱动作者。模型隐藏于交互的背后,与设备模型的直接交互通常由总线级的逻辑和其他的内核子系统处理。所以许多驱动作者可完全忽略设备模
型, 并相信设备模型能处理好他所负责的事。

在此之前请先了解一下
sysfs
,请看 Linux

那些事儿之我是Sysfs(1)sysfs

初探

我就不在这里废话了!这里还建议先看看 sysfs 的内核文档/Documentation/filesystems/

sysfs.txt

我将其翻译好做成PDF,下载地址:http://blogimg.chinaunix.net/blog/upfile2/071229162826.pdf




如有错误欢迎指正!


Kobject、
Kset 和 Subsystem



Kobjects


kobject是一种数据结构,定义在 <linux/kobject.h>


struct
 kobject {

    const
 char
    *
 k_name;
/*kobject 的名字数组(sysfs 
入口使用的名字)指针;如果名字数组大小小于KOBJ_NAME_LEN,它指向本数组的name,否则指向另外分配的一个名字数组空间 */

    char
            name[
KOBJ_NAME_LEN]
;
/*kobject 
的名字数组,若名字数组大小不小于KOBJ_NAME_LEN,只储存前KOBJ_NAME_LEN个字符*/

    struct
 kref        kref;
/*kobject 的引用计数*/

    struct
 list_head    entry;
/*kobject 
之间的双向链表,与所属的kset形成环形链表
*/

    struct
 kobject        *
 parent;
/*在sysfs分层结构中定
位对象,指向上一级kset中的struct kobject kobj*/

    struct
 kset        *
 kset;
/*指向所属的kset*/

    struct
 kobj_type    *
 ktype;
/*负责对该kobject类型
进行跟踪的struct kobj_type的指针*/

    struct
 dentry        *
 dentry;
/*sysfs文件系统中与该对象对应的文件节点路径指针*/

    wait_queue_head_t    poll;
/*等待队列头*/

}
;


kobject 是组成设备模型的基本结构,初始它只被作为一个简单的引用计数, 但随时间的推移,其任务越来越多。现在kobject
所处理的任务和支持代码包括:

对象的引用计数

:跟踪对象生命周期的一种方法是使用引用计数。当没有内核代码持有该对象的引用时, 该对象将结束自己的有效生命期并可被删除。
sysfs 表述
:在
sysfs 中出现的每个对象都对应一个 kobject, 它和内核交互来创建它的可见表述。
数据结构关联
:整体来看,
设备模型是一个极端复杂的数据结构,通过其间的大量链接而构成一个多层次的体系结构。kobject 实现了该结构并将其聚合在一起。
热插拔事件处理

:kobject 子系统将产生的热插拔事件通知用户空间。
一个kobject对自身并不感兴趣,它存在的意义在于把高级对象连接到设备模型上。因此内核代码很少(甚至不知道)创
建一个单独的 kobject;而kobject 被用来控制对大型域(domain)相关对象的访问,所以kobject
被嵌入到其他结构中。kobject 可被看作一个最顶层的基类,其他类都它的派生产物。 kobject
实现了一系列方法,对自身并没有特殊作用,而对其他对象却非常有效。
对于给定的kobject指针,可使用container_of

得到包含它的结构体的指针。
kobject 初始化

kobject的初始化较为复杂,但是必须的步骤

下:

(1)将整个kobject清零,通常使用memset函数。

(2)调用kobject_init()函数,设置结构内部一些成员。所做的一件事情是设置kobject的引用计数为1。具体的源码如下:

void
 kobject_init(
struct
 
kobject *
 kobj)/*in
 kobject.c
*/

{

    if
 (
!
kobj)

        return
;

    kref_init(
&
kobj-
>
kref)
;
/*设置引用计数为1*/

    INIT_LIST_HEAD(
&
kobj-
>
entry)
;
/*初始化kobject 之间的双向链表*/

    init_waitqueue_head(
&
kobj-
>
poll)
;
/*初始化等待队列头*/

    kobj-
>
kset =
 
kset_get(
kobj-
>
kset)
;
/*增加所属kset的引用计数(若没有所属的kset,则返回NULL)*/

}

void
 kref_init(
struct
 kref *
kref)/*in kobject.c
*/

{

    atomic_set(
&
kref-
>
refcount,
1)
;

    smp_mb(
)
;

}

static
 inline
 struct
 kset *
 to_kset(
struct
 kobject *
 kobj)/*in
 kobject.h
*/

{

    return
 kobj ?
 container_of(
kobj,
struct
 kset,
kobj)
 :
 NULL
;

}

static
 inline
 struct
 kset *
 kset_get(
struct
 kset *
 k)/*in kobject.h
*/

{

    return
 k ?
 to_kset(
kobject_get(
&
k-
>
kobj)
)
 :
 NULL
;/*增加引用计数*/

}


(3)设置kobject的名字

int
 kobject_set_name(
struct
 
kobject *
 kobj,
 const
 char
 *
 fmt,
 .
.
.
)
;


(4)直接或间接设置其它成员:ktype、kset和parent。 (重要)

对引用计数的操作

kobject
的一个重要函数是为包含它的结构设置引用计数。只要对这个对象的引用计数存在, 这个对象( 和支持它的代码) 必须继续存在。底层控制 kobject
的引用计数的函数有:
struct
 kobject *
kobject_get(
struct
 kobject *
kobj)
;
/*若成功,递增 kobject 的引用计数并返回一个指向 kobject 
的指针,否则返回 NULL。必须始终测试返回值以免产生竞态*/

void
 kobject_put(
struct
 kobject *
kobj)
;
/*递减引用计数并在可能的情况下释放这个对象*/


注意:kobject _init 设置这个引用计数为 1,因此创建一个
kobject时, 当这个初始化引用不再需要,应当确保采取 kobject_put 调用。同理:struct cdev 的引用计数实现如下:


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;

}


创建一个对 cdev 结构的引用时,还需要创建包含它的模块的引用。因此,
cdev_get 使用 try_module_get 来试图递增这个模块的使引用计数。如果这个操作成功, kobject_get 被同样用来递增
kobject 的引用计数。kobject_get 可能失败, 因此这个代码检查 kobject_get
的返回值,如果调用失败,则释放它的对模块的引用计数。

release 函数和 kobject 类型

引用计数不由创建 kobject 的代码直接控制,当 kobject
的最后引用计数消失时,必须异步通知,而后kobject中ktype所指向的kobj_type结构体包含的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函数, 并且这个
kobject 必须在release函数被调用前保持不变( 稳定状态 ) 。这样,每一个 kobject 需要有一个关联的 kobj_type
结构,指向这个结构的指针能在 2 个不同的地方找到:

(1)kobject
结构自身包含一个成员(ktype)指向kobj_type ;

(2)如果这个 kobject 是一个 kset 的成员, kset
会提供kobj_type 指针。


struct
 kset {

    struct
 kobj_type    *
 ktype; /*
指向该kset对象类型的指针*/

    struct
 list_head    list

;/*用于连接该kset中所有
kobject以形成环形链表的链表头*/

    spinlock_t        list_lock;/*
用于避免竞态的自旋锁*/

    struct
 kobject        kobj; /*嵌入的kobject*/

    struct
 kset_uevent_ops    *
 uevent_ops
;


/*原有的struct kset_hotplug_ops * 
hotplug_ops;已经不存在,被kset_uevent_ops 结构体替换,在热插拔操作中会介绍
*/

}
;


以下宏用以查找指定kobject的kobj_type 指针:
struct
 kobj_type *
get_ktype(
struct
 kobject *
kobj)
;


这个函数其实就是从以上提到的这两个地方返回kobj_type指针,源码如
下:
static
 inline
 struct
 kobj_type *
 get_ktype(
struct
 kobject *
 k)

{

    if
 (
k-
>
kset &
&
 k-
>
kset-
>
ktype)

        return
 k-
>
kset-
>
ktype;

    else
 

        return
 k-
>
ktype;

}


kobject
层次结构、kset

和子系统


内核通常用kobject 结构将各个对象连接起来组成一个分层的结构体系,与模型化的子系统相匹配。有 2 个独立的机制用于连接: parent 指针和 kset
。

parent
 是指向另外一个kobject 结构(分层结构中上一层的节点)的指针,主要用途是在 sysfs 层次中定位对象.


kset

kset 象 kobj_type 结构的扩展; 一个 kset 是嵌入到相同类型结构的
kobject 的集合。但 struct
kobj_type 关注的是对象的类型,而struct kset 关心的是对象的聚合和集合,其主要功能是包容,可认为是kobjects
的顶层容器类。每个 kset 在内部包含自己的 kobject, 并可以用多种处理kobject 的方法处理kset。 ksets 总是在
sysfs 中出现; 一旦设置了 kset 并把它添加到系统中, 将在 sysfs 中创建一个目录;kobjects 不必在 sysfs
中表示, 但kset中的每一个 kobject 成员都在sysfs中得到表述。

增加 kobject 到 kset 中去,通常是在kobject
创建时完成,其过程分为2步:

(1)完成kobject的初始化,特别注意mane和parent和初始化。

(2)把kobject 的 kset 成员指向目标kset。

(3)将kobject 传递给下面的函数:

int
 kobject_add(
struct
 
kobject *
kobj)
;
 /*函数可能失败(返回一个负错误
码),程序应作出相应地反应*/


内核提供了一个组合函数:

extern
 int
 kobject_register(
struct
 kobject *
kobj)
;
 /*仅仅是一个 kobject_init 和 kobject_add 的结合,其他成员的初始化必须在之前手动完成
*/


当把一个kobject从kset中删除以清除引用时使用:

void
 kobject_del(
struct
 
kobject *
kobj)
;
 /*是 kobject_del 和
 kobject_put 的结合*/


kset 在一个标准的内核链表中保存了它的子节点,在大部分情况下, 被包含的 kobjects 在它们的 parent 成员中保存指向
kset内嵌的 kobject的指针,关系如下:



图表中的所有的被包含的 kobjects 实际上被嵌入在一些其他类型中, 甚至可能其他的 kset。

kset 上的操作

ksets 有类似于kobjects初始化和设置接口:

void
 kset_init(
struct
 kset *
kset)
;

int
 kset_add(
struct
 kset *
kset)
;

int
 
kset_register(
struct
 kset *
kset)
;

void
 kset_unregister(
struct
 kset *
kset)
;

/*管理 ksets 的引用计数:*/

struct
 kset *
kset_get(
struct
 kset *
kset)
;

void
 kset_put(
struct
 kset *
kset)
;

/* kset 也有一个名字,存储于嵌入的 kobject,因此设置它的名字用:*/

kobject_set_name(
&
my_set-
>
kobj,
 "The 
name"
)
;


ksets 还有一个指针指向 kobj_type 结构来描述它包含的 kobject,这个类型优先于 kobject 自身中的 ktype
。因此在典型的应用中, 在 struct kobject 中的 ktype 成员被设为 NULL, 而 kset
中的ktype是实际被使用的。

在新的内核里, kset 不再包含一个子系统指针struct subsystem * subsys,
而且subsystem已经被kset取代。

子系统

子系统是对整个内核中一些高级部分的表述。子系统通常(但不一定)出现在 sysfs分层结构中的顶层,内核子系统包括
block_subsys(/sys/block 块设备)、 devices_subsys(/sys/devices
核心设备层)以及内核已知的用于各种总线的特定子系统。

对于新的内核已经不再有
subsystem
数据结构
了,用kset代替了。每个 kset 必须属于一个子系统,子系统成员帮助内核在分层结构中定位 kset 。
/*子系统通常用以下的宏声明:*/

decl_subsys(
name,
 struct
 
kobj_type *
type,
 struct
 kset_uevent_ops *
 uevent_ops)
;

/*子系统的操作函数:*/

void
 subsystem_init(
struct
 kset *
s)
;

int
 subsystem_register(
struct
 kset *
s)
;

void
 subsystem_unregister(
struct
 kset *
s)
;

struct
 
subsystem *
subsys_get(
struct
 kset *
s)

void
 subsys_put(
struct
 kset *
s)
;


/*这些函数基本上是kset操作
函数的包装,以实现子系统的操作*/


底层
sysfs操作


kobject 是在 sysfs 虚拟文件系统后的机制。对每个在
sysfs 中的目录
, 在内核中都会有一个
kobject 与之对应。每个 kobject 都输出一个或多个属性, 它在 kobject 的 sysfs 目录中以文件
的形式出现, 其中的内容由内核产生。 <linux/sysfs.h>
包含 sysfs
的工作代码。

在 sysfs 中创建kobject的入口是kobject_add的工作的一部分,只要调用
kobject_add 就会在sysfs 中显示,还有些知识值得记住:

(1)kobjects 的 sysfs 入口始终为目录, kobject_add 的调用将在sysfs
中创建一个目录,这个目录包含一个或多个属性(文件);

(2)分配给 kobject 的名字( 用 kobject_set_name ) 是
sysfs 中的目录名,出现在 sysfs 层次的相同部分的 kobjects 必须有唯一的名字. 分配给 kobjects
的名字也应当是合法的文件名字: 它们不能包含非法字符(如:斜线)且不推荐使用空白。

(3)sysfs 入口位置对应 kobject 的
parent 指针。若 parent 是 NULL ,则它被设置为嵌入到新 kobject 的 kset 中的 kobject;若 parent
和 kset 都是 NULL, 则sysfs 入口目录在顶层,通常不推荐。

默认
属性


当创建kobject 时, 每个 kobject
都被给定一系列默认属性。这些属性保存在 kobj_type 结构中:
struct
 kobj_type {

 void
 (
*
release)
(
struct
 kobject *
)
;

 struct
 
sysfs_ops *
sysfs_ops;
/*提供实现以下属性的方法*/

 struct
 attribute *
*
default_attrs;
 /*用于保存类
型属性列表(指针的指针) */

}
;
 

struct
 attribute {

 char
 *
name;
/*属性的名字( 在 
kobject 的 sysfs 目录中显示)*/

 struct
 module *
owner;
/*指向模块的指针(如果有), 此模块负责实现这个属性*/

 mode_t mode;
 /*属性的保护位,modes 的宏定义在 <linux/stat.h>:例如S_IRUGO 为只读属性等等*/

}
;
 /*default_attrs 
列表中的最后一个元素必须用 0 填充*/


sysfs 读写这些属性是由 kobj_type->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)
;

}
;


当用户空间读取一个属性时,内核会使用指向 kobject 的指针(kobj)和正确的属性结构(*attr)来调用show
方法,该方法将给定属性值编码进缓冲(buffer)(注意不要越界( PAGE_SIZE 字节)), 并返回实际数据长度。sysfs
的约定要求每个属性应当包含一个单个人眼可读值; 若返回大量信息,需将它分为多个属性.

也可对所有 kobject 关联的属性使用同一个 show 方法,用传递到函数的 attr 指针来判断所请求的属性。有的 show
方法包含对属性名字的检查。有的show 方法会将属性结构嵌入另一个结构, 这个结构包含需要返回属性值的信息,这时可用container_of
获得上层结构的指针以返回属性值的信息。

store 方法将存在缓冲(buffer)的数据( size 为数据的长度,不能超过 PAGE_SIZE
)解码并保存新值到属性(*attr), 返回实际解码的字节数。store
方法只在拥有属性的写权限时才能被调用。此时注意:接收来自用户空间的数据一定要验证其合法性。如果到数据不匹配, 返回一个负的错误值。

非默认属


虽然 kobject 类型的 default_attrs 成员描述了所有的 kobject 会拥有的属性,倘若想添加新属性到
kobject 的 sysfs 目录属性只需简单地填充一个attribute结构并传递到以下函数:

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

/*若成功,文件以
attribute结构中的名字创建并返回 0; 否则, 返回负错误码*/

/*注意:内核会调用相同的 show() 和 store() 
函数来实现对新属性的操作,所以在添加一个新非默认属性前,应采取必要的步骤确保这些函数知道如何实现这个属性*/


若要删除属性,调用:

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

/*调用后, 
这个属性不再出现在 kobject 的 sysfs 
入口。若一个用户空间进程可能有一个打开的那个属性的文件描述符,在这个属性已经被删除后,show 和 store 仍然可能被调用*/


二进制属性

sysfs
通常要求所有属性都只包含一个可读文本格式的值,很少需要创建能够处理大量二进制数据的属性。但当在用户空间和设备间传递不可改变的数据时(如上传固件到设备
)就需要这个特性。二进制属性使用一个
bin_attribute 结构来描述:

struct
 bin_attribute {

    struct
 attribute    attr;
/*属性结构体*/

    size_t
            size;
/*这个二进制属性的最大大小(若无最大值则为0)*/

    void
            *
private
;

    ssize_t (
*
read
)
(
struct
 
kobject *
,
 char
 *
,
 loff_t,
 size_t
)
;

    ssize_t
 (
*
write
)
(
struct
 
kobject *
,
 char
 *
,
 loff_t,
 size_t
)
;

/*read 和 write 
方法类似字符驱动的读写方法;,在一次加载中可被多次调用,每次调用最大操作一页数据,且必须能以其他方式判断操作数据的末尾*/

    int
 (
*
mmap)
(
struct
 
kobject *
,
 struct
 bin_attribute *
attr,

         struct
 vm_area_struct *
vma)
;

}
;

/*二进制属性必须显式创建,不能以默认属性被创建,创建一个二进制属性调用:*
/

int
 
sysfs_create_bin_file(
struct
 kobject *
kobj,
 struct
 bin_attribute *
attr)
;

/*删除二进制属性调用:*/

int
 sysfs_remove_bin_file(
struct
 kobject *
kobj,
 struct
 bin_attribute *
attr)
;


符号链接

sysfs 文件系统具有树型结构, 反映
kobject之间的组织层次关系。为了表示驱动程序和所管理的设备间的关系,需要额外的指针,其在 sysfs 中通过符号链接实现。
/*在 sysfs 创建一个符号链接:*/

int
 sysfs_create_link(
struct
 
kobject *
kobj,
 struct
 kobject *
target,
 char
 *
name)
;

/*函数创建一个链接
(name)指向target的 sysfs 入口作为 kobj 的一个属性,是一个相对连接,与它在sysfs 系统中的位置无关*/

/*删除符号连接调用:*/

void
 sysfs_remove_link(
struct
 kobject *
kobj,
 char
 *
name)
;


热插拔事件
产生


一个热插拔事件是一个从内核空间发送到用户空间的通知, 表明系统配置已经改变. 无论 kobject
被创建或删除,都会产生这种事件。热插拔事件会导致对 /sbin/hotplug 的调用, 它通过加载驱动程序, 创建设备节点,
挂载分区或其他正确动作响应事件。

热插拔事件的实际控制是通过一套存储于 kset_uevent_ops (《LDD3》中介绍的struct kset_hotplug_ops *
hotplug_ops;在2.6.22.2中已经被kset_uevent_ops 结构体替换

结构的方法完成:

struct
 kset_uevent_ops {

    int
 (
*
filter)
(
struct
 kset *
kset,
 struct
 kobject *
kobj)
;

    const
 char
 *
(
*
name)
(
struct
 kset *
kset,
 struct
 kobject *
kobj)
;

    int
 (
*
uevent)
(
struct
 kset *
kset,
 struct
 
kobject *
kobj,
 char
 *
*
envp,

            int
 num_envp,
 char
 *
buffer,
 int
 buffer_size)
;

}
;


可以在 kset 结构的uevent_ops 成员中找到指向kset_uevent_ops结构的指针。

若在 kobject 中不包含指定的 kset , 内核将通过 parent 指针在分层结构中进行搜索,直到发现一个包含有kset的
kobject ; 接着使用这个 kset 的热插拔操作。

kset_uevent_ops 结构中的三个方法作用如下:

(1) filter 函数让 kset
代码决定是否将事件传递给用户空间。如果 filter 返回 0,将不产生事件。以磁盘的 filter
函数为例,它只允许kobject产生磁盘和分区的事件,源码如下:
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)
)
;

}


(2) 当调用用户空间的热插拔程序时,相关子系统的名字将作为唯一的参数传递给它。name
函数负责返回合适的字符串传递给用户空间的热插拔程序。

(3)热插拔脚本想得到的任何其他参数都通过环境变量传递。uevent 函数的作用是在调用热插拔脚本之前将参数添加到环境变量中。函数原型:

int
 (
*
uevent)
(
struct
 kset *
kset,
 struct
 kobject *
kobj,
 /*产生事件的目标对象*/

 char
 *
*
envp,
/*一个保存其他环境变量定义(通常为 NAME=value 的格式)的数组*/

 int
 num_envp,
 /*环境变量数
组中包含的变量个数(数组大小)*/

 char
 *
buffer,
 int
 buffer_size/*环境变量被编码后放入的缓冲区的指针和字
节数(大小)*/

/*若需要添加任何环境变量到
 envp, 必须在最后的添加项后加一个 NULL 入口,使内核知道数组的结尾*/

        )
;

/*返回值正常应当是 
0,若返回非零值将终止热插拔事件的产生*/


热插拔事件的产生通常是由在总线驱动程序层的逻辑所控制。

以上是
Linux设备模型的底层原理简介,具体的细节应该参阅内核源码和《ULK3》。


文章的例子和实验使用《LDD3》所配的lddbus模块(稍作修改)。

总线


总线是处理器和一个或多个设备之间的通道,在设备模型中, 所有的设备都通过总线相连, 甚至是内部的虚拟"platform
"总线。总线可以相互插入。设备模型展示了总线和它们所
控制的设备之间的实际连接。

在 Linux 设备模型中, 总线由 bus_type 结构表示, 定义在 <linux/device.h>


struct
 bus_type {

    const
 char
        *
 name;
/*总线类型名称*/

    struct
 module        *
 owner;
/*指向模块的指针(如果有),
 此模块负责操作这个总线*/

    struct
 kset        subsys;
/*与该总线相关的子系统*/

    struct
 kset        drivers;
/*总线驱动程序的kset*/

    struct
 kset        devices;
/* 挂在该总线的所有设备的kset*/

    struct
 klist        klist_devices;
/*与该总线相关的驱动程序链表*/

    struct
 klist        klist_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;

}
;


总线的注册和
删除


总线的主要注册步骤:

(1)申明和初始化 bus_type 结构体。只有很少的 bus_type
成员需要初始化,大部分都由设备模型核心控制。但必须为总线指定名字及一些必要的方法。例如:

struct
 bus_type ldd_bus_type =
 {

    .
name =
 "ldd"
,

    .
match =
 ldd_match,

    .
uevent =
 ldd_uevent,

}
;


(2)调用bus_register函数注册总线。

int
 bus_register(
struct
 
bus_type *
 bus)


调用可能失败, 所以必须始终检查返回值。若成功,新的总线子系统将被添加进系统,并可在
sysfs 的 /sys/bus 下看到。之后可以向总线添加设备。

例如:
ret =
 bus_register(
&
ldd_bus_type)
;

if
 (
ret)

 return
 
ret;


当必须从系统中删除一个总线时, 调用:



void
 bus_unregister(
struct
 
bus_type *
bus)
;


总线方法

在 bus_type 结构中定义了许多方法,它们允许总线核心作为设备核心和单独的驱动程序之间提供服务的中介,主要介绍以下两个方法:

int
 (
*
match)
(
struct
 device *
 dev,
 struct
 device_driver *
 drv)
;

/*当一个新设备或者驱动被添加到这个总线时,这个方法会被调用一次或多次,若指定
的驱动程序能够处理指定的设备,则返回非零值。必须在总线层使用这个函数, 
因为那里存在正确的逻辑,核心内核不知道如何为每个总线类型匹配设备和驱动程序*/

int
 (
*
uevent)
(
struct
 device
 *
dev,
 char
 *
*
envp,
 int
 num_envp,
 char
 *
buffer,
 int
 buffer_size)
;

/*在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量(参数和 kset 的uevent方法相同)*/


lddbus的match和uevent方法:


static
 int
 ldd_match(
struct
 device *
dev,
 struct
 device_driver *
driver)

{

 return
 !
strncmp
(
dev-
>
bus_id,
 driver-
>
name,
 strlen
(
driver-
>
name)
)
;

}
/*仅简单比较驱动和设备的名字*/

/*当涉及实际硬件时, match 函数常常对设备提供的硬件 ID 
和驱动所支持的 ID 做比较*/

static
 int
 ldd_uevent(
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;

}
/*在环境变量中加入 lddbus 源码的当前版本号*/


对设备和驱动
的迭代


若要编写总线层代码,
可能不得不对所有已经注册到总线的设备或驱动进行一些操作,这可能需要仔细研究嵌入到 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_drv(
struct
 bus_type *
bus,
 struct
 
device_driver *
start,
 void
 *
data,
 int
 (
*
fn)
(
struct
 device_driver *
,
 void
 *
)
)
;

/*这两个函数迭代总线上的每个设备或驱动程序, 将关联的 device 或 device_driver 
传递给 fn, 同时传递 data 值。若 start 为 NULL, 则从第一个设备开始; 否则从 start 之后的第一个设备开始。若 fn
 返回非零值, 迭代停止并且那个值从 bus_for_each_dev 或bus_for_each_drv 返回。*/


总线属性


几乎 Linux 设备模型中的每一层都提供添加属性的函数, 总线层也不例外。bus_attribute 类型定义在 <linux/device.h>
如下:

struct
 bus_attribute {

    struct
 attribute    attr;

    ssize_t
 (
*
show)
(
struct
 bus_type *
,
 char
 *
 buf)
;

    ssize_t (
*
store)
(
struct
 bus_type *
,
 const
 char
 *
 buf,
 size_t
 count
)
;

}
;


可以看出struct


bus_attribute 和struct

attribute
很相似,其实大部分在 kobject 级上的设备模型层都是以这种方式工作。

内核提供了一个宏在编译时创建和初始化 bus_attribute 结构:

BUS_ATTR(
_name,
_mode,
_show,
_store)
/*这个宏声明一个结构, 将
 bus_attr_ 作为给定 _name 的前缀来创建总线的真正名称*/

/*总线的属性必须显式调用 bus_create_file 来创建:*/

int
 bus_create_file(
struct
 bus_type *
bus,
 struct
 bus_attribute *
attr)
;
 

/*删除总线的属性
调用:*/

void
 
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"
)
;

/*这个调用创建一个包含 lddbus 
代码的版本号的属性文件(/sys/bus/ldd/version)*/


设备



在最底层, Linux 系统中的每个设备由一个 struct device 代表:

struct
 device {

    struct
 klist        klist_children;

    struct
 klist_node    knode_parent;
   /* node in sibling list */

    struct
 klist_node    knode_driver;

    struct
 klist_node    knode_bus;

    struct
 device        *
parent;
/* 设备的 "父" 
设备,该设备所属的设备,通常一个父设备是某种总线或者主控制器. 如果 parent 是 NULL, 则该设备是顶层设备,较少见 */

    struct
 kobject kobj;
/*代表该设备并将其连接到结构体系中的 kobject; 注意:作为通用的规则, 
device->kobj->parent 应等于 device->parent->kobj*/

    char
    bus_id[
BUS_ID_SIZE]
;
/*在总线上唯一标识该设备的字符串;例如: PCI 设备使用标准的 PCI 
ID 格式, 包含:域, 总线, 设备, 和功能号.*/

    struct
 device_type    *
type;

    unsigned
        is_registered:
1;

    unsigned
        uevent_suppress:
1;

    struct
 device_attribute uevent_attr;

    struct
 device_attribute *
devt_attr;

    struct
 semaphore    sem;
  /* semaphore to 
synchronize calls to its driver. */

    struct
 bus_type    *
 bus;
     /*标识该设备连接在何种类型的总线上*/

    struct
 device_driver *
driver;
    /*管理该设备的驱动程
序*/

    void
        *
driver_data;
    /*该设
备驱动使用的私有数据成员*/

    void
        *
platform_data;
    /* 
Platform specific data, device core doesn't touch it */

    struct
 dev_pm_info    power;

#
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. */

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

    struct
 dma_coherent_mem    *
dma_mem;
 /* internal for coherent mem override 
*/

    /* arch specific
 additions */

    struct
 dev_archdata    archdata;

    spinlock_t        devres_lock;

    struct
 list_head    devres_head;

    /* class_device migration path */

    struct
 list_head    node;

    struct
 class
        *
class
;

    dev_t          devt;
       /* 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
 ldd_bus_release(
struct
 device *
dev)

{

 printk(
KERN_DEBUG "lddbus release/n"
)
;

}

struct
 device ldd_bus =
 {

 .
bus_id =
 "ldd0"
,

 .
release
 =
 ldd_bus_release

}
;
 /*这是顶层总线,parent 和
 bus 成员为 NULL*/

/*作
为第一个(并且唯一)总线, 它的名字为 ldd0,这个总线设备的注册代码如下:*/

ret =
 device_register(
&
ldd_bus)
;

if
 (
ret)

 printk(
KERN_NOTICE "Unable to register ldd0/n"
)
;

/*一旦调用完成, 新总线会在 sysfs 中 /sys/devices 
下显示,任何挂到这个总线的设备会在 /sys/devices/ldd0 下显示*/


设备属性

sysfs 中的设备入口可有属性,相关的结构是:

/* interface for exporting device attributes 这个结构体和《LDD3》中的不同,已经被更新过了,请特别注意!
*/

struct
 device_attribute {

    struct
 attribute attr;

    ssize_t (
*
show)
(
struct
 device *
dev,
 struct device_attribute *attr
,
char
 *
buf)
;

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

}
;

/*设备属性结构可在编译时建立, 使用以下宏:*/

DEVICE_ATTR(
_name,
_mode,
_show,
_store)
;

/*这个宏声
明一个结构, 将 dev_attr_ 作为给定 _name 的前缀来命名设备属性

/*属性文件的实际处理使用以下函数:*/

 int
 device_create_file(
struct
 device *
device,
    struct
 device_attribute *
 entry)
;

 void
 device_remove_file(
struct
 device
 *
 dev,
 struct
 device_attribute *
 attr)
;


设备结构的嵌



device 结构包含设备模型核心用来模拟系统的信息。但大部分子系统记录了关于它们又拥有的设备的额外信息,所以很少单纯用 device
结构代表设备,而是,通常将其嵌入一个设备的高层表示中。底层驱动几乎不知道 struct device。

lddbus 驱动创建了它自己的 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)
;


lddbus 导出的注册和注销接口如下:

/*

 * LDD devices.

 */

/*

 *
 For now, no references to LDDbus devices go out which are not

 * 
tracked via the module reference count, so we use a no-op

 * release 
function.

 */

static
 void
 ldd_dev_release(
struct
 device *
dev)

{
 }

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)
;

void
 
unregister_ldd_device(
struct
 ldd_device *
ldddev)

{

    device_unregister(
&
ldddev-
>
dev)
;

}

EXPORT_SYMBOL(
unregister_ldd_device)
;


sculld 驱动添加一个自己的属性到它的设备入口,称为 dev, 仅包含关联的设备号,源码如下:

static
 ssize_t sculld_show_dev(
struct
 device *
ddev,

struct device_attribute *attr
,

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"
,
 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( "Unable to create dev attribute ! /n");

}
 /*注意:程序使用 driver_data 成员来存储指向我们自己的内部的设备结构的指针。请检查

device_create_file

的返回值,否则编译时会有警告。*/


设备驱动程




设备模型跟踪所有系统已知的驱动,主要目的是使驱动程序核心能协调驱
动和新设备之间的关系。
一旦驱动在系统中是已知的对象就可能完成大量的工作。驱动程序的结构体 device_driver 定义如下:

/*定义在<linux/device.h>*/

struct
 device_driver {

    const
 char
        *
 name;
/*驱动程序的名字( 在 
sysfs 中出现 )*/

    struct
 bus_type        *
 bus;
/*驱动程序所操作的总线类型*/

    struct
 kobject        kobj;
/*内嵌的kobject对象*/

    struct
 klist        klist_devices;
/*当前驱动程序能操作的设备链表*/

    struct
 klist_node    knode_bus;

    struct
 module        *
 owner;

    const
 char
         *
 mod_name;
    /* used for built-in modules */

    struct
 module_kobject    *
 mkobj;

    int
    (
*
probe)
    (
struct
 device *
 dev)
;
/*查询一个特定
设备是否存在及驱动是否可以使用它的函数*/

    int
    (
*
remove
)
    (
struct
 
device *
 dev)
;
/*将设备从系统中删除*/

    void
    (
*
shutdown
)
    (
struct
 device *
 dev)
;
/*关闭设备*/

    int
    (
*
suspend)
    (
struct
 device *
 dev,
 pm_message_t state)
;

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

}
;

/*注册device_driver 结构的函数是:*/

int
 driver_register(
struct
 device_driver *
drv)
;

void
 
driver_unregister(
struct
 device_driver *
drv)
;

/*driver的属性结构在:*/

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 ) 
指向一组为属于该总线的所有设备创建的默认属性*/


驱动程序结构
的嵌入


对大多数驱动程序核心结构, 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(
drv)
 container_of(
drv,
 struct
 ldd_driver,
 driver)
;


lddbus总线中相关的驱动注册和注销函数是:

/*

 * Crude driver interface.

 */

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)
;

}

int
 register_ldd_driver(
struct
 ldd_driver *
driver)

{

 int
 ret;

 driver-
>
driver.
bus =
 &
ldd_bus_type;

 ret =
 driver_register(
&
driver-
>
driver)
;
/*注册底层的 device_driver 结构到核心*/

 if
 (
ret)

 return
 ret;

 driver-
>
version_attr.
attr.
name =
 "version"
;
/* driver_attribute 结构必须手工填充*/

 driver-
>
version_attr.
attr.
owner =
 driver-
>
module;
/*注意:设定 
version 属性的拥有者为驱动模块, 不是 lddbus 模块!因为 show_version 函数是使用驱动模块所创建的 
ldd_driver 结构,若 ldd_driver 结构在一个用户空间进程试图读取版本号时已经注销,就会出错*/

 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)
;
/*建立版本属性,因为这个属性在运行
时被创建,所以不能使用 DRIVER_ATTR 宏*/

}

void
 
unregister_ldd_driver(
struct
 ldd_driver *
driver)

{

    driver_unregister(
&
driver-
>
driver)
;

}

EXPORT_SYMBOL(
register_ldd_driver)
;

EXPORT_SYMBOL(
unregister_ldd_driver)
;


在sculld 中创建的 ldd_driver 结构如下:

/* Device model stuff */

static
 struct
 ldd_driver sculld_driver =
 {

    .
version =
 "$Revision:
 1.21 $"
,

    .
module =
 THIS_MODULE,

    .
driver
 =
 {

        .
name =
 "sculld"
,

    }
,

}
;
/*只要一个简单的 register_ldd_driver 
调用就可添加它到系统中。一旦完成初始化, 驱动信息可在 sysfs 中显示*/


类 子系统

类是一个设备的高层视图, 它抽象出了底层的实现细节,从而允许用户空间使用设备所提供的功能,
而不用关心设备是如何连接和工作的。类成员通常由上层代码所控制, 而无需驱动的明确支持。但有些情况下驱动也需要直接处理类。

几乎所有的类都显示在 /sys/class 目录中。出于历史的原因,有一个例外:块设备显示在 /sys/block目录中。在许多情况,
类子系统是向用户空间导出信息的最好方法。当类子系统创建一个类时,
它将完全拥有这个类,根本不用担心哪个模块拥有那些属性,而且信息的表示也比较友好。

为了管理类,驱动程序核心导出了一些接口,其目的之一是提供包含设备
号的属性以便自动创建设备节点,所以udev的使用离不开类。 类函数和结构与设备模型的其他部分遵循相同的模式
,所以真正崭新的概念是
很少的。

注意:class_simple 是老接口,在2.6.13中已被删除,这里不再研究。

管理类的接口

类由 struct class 的结构体来定义:



/*

 * device classes

 */

struct
 class
 {

    const
 char
        *
 name;
/*每个类需要一个唯一的名字, 它将显示在 /sys/class 中*/

    struct
 module        *
 owner;

    struct
 kset        subsys;

    struct
 list_head    children;

    struct
 list_head    devices;

    struct
 list_head    interfaces;

    struct
 kset        class_dirs;

    struct
 semaphore    sem;
    /* 
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)
;


类设备

类存在的真正目的是给作为类成员的各个设备提供一个容器,成员由 struct class_device 来表示:



struct
 class_device {

    struct
 list_head    node;
/*for internal use by the driver core 
only*/

    struct
 
kobject        kobj;
/*for internal use by the driver core 
only*/

    struct
 class
        *
 class
;
    /* 指向该设备所属的类,必须*/

    dev_t            devt;
        /* 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)
;

    char
    class_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
 list_head    node;

    struct
 class
        *
class
;
/* 指向该接口所属的类*/

    int
 (
*
add)
 (
struct
 
class_device *
,
 struct
 class_interface *
)
;


/*当一个类设备被加入到在 class_interface 结构中指定的类时, 将调用接口的 add 
函数,进行一些设备需要的额外设置,通常是添加更多属性或其他的一些工作*/

    void
 (
*
remove
)
    (
struct
 class_device *
,
 struct
 class_interface *
)
;
/*一个接口的功能是简单明了的. 当设备从类中删除, 将调用remove 方法来进行必要的清理*/

    int
 (
*
add_dev)
     (
struct
 
device *
,
 struct
 class_interface *
)
;

    void
 (
*
remove_dev)
 (
struct
 device *
,
 struct
 class_interface *
)
;

}
;

/*注册或注销接口的函数:*/

int
 class_interface_register(
struct
 class_interface *
class_intf)
;

void
 class_interface_unregister(
struct
 class_interface *
class_intf)
;

/*一个类可注册多个接口*/


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐