文档库

最新最全的文档下载
当前位置:文档库 > Linux驱动实验报告

Linux驱动实验报告

Linux平台上IO接口驱动程序设计

以按键和数码管为例

南京大学电子科学与工程学院

李超凡091180066

2011-12-31

摘要

本文介绍了一个简单的IO驱动程序的实现过程,通过编写内核模块的方式,控制嵌入式开发板上的单按键键盘和数码管。以此为例介绍了与之相关的Linux内核中的定时器的运用,探讨了在Linux中利用等待队列阻塞一个进程的具体方法,并在驱动程序设计的过程中尝试使用了Linux2.6内核引入的Linux Device Model,以及与之相配合的sysfs虚拟文件系统。

1介绍

1.1实验环境说明

开发板环境介绍:

1.开发板使用深圳市武耀博德信息技术有限公司生产的基于高性能的PXA270处理器的多功

能嵌入式开发平台EELIOD。

2.用于开发板上的Linux内核源代码为linux-2.6.9-eeliod,busybox版本号1.12。

3.开发板自带数码管四个,处理器通过一个两个锁存器与之相连,一共占用两个地址,分别

是0x10300000、0x10400000。

4.开发板自带LED灯8个,处理器同样通过一个锁存器与之相连,占用一个地址0x10500000。

5.开发板的单按键一共四个,通过GPIO与开发板相连,本实验中使用了其中了两个单按

键,这两个单按键分别在地址0x40e00100的第三和第四位。

PC环境介绍:

1.PC型号为Lenovo Thinkpad SL41028425AC。

2.PC机使用debian-6.0.3系统,Linux内核版本号

3.0.8。

3.PC机在/dev/input目录下已有如/dev/input/mice、/dev/input/mouse0、/dev/

input/event0等鼠标和键盘的设备文件,并使用内核自带的驱动,处于工作状态。

1.2设备驱动简介

设备驱动或者简写作驱动,是一类用来实现操作系统或者应用程序与硬件交互的计算机程序。驱动的编写应当为应用程序的编写服务,即能够简化应用程序的编写,尤其应当为应用程序的编写提供统一的编程接口,使之不受制于具体的硬件。

现代操作系统比如Windows、Linux一般都提供两套模式:内核模式(kernel mode)和用户模式(user mode),运行于用户模式的程序比如各种应用程序无法访问到内核模式的资源,除非通过系统调用(system call)。大多数设备驱动均运行于内核模式,但也有一部分设备驱动运行于用户模式。本文探讨运行于内核模式的驱动程序的编写。

在Linux操作系统中,运行于内核模式的驱动程序既可以内建于内核中,在开机时随内核一起加载,又可编译成单独的模块(module),随用随加载。很多Linux发行版在/lib/modules/目录下存储有大量模块,编译这些模块的源代码绝大部分都是随内核源代码一起发布的,但是

没有被编入内核的那部分。它们大多是一些具体设备的驱动程序,但由于所涉及的设备太过琐碎,很多人可能不需要它们,故而被编译成模块,放入/lib/modules目录中,用时再调入内核。

Linux中的内核模块一般以.ko为后缀名,Windows中的对应物则以.sys为后缀,一般.sys文件集中存放在Windows的系统分区的WINDOWS\system32\drivers文件夹下。早期版本的Linux如Linux2.4则以.o为后缀名,2.4和2.6版本的Makefile的书写也有较大的区别。2.4版本的模块编译过程不需要内核源码只需要内核源码的头文件。而2.6版本的模块编写则需要有对应版本的内核源码。

1.3sysfs虚拟文件系统简介

Linux2.6引入了全新的sysfs虚拟文件系统。这个文件系统一般就是现在的Linux发行版中常见的/sys目录1。这个文件系统将有关设备驱动的信息尤其是使用了Linux Device Model的驱动程序的相关信息通过sysfs虚拟文件系统中的文件呈现给应用程序,同时,应用程序也可以通过这个文件系统对设备驱动进行配置。在没有这个虚拟文件系统时,这些工作一般是通过procfs 完成或者不使用文件系统的方式,通过ioctl命令来对设备驱动进行配置以及调试。在Linux2.6中引入sysfs的部分原因正是由于procfs中存储了太多的与进程无关的信息。

应用程序从sysfs中读取信息是非常简单的,写入也是非常简单的,甚至不需要使用C语言编写应用程序,使用简单的cat命令和echo命令,通过bash即可实现一些简单的应用。例如,如果要写入255到/sys/devices/platform/i8042/serio4/serio5/speed2只需执行:

$echo255>/sys/devices/platform/i8042/serio4/serio5/speed

如果要读取信息则是:

$cat/sys/devices/platform/i8042/serio4/serio5/speed

由于sysfs适合于一些简单的配置信息的读取与写入,故而关于数码管与LED灯的信息比较适合存储在sysfs中,因为它们结构简单,且不涉及大规模的数据读写,故而只需要利用sysfs基本已经可以完成驱动的编写。本文将在后面的部分详细介绍之。

1.4按键驱动程序的特点介绍

对于按键驱动程序的编写,如果只考虑读取按键的状态,同样是非常简单的,甚至也可以使用sysfs实现之。但是这是远远不够的,因为如果将按键看做一个输入设备,那么必须记录下每次按键按下弹起事件,并判断是哪一个按键按下,生成相应的编码,并通过一个设备文件传送给应用程序。这决定了如果按键不使用中断的话,则只能通过不断扫描的方式去判断是否有按键按下,这便需要用到Linux的定时器。并且,如果应用程序读取设备文件时,没有按键事件发生,则一般情况下这个应用程序所对应的进程要进入睡眠状态,等待按键事件。这个过程即所谓的“阻塞IO”(blocking IO)。

一般情况下,Linux的键盘鼠标指点杆触摸板等输入设备文件都存放在/dev/input目录下,可以使用cat命令读取这些文件从而对阻塞IO有一个直观的感受。例如

$cat/dev/input/mice

就可以读取鼠标动作,但是在鼠标没有动作的时候,这个进程并不结束,而是在终端的左下角不断闪烁光标。另开一个终端,使用

$ps ax|grep mice

可以看到cat mice这个进程处于S+状态,即睡眠状态。当动鼠标时,终端上会有字符不断的显示出来,当停止移动鼠标时,光标静止并不断闪烁,进程依旧是睡眠状态。下面是一个简单的应用程序,可以进一步测试这些设备的特点,并在后面用来测试笔者所编写的按键驱动:

1#include

1当然也可以人为设定其他目录作为sysfs

2这个文件是笔者使用的PC机上的控制指点杆速度的文件

2

3int main(){

4FILE*fp;

5char a;

6fp=fopen("/dev/input/mice","r");

7stdin=fp;

8scanf("%c",&a);

9printf("received%d",a);

10fclose(fp);

11return0;

12}

代码1.getvalue.c

第六行打开一个设备文件,即鼠标设备;第七行对stdin重新赋值,使之指向打开的设备文件;第八行从中读取一个字符,存入a,然后再打印出来。

整个过程非常简单,但是,如果没有第七行,也就是说stdin没有被重新赋值,则这个程序执行的结果相信学过C语言的都应该非常熟悉,我们敲键盘,敲的内容会在终端上显示出来,当按下回车时,程序读入敲入的第一个字符,并打印一行字,然后程序终止。但是,但我们将stdin重定向到/dev/input/mice3,则会发现,鼠标只要稍一动,程序立即终止,这佐证了是Linux内核对stdin进行了缓冲,只有当敲击键盘回车时,才会将缓冲的数据送给scanf函数。而原有的设备驱动,并没有设计缓冲,至少这个鼠标设备文件对应的驱动没有设计缓冲,只要有数据,当即送给了scanf函数。所以,在后文中介绍的按键驱动中也没有设计缓冲。

2具体过程

本文略去一个模块中不涉及到具体设备的部分的介绍,比如设备号的设定,相关结构体的初始化,file_operations结构体中的函数指针的绑定,以及设备文件的生成,并假定这些配置工作已被正确的完成。4

2.1数码管及LED驱动的编写

2.1.1地址映射

由于arm处理器的IO接口与内存是统一编址的,所以,arm平台上的IO地址映射5在Linux 中只需要调用函数ioremap即可完成,这个函数的原型是

void*ioremap(unsigned long phys_addr,unsigned long size);

函数第一个参数是所要映射的实地址段的首地址,第二个参数是映射的实地址段的长度,以字节为单位,假定映射成功则返回一个线性地址即一个void指针,如果失败则返回一个NULL指针。

本文为方便地址的映射设计了如下结构体来统一的描述本文所涉及到的接口:

1struct eeliod_attribute{

2struct attribute attr;

3unsigned int data;

4unsigned int addr;

5size_t size;

6unsigned int flags;

7};

代码2.eeliod_attribute结构体

其中,attr是Linux Device Model的attribute结构体的实例,后文将予以说明;data用来存储这个设备所涉及到的数据,比如数码管各段的值;addr则是该设备对应的实地址;size即该设备所要占用的地址段的长度;flags用来记录该设备的读写属性,比如数码管由于采用了锁存器便是只读的。

然后,通过一个结构体数组,将这个结构体实例化:

3当去掉第七行后,假定该文件编译后的可执行文件名是getvalue,使用./getvalue

4这些工作的具体过程可参见附录3.2中的代码,或者笔者《8253驱动程序编写》一文

5地址映射就是把地址段从实地址空间映射到线性地址空间的过程

1static struct eeliod_attribute eeliod_attr[]={

2{{"GPLR0",THIS_MODULE,0666},0,0x40e00000,4,O_RDONLY},

3{{"GPLR1",THIS_MODULE,0666},0,0x40e00004,4,O_RDONLY},

4{{"GPLR2",THIS_MODULE,0666},0,0x40e00008,4,O_RDONLY},

5{{"GPLR3",THIS_MODULE,0666},0,0x40e00100,4,O_RDONLY},

6......

7{{"num_1_2",THIS_MODULE,0666},0xffff,0x10300000,2,O_WRONLY},

8{{"num_3_4",THIS_MODULE,0666},0xffff,0x10400000,2,O_WRONLY},

9{{"led",THIS_MODULE,0666},0,0x10500000,2,O_WRONLY},

10{{NULL,NULL,0},0,0,0,0},

11};

代码3.eeliod_attribute结构体数组

其中,省略号部分均是关于GPIO其他各个寄存器的部分,本文没有涉及。这样地址映射既可以统一的写成如下形式:

void*ptr=NULL;

ptr=ioremap(eeliod_attrp->addr,(long)eeliod_attrp->size);

其中,eeliod_attrp是eeliod_attribute结构体的指针。

2.1.2Linux Device Model及sysfs虚拟文件系统的配置

Linux Device Model中最基础的一个结构体便是kobject。“这个结构体将涉及到Device Model的资源组织起来,最初,这个结构体被设计成一个引用计数器”[1]。在2.6.9的内核上初始化Linux Device Model相关资源的代码如下所示6:

1eeliod_kobj=(struct kobject*)kcalloc(1,sizeof(struct kobject),GFP_KERNEL);

2memset(eeliod_kobj,0,sizeof(struct kobject));

3

4kobject_init(eeliod_kobj);

5eeliod_kobj->ktype=&ktype_eeliod;

6eeliod_kobj->parent=NULL;

7eeliod_kobj->kset=NULL;

8

9kobject_set_name(eeliod_kobj,"eeliod_kobj");

10kobject_add(eeliod_kobj);

11

12if(!eeliod_kobj)return-ENOMEM;

代码4.kobject结构体初始化

其中的eeliod_kobj是已经定义的struct kobject*类型的指针。

第一行的代码用来分配内存;第二行的代码则用来确保所获得的内存为全零,亦即所获得的对象的各个成员变量初值均为0,否则,可能会造成意想不到的后果[1]。第四行和第十行分别用来初始化这个kobject对象和添加这个对象,执行第十行的代码后如果没有错误,则在sysfs 文件系统中某个目录会出现一个子目录。

这个子目录的名称即由第九行的代码决定,这个函数类似于fprintf函数,第一个参量是个指针,第二个参量是一个字符串,部分值由后面的变量决定。显然,上面这个函数调用将这个kobject的名称设定为eeliod_kobject,即生成的子目录名为eeliod_kobject。

而这个子目录的位置则有六行和第七行的两个赋值语句共同决定:如果两个成员变量都被赋值为NULL,则目录eeliod_kobject会创建在sysfs的根目录,一般情况下是/sys。如果其中一个不为NULL比如parent不为NULL而是某一个kobject对象的指针比如kernel,则eeliod_kobject目录会被创建在/sys/kernel目录下。

第五行的代码决定了这个kobject的类型,ktype是结构体kobj_type的对象,kobj_type 结构体的原型如代码5所示:

1struct kobj_type{

2void(*release)(struct kobject*);

3struct sysfs_ops*sysfs_ops;

4struct attribute**default_attrs;

5};

代码5.kobj_type结构体

6在较新版本的内核上,初始化过程被大大简化,甚至仅需要一个函数kobject_create_and_add。但是,这个结构体的基本结构本没有改变,使用旧的方法需要对这个结构体有更多的了解

可以看到,kobj_type的第一个成员变量是一个函数指针,既然叫作release那么自然是用来注销kobject对象所占用的资源用的,当一个kobject的引用计数减到0时,这个函数就会被自动调用[1],笔者用的最简单的方法实现这个回调函数:

1static void general_kobject_release(struct kobject*kobj){

2kfree(kobj);

3}

代码6.kobj_type的release回调函数

即直接释放掉这个kobject对象所占用的内存。

sysfs_ops结构体与file_operations结构体类似,存放着一些函数指针,用来供用户程序调用。sysfs_ops结构体只有两个成员变量,一个ssize_t(*show)(struct kobject*kobj, struct attribute*attr,char*buffer);一个是ssize_(*store)(struct kobject*kobj, struct attribute*attr,const char*buffer,size_t size),前一个在用户程序读取sysfs中的文件时被调用,后一个在用户程序往sysfs中的文件写入值时被调用,当然,前提是这些文件对应于这个sysfs_ops结构体所属的kobject对象。

上文说到sysfs中的文件,这些文件在代码4所创建的目录eeliod_kobj下。sysfs中的每一个目录均代表着内核中的一个kobject或者kset,而这些目录中的每一个文件则代表这些目录所代表的kobject的某个属性(attribute),对代码5中的default_attrs赋值即是向kobject 中添加属性的方法之一。由于属性与sysfs中的文件时一一对应的,故而添加了属性也即添加了文件。

笔者一共添加了39个属性到kobject对象eeliod_kobj中,包括两个用来控制数码管的、一个用来控制LED灯的和36个与GPIO相关的,虽然GPIO似乎用不上sysfs,但是,添上这36个属性可以方便调试。添加的方法即将代码3中的eeliod_attr的中的每一个eeliod_attribute对象所包含的attribute对象初始化到default_attrs数组中去,如下面的代码所示:

static struct attribute*none_attrs[]={

&eeliod_attr[0].attr,

&eeliod_attr[1].attr,

&eeliod_attr[2].attr,

......

&eeliod_attr[37].attr,

&eeliod_attr[38].attr,

NULL,

};

代码7.default_attrs的初始化

在初始化代码4中的ktype_eeliod时将none_attrs赋值给default_attrs即完成属性的添加。

默认属性是随着kobject对象的加载被一起加载进内核的,Linux Device Model还设计了其他的方法动态地添加属性7以及卸载属性8,甚至还可以添加及卸载不使用sysfs_ops中的函数指针的属性9。许多情况下,使用默认属性已经足够,笔者即只是用了默认属性。

最后,介绍sysfs_ops中的两个回调函数的实现,show函数和store函数的实现如下面的代码所示:

1static ssize_t none_show(struct kobject*kobj,struct attribute*attr,char*buf){

2struct eeliod_attribute*eeliod_attrp=

3container_of(attr,struct eeliod_attribute,attr);

4if(eeliod_attrp->flags==O_RDONLY||eeliod_attrp->flags==O_RDWR){

5syn_rd(eeliod_attrp);

6}

7return sprintf(buf,"%x\n",eeliod_attrp->data);

8}

9static ssize_t none_store(struct kobject*kobj,struct attribute*attr,const char* buf,size_t count){

10

11struct eeliod_attribute*eeliod_attrp=

12container_of(attr,struct eeliod_attribute,attr);

13

7使用函数sysfs_create_file

8使用函数sysfs_remove_file

9使用函数sysfs_create_bin_file和函数sysfs_remove_bin_file以及结构体bin_attribute,这个过程在较新的内核版本中已经有了更简洁的方法

14int temp;

15

16sscanf(buf,"%xu",&temp);

17switch(eeliod_attrp->size){

18case1:

19eeliod_attrp->data=(unsigned char)temp;

20break;

21

22case2:

23eeliod_attrp->data=(unsigned short)temp;

24break;

25

26case4:

27eeliod_attrp->data=(unsigned int)temp;

28break;

29

30default:

31printk(KERN_ALERT"attribute size too big%d\n",VERSION);

32break;

33}

34if(eeliod_attrp->flags==O_WRONLY||eeliod_attrp->flags==O_RDWR){

35syn_wr(eeliod_attrp);

36}

37return count;

38}

代码8.show与store函数的实现

可以发现,这两个函数的实现是非常容易的,尤其与file_operations中的回调函数不同的是,这两个函数不需要调用copy_to_user和copy_from_user,可能是由于sysfs使用了VFS即虚拟文件系统[1],用户空间与内核空间数据的交换已经由VFS的代码解决。

container_of(attr,struct eeliod_attribute,attr)是Linux内核开发中使用非常广的一个技巧,这个技巧其实是通过一个宏来实现的,其作用就是将一个已知结构体的对象中某个成员变量的指针转换为这个结构体的对象的指针。具体到代码8中,即将参变量中传递来的struct attribute*类型的指针转变为struct eeliod_attribute*类型的指针。

而syn_wr和syn_rd两个函数分别负责同步eeliod_attrp->data中的值与底层硬件中寄存器或锁存器中的值的。这两个函数简单的说就是先用上文介绍的ioremap函数将实地址映射到线性地址,再调用readl、readw、readb和writeb、writew、writel等宏将eeliod_attrp->data与底层的硬件同步。具体的代码详见附录3.2。

2.1.3数码管驱动程序的测试

在1.3中,笔者已经介绍一些测试方法,即通过cat和echo函数作用于sysfs中的文件,测试相应的硬件响应。

在代码8中,笔者的sscanf和sprintf函数使用了十六进制输入输出数据,对于数码管来说,十六进制更加方便。因此,灭掉LED灯的命令即是:

$echo ff>/sys/eeliod_kobj/led

数码管的操作与之类似。另外,笔者还实现了传统的ioctl控制方法,可以先用ioctl写入一个值,然后再用读取sysfs中相应的文件,进一步测试驱动程序的功能的完善性。ioctl的实现如下所示:

1static int eeliod_ioctl(struct inode*inode,struct file*filep,unsigned int cmd ,unsigned long arg){

2switch(cmd){

3case NUM_1_2:

4wr_store(arg,eeliod_attr+NUM_1_2);

5break;

6

7case NUM_3_4:

8wr_store(arg,eeliod_attr+NUM_3_4);

9break;

10

11case LED:

12wr_store(arg,eeliod_attr+LED);

13break;

14

15default:

16break;

17}

18return0;

19}

代码9.ioctl函数的实现

代码9中的wr_store函数与代码8中的syn_wr类似,只不过wr_store同时实现向eeliod_attrp->data中写值和同步到底层硬件两个过程,其实syn_wr(eeliod_attrp)就等于wr_store(eeliod_ attrp->data,eeliod_attrp),wr_store和syn_wr的具体实现见附录3.2。

实现了这个函数,应用程序就可以使用系统调用ioctl来控制数码管和LED灯了,笔者编

写了一个ioctl_test.c应用程序在附录3.4里,ioctl的使用方法可参见这个文件。

2.2键盘驱动的编写

2.2.1Kernel Timers的运用

Linux的内核模块运行于内核模式,用户模式下的进程调度正是由内核来完成的,故而内核

模式中是没有进程调度的概念的,故而不能使用用户模式经常使用的一些方法去执行一些需要

延期执行或者定期重复执行的代码。比如下面的代码在用户模式的程序甚至单片机的代码中经

常看到:

while(1){

//some codes

}

代码10.重复执行代码的例子

这在用户程序中是没有问题的,因为有内核作进程调度,运行这些代码的进程阻塞,别的

进程照常运行。但在内核中随意的写上这样的代码却会造成内核崩溃,然后死机。比如在module_init所指定的模块初始化函数中写上这样的代码,便可能会在insmod的时候造成死机。

因此,Linux中的内核定时器(Kernel Timers)便有了存在的必要,使用内核定时器首先要实

例化一个timer_list结构体,使用如下的代码获取一个struct timer_list*类型的指针:static struct timer_list*timer_p=NULL;

然后再在模块初始化函数中获取内存,进行一些必要的配置:

1int ret;

2

3timer_p=(struct timer_list*)kcalloc(1,sizeof(struct timer_list),GFP_KERNEL);

4init_timer(timer_p);

5timer_p->expires=0;

6timer_p->function=timer_callback;

7timer_p->data=(unsigned long)(eeliod_attr[3].addr);

8

9printk(KERN_ALERT"timer_added\n");

10ret=mod_timer(timer_p,jiffies+DELAY);///msecs_to_jiffies(DELAY));

11if(ret)printk("Error in mod_timer\n");

12

13add_timer(timer_p);

代码11.Kernel Timers的初始化

第三行用来分配内存;第四行调用内核的初始化函数对这个对象进行初始化;第五行设定所要

延时执行的时间;第六行设定所要延时执行的回调函数(callback function);第七行则是送给

回调函数的一个变量,如果所需要传递的内容一个unsigned long变量不够,当然也可以传递

一个指针,本例即传递了一个指针,但是并没有在回调函数中使用,详见代码12;第十行的mod_timer函数则是用来设置计时器的timer_p->expires的值的,这个值应该随着时间的流

逝不断递减的,当减到0时执行回调函数timer_p->function,本例中即是timer_callback。

其中的回调函数的实现如代码12所示,值得注意的是timer_list中回调函数工作于atomic context10而言的,使用in_atomic()函数会得到非零的结果,这也许导致了笔者在实现这个回调10这个背景(context)是相对于进程背景(process context)而言的

函数时的一些不寻常的情况,比如实测发现iounmap函数如果在这个回调函数中被调用会导致内核崩溃:

1static void timer_callback(unsigned long data){

2int b=0;

3void*ptr=NULL;

4

5ptr=ioremap(0x40e00100,4);

6if(ptr==NULL){

7printk(KERN_ALERT"error,ptr==NULL timer_callback\n");

8return;

9}

10

11b=readl(ptr);

12

13static int last_arg[4]={0,0,0,0};

14int temp=0;

15

16if(temp=(int)(0x00000004&b)){

17last_arg[0]+=temp;

18if(last_arg[0]>JIT/2){

19key_buf='a';

20key_pressed=1;

21wake_up_interruptible(&wq);

22last_arg[0]=0;

23printk(KERN_DEBUG"key SW4pressed\n");

24}

25}

26

27if(temp=(int)(0x00000008&b)){

28last_arg[1]+=temp;

29if(last_arg[1]>JIT){

30key_buf='b';

31key_pressed=1;

32wake_up_interruptible(&wq);

33last_arg[1]=0;

34printk(KERN_DEBUG"key SW1pressed\n");

35}

36}

37

38int ret;

39ret=mod_timer(timer_p,jiffies+DELAY);

40}

代码12.timer_callback的实现

代码前11行是地址映射和读取相应的GPIO寄存器以判断按键是否被按下的相关代码;13行到37行则是关于键盘防抖动的部分,方抖动的思路即是不断记录按键的状态,当按键在被按下状态持续一定的时间11,产生一个按键事件;代码的38到40行则是用来实现定期不断执行以上代码的作用,即在定时器的回调函数中更新定时器使之再次启动从而不断执行。

当产生了按键事件后,即需要停止进程阻塞,并将相应的字符12或者键码传递给用户程序,这将在2.2.2讨论。

2.2.2Blocking I/O的实现

由于按键设备驱动要求在有按键事件时才响应用户程序的数据请求,那么在没有按键事件时就只能将用户程序的进程阻塞,在Linux下这通过一个等待队列使这个进程进入睡眠状态。首先,需要声明一个wait_queue_head_t以及一个变量:

static int key_pressed=0;

static DECLARE_WAIT_QUEUE_HEAD(wq);

上述的key_pressed变量在代码12中和代码13中所实现的file_operations结构体中的read 函数中被用到:

11可能由于笔者使用的案件不对称两个按键所需要的时间不等,一个是JIT一个是JIT/2

12本例中只实现了两个按键的情况,为了测试的方便一个'a'代表,一个用'b'代表,更多按键的情况与此类似,0x00000008&b这样的语句用于判断GPIO的某一位是否为高电平,亦即判断某个按键是否被按下

1ssize_t sleepy_read(struct file*filp,char__user*buf,size_t count,loff_t*pos)

2{

3int retval;

4

5printk(KERN_DEBUG"process%i(%s)going to sleep\n",current->pid,current->comm); 6wait_event_interruptible(wq,key_pressed!=0);

7key_pressed=0;

8

9printk(KERN_DEBUG"awoken%i(%s)\n",current->pid,current->comm);

10if(copy_to_user(buf,&key_buf,1)){

11retval=-EFAULT;

12}

13return1;

14}

代码13.read函数的实现

current是一个struct task_struct*类型的指针,指向当前进程。所以,current->pid即是当前的进程号。因为使用了这个current宏,这个printk函数必须要在process context下运行,而且下面的wait_event_interruptible也有这个要求。

代码13中wait_event_interruptible13函数使得进程进入睡眠状态,其第一个参量wq是一个wait_queue_head_t类型变量,第二个参量一般是一个条件判断表达式,与if()语句中的内容一样,用于在进入睡眠状态时或退出睡眠状态时判断条件是否成立,如果成立则不进入睡眠状态或退出睡眠状态。

使一个其他进程退出睡眠状态需要调用如代码12中所示的wake_up_interruptible函数,并同时使得上面所说的条件判断表达式为真,在本例中即向key_pressed赋值1。

这样即可使得当产生一个按键事件时,进程从睡眠状态中退出,并执行第八行之后的代码,亦即把&key_buf指向的地址上的值传递一个字节到用户地址空间(user space),也就是把key_buf传送到用户地址空间。key_buf是一个全局变量,在代码12中已经出现过,用于区分不同的按键按下。

最后返回一个1。正因为返回的是常量1,故而如果有用户程序读取这个设备文件,则这个进程永远不会正常退出。只有当返回0的时候,才表示文件结束符EOF,返回1表示用户程序读到一个字节的内容,而且文件没有结束。这正符号键盘驱动的设计要求,按键是永远可能被按下的。

2.2.3按键驱动程序的测试

按键程序的测试按照1.4中的方法测试即可。即用cat和1.4中所示的那个getvalue程序用来测试设备文件是否具有相应的特点。事实上,经过笔者在开发板上实际测试,上边所写的驱动确实可以做到cat设备文件时,每按一次按键,相应字符显示在终端上,不按键时光标闪烁,进程睡眠。而用getvalue测试时,也是按一个按键则进程结束退出。

3附录

3.1Makefile文件

1KSRC=/home/st/091180066/src/linux-2.6.9-eeliod/

2#KSRC=/lib/modules/$(shell uname-r)/build

3#KSRC=/media/__ubuntu_11.10_/home/li3939108/linux-source-eeliod/linux-2.6.9-eeliod/

4PWD=$(shell pwd)

5CC=/usr/local/arm-linux/bin/arm-linux-gcc

6#CC=gcc

7obj-m:=key.o

8default:

9$(MAKE)-C$(KSRC)M=$(PWD)modules

10clean:

11$(MAKE)-C$(KSRC)SUBDIRS=$(PWD)clean

12test:test.c

13$(CC)-o test test.c

14ioctl:ioctl_test.c

15$(CC)-o ioctl_test ioctl_test.c

13在较新版本的Linux中使用这个函数以及相应的wake_up_interruptible函数需要包含头文件

代码14.Makefile

3.2key.c文件

下面这个文件即是驱动的最重要的文件:

1#include

2#include

3#include

4#include

5#include

6#include

7#include

8#include

9

10#include

11

12#include

13

14

15#include

16#include

17

18#define NUM_1_236

19#define NUM_3_437

20#define LED38

21

22#define GPLR00

23#define GPLR11

24#define GPLR22

25#define GPLR33

26

27//#define DELAY(msecs_to_jiffies(1000))

28#define DELAY2

29#define JIT60

30

31#define VERSION2

32

33static struct timer_list*timer_p=NULL;

34static int key_pressed=0;

35static char key_buf=0;

36static DECLARE_WAIT_QUEUE_HEAD(wq);

37

38struct eeliod_attribute{

39struct attribute attr;

40unsigned int data;

41unsigned int addr;

42size_t size;

43unsigned int flags;

44};

45

46static struct eeliod_attribute eeliod_attr[]={

47{{"GPLR0",THIS_MODULE,0666},0,0x40e00000,4,O_RDONLY}, 48{{"GPLR1",THIS_MODULE,0666},0,0x40e00004,4,O_RDONLY}, 49{{"GPLR2",THIS_MODULE,0666},0,0x40e00008,4,O_RDONLY}, 50{{"GPLR3",THIS_MODULE,0666},0,0x40e00100,4,O_RDONLY}, 51{{"GPDR0",THIS_MODULE,0666},0,0x40e0000c,4,O_RDWR}, 52{{"GPDR1",THIS_MODULE,0666},0,0x40e00010,4,O_RDWR}, 53{{"GPDR2",THIS_MODULE,0666},0,0x40e00014,2,O_RDWR}, 54{{"GPDR3",THIS_MODULE,0666},0,0x40e0010c,4,O_RDWR}, 55{{"GPSR0",THIS_MODULE,0666},0,0x40e00018,4,O_WRONLY}, 56{{"GPSR1",THIS_MODULE,0666},0,0x40e0001c,4,O_WRONLY}, 57{{"GPSR2",THIS_MODULE,0666},0,0x40e00020,2,O_WRONLY}, 58{{"GPSR3",THIS_MODULE,0666},0,0x40e00118,4,O_WRONLY}, 59{{"GPCR0",THIS_MODULE,0666},0,0x40e00024,4,O_WRONLY}, 60{{"GPCR1",THIS_MODULE,0666},0,0x40e00028,4,O_WRONLY}, 61{{"GPCR2",THIS_MODULE,0666},0,0x40e0002c,2,O_WRONLY}, 62{{"GPCR3",THIS_MODULE,0666},0,0x40e00124,4,O_WRONLY}, 63{{"GRER0",THIS_MODULE,0666},0,0x40e00030,4,O_RDWR}, 64{{"GRER1",THIS_MODULE,0666},0,0x40e00034,4,O_RDWR}, 65{{"GRER2",THIS_MODULE,0666},0,0x40e00038,2,O_RDWR},

66{{"GRER3",THIS_MODULE,0666},0,0x40e00130,4,O_RDWR},

67{{"GFER0",THIS_MODULE,0666},0,0x40e0003c,4,O_RDWR},

68{{"GFER1",THIS_MODULE,0666},0,0x40e00040,4,O_RDWR},

69{{"GFER2",THIS_MODULE,0666},0,0x40e00044,2,O_RDWR},

70{{"GFER3",THIS_MODULE,0666},0,0x40e0013c,4,O_RDWR},

71{{"GEDR0",THIS_MODULE,0666},0,0x40e00048,4,O_RDWR},

72{{"GEDR1",THIS_MODULE,0666},0,0x40e0004c,4,O_RDWR},

73{{"GEDR2",THIS_MODULE,0666},0,0x40e00050,2,O_RDWR},

74{{"GEDR3",THIS_MODULE,0666},0,0x40e00148,4,O_RDWR},

75{{"GAFR0_L",THIS_MODULE,0666},0,0x40e00054,2,O_RDWR},

76{{"GAFR0_U",THIS_MODULE,0666},0,0x40e00058,2,O_RDWR},

77{{"GAFR1_L",THIS_MODULE,0666},0,0x40e0005c,2,O_RDWR},

78{{"GAFR1_U",THIS_MODULE,0666},0,0x40e00060,2,O_RDWR},

79{{"GAFR2_L",THIS_MODULE,0666},0,0x40e00064,2,O_RDWR},

80{{"GAFR2_U",THIS_MODULE,0666},0,0x40e00068,2,O_RDWR},

81{{"GAFR3_L",THIS_MODULE,0666},0,0x40e0006c,2,O_RDWR},

82{{"GAFR3_U",THIS_MODULE,0666},0,0x40e00070,2,O_RDWR},

83{{"num_1_2",THIS_MODULE,0666},0xffff,0x10300000,2,O_WRONLY}, 84{{"num_3_4",THIS_MODULE,0666},0xffff,0x10400000,2,O_WRONLY}, 85{{"led",THIS_MODULE,0666},0,0x10500000,2,O_WRONLY},

86{{NULL,NULL,0},0,0,0,0},

87};

88

89

90static int wr_store(int b,struct eeliod_attribute*eeliod_attrp){

91void*ptr=NULL;

92ptr=ioremap(eeliod_attrp->addr,(long)eeliod_attrp->size);

93switch(eeliod_attrp->size){

94case1:

95writeb(b&0x000000ff,ptr);

96break;

97

98case2:

99writew(b&0x0000ffff,ptr);

100break;

101

102case4:

103writel(b,ptr);

104break;

105

106default:

107printk(KERN_ALERT"excessed size to write to iomap\n");

108break;

109}

110iounmap(ptr);

111return eeliod_attrp->data=b;

112}

113

114static int syn_wr(struct eeliod_attribute*eeliod_attrp){

115return wr_store(eeliod_attrp->data,eeliod_attrp);

116}

117static int rd_show(struct eeliod_attribute*eeliod_attrp){

118

119int b=0;

120void*ptr=NULL;

121if(eeliod_attrp==NULL){

122printk(KERN_ALERT"error,eeliod_attrp==NULL,rd_show\n"); 123return-1;

124}

125

126ptr=ioremap(eeliod_attrp->addr,eeliod_attrp->size);

127if(ptr==NULL){

128printk(KERN_ALERT"error,ptr==NULL rd_show\n");

129return-1;

130}

131switch(eeliod_attrp->size){

132case1:

133b=readb(ptr);

134break;

135

136case2:

137b=readw(ptr);

138break;

139

140case4:

141b=readl(ptr);

142break;

143

144default:

145b=-1;

146printk(KERN_ALERT"excessed size to write to iomap\n");

147break;

148}

149iounmap(ptr);

150return eeliod_attrp->data=b;

151}

152static int syn_rd(struct eeliod_attribute*eeliod_attrp){

153return rd_show(eeliod_attrp);

154}

155

156static ssize_t none_show(struct kobject*kobj,struct attribute*attr,char*buf){ 157struct eeliod_attribute*eeliod_attrp=

158container_of(attr,struct eeliod_attribute,attr);

159if(eeliod_attrp->flags==O_RDONLY||eeliod_attrp->flags==O_RDWR){

160syn_rd(eeliod_attrp);

161}

162return sprintf(buf,"%x\n",eeliod_attrp->data);

163}

164static ssize_t none_store(struct kobject*kobj,struct attribute*attr,const char* buf,size_t count){

165

166struct eeliod_attribute*eeliod_attrp=

167container_of(attr,struct eeliod_attribute,attr);

168

169int temp;

170

171sscanf(buf,"%xu",&temp);

172switch(eeliod_attrp->size){

173case1:

174eeliod_attrp->data=(unsigned char)temp;

175break;

176

177case2:

178eeliod_attrp->data=(unsigned short)temp;

179break;

180

181case4:

182eeliod_attrp->data=(unsigned int)temp;

183break;

184

185default:

186printk(KERN_ALERT"attribute size too big%d\n",VERSION);

187break;

188}

189if(eeliod_attrp->flags==O_WRONLY){

190syn_wr(eeliod_attrp);

191}

192return count;

193}

194

195static void general_kobject_release(struct kobject*kobj){

196kfree(kobj);

197}

198

199static struct sysfs_ops none_ops={

200.show=none_show,

201.store=none_store,

202};

203

204static struct attribute*none_attrs[]={

205&eeliod_attr[0].attr,

206&eeliod_attr[1].attr,

207&eeliod_attr[2].attr,

208&eeliod_attr[3].attr,

209&eeliod_attr[4].attr,

210&eeliod_attr[5].attr,

211&eeliod_attr[6].attr,

212&eeliod_attr[7].attr,

213&eeliod_attr[8].attr,

214&eeliod_attr[9].attr,

215&eeliod_attr[10].attr,

216&eeliod_attr[11].attr,

217&eeliod_attr[12].attr,

218&eeliod_attr[13].attr,

219&eeliod_attr[14].attr,

220&eeliod_attr[15].attr,

221&eeliod_attr[16].attr,

222&eeliod_attr[17].attr,

223&eeliod_attr[18].attr,

224&eeliod_attr[19].attr,

225&eeliod_attr[20].attr,

226&eeliod_attr[21].attr,

227&eeliod_attr[22].attr,

228&eeliod_attr[23].attr,

229&eeliod_attr[24].attr,

230&eeliod_attr[25].attr,

231&eeliod_attr[26].attr,

232&eeliod_attr[27].attr,

233&eeliod_attr[28].attr,

234&eeliod_attr[29].attr,

235&eeliod_attr[30].attr,

236&eeliod_attr[31].attr,

237&eeliod_attr[32].attr,

238&eeliod_attr[33].attr,

239&eeliod_attr[34].attr,

240&eeliod_attr[35].attr,

241&eeliod_attr[36].attr,

242&eeliod_attr[37].attr,

243&eeliod_attr[38].attr,

244NULL,

245};

246

247static struct kobj_type ktype_eeliod={

248.release=general_kobject_release,

249.sysfs_ops=&none_ops,

250.default_attrs=none_attrs,

251};

252

253int eeliod_open(struct inode*node,struct file*file){

254printk(KERN_DEBUG"I am opened,from eeliod,ver%d\n",VERSION);

255return0;

256}

257int eeliod_release(struct inode*node,struct file*file){

258printk(KERN_DEBUG"I am released,from eeliod,ver%d\n",VERSION);

259return0;

260}

261

262static int eeliod_write(struct file*filep,const char__user*userp,size_t size, loff_t*offp){

263return0;

264}

265ssize_t sleepy_read(struct file*filp,char__user*buf,size_t count,loff_t*pos) 266{

267int ret;

268int retval;

269//printk("Starting timer to fire in2ms(%ld)\n",jiffies);

270/*

271ret=mod_timer(timer_p,jiffies+DELAY);

272if(ret)printk("Error in mod_timer\n");

273*/

274

275printk(KERN_DEBUG"process%i(%s)going to sleep\n",current->pid,current->comm); 276wait_event_interruptible(wq,key_pressed!=0);

277key_pressed=0;

278

279printk(KERN_DEBUG"awoken%i(%s)\n",current->pid,current->comm);

280if(copy_to_user(buf,&key_buf,1)){

281retval=-EFAULT;

282}

283

284return1;

285}

286

287

288static int eeliod_ioctl(struct inode*inode,struct file*filep,unsigned int cmd ,unsigned long arg){

289switch(cmd){

290case NUM_1_2:

291wr_store(arg,eeliod_attr+NUM_1_2);

292break;

293

294case NUM_3_4:

295wr_store(arg,eeliod_attr+NUM_3_4);

296break;

297

298case LED:

299wr_store(arg,eeliod_attr+LED);

300break;

301

302default:

303break;

304}

305return0;

306}

307/*

308*a timer declaration and a loop callback function

309*/

310static void timer_callback(unsigned long data){

311//struct eeliod_attribute*eeliod_attrp=(struct eeliod_attribute*)data;

312int b=0;

313void*ptr=NULL;

314

315/*

316if(eeliod_attrp==NULL){

317printk(KERN_ALERT"error,eeliod_attrp==NULL,timer_callback\n");

318return;

319}

320*/

321ptr=ioremap(0x40e00100,4);

322//printk(KERN_ALERT"ioremap called\n");

323if(ptr==NULL){

324printk(KERN_ALERT"error,ptr==NULL timer_callback\n");

325return;

326}

327

328b=readl(ptr);

329//printk(KERN_ALERT"readl called ptr:%d\n",(unsigned int)ptr);

330

331//iounmap(ptr);

332//printk(KERN_ALERT"iounmap called\n");

333

334static int last_arg[4]={0,0,0,0};

335int temp=0;

336

337if(temp=(int)(0x00000004&b)){

338last_arg[0]+=temp;

339if(last_arg[0]>JIT/2){

340key_buf='a';

341key_pressed=1;

342wake_up_interruptible(&wq);

343last_arg[0]=0;

344printk(KERN_DEBUG"key SW4pressed\n");

345}

346}

347

348if(temp=(int)(0x00000008&b)){

349last_arg[1]+=temp;

350if(last_arg[1]>JIT){

351key_buf='b';

352key_pressed=1;

353wake_up_interruptible(&wq);

354last_arg[1]=0;

355printk(KERN_DEBUG"key SW1pressed\n");

356}

357}

358

359int ret;

360//printk(KERN_ALERT"timer_added\n");

361ret=mod_timer(timer_p,jiffies+DELAY);///msecs_to_jiffies(DELAY));

362}

363

364

365static struct cdev*eeliod_cdev;

366static struct kobject*eeliod_kobj;

367

368static struct file_operations fops=

369{

370.open=eeliod_open,

371.release=eeliod_release,

372.write=eeliod_write,

373.read=sleepy_read,

374.ioctl=eeliod_ioctl,

375};

376static int eeliod_init(void){

377int err;

378int devno;

379eeliod_cdev=cdev_alloc();

380devno=MKDEV(123,222);

381

382eeliod_cdev->ops=&fops;

383eeliod_cdev->owner=THIS_MODULE;

384

385err=cdev_add(eeliod_cdev,devno,1);

386if(err)printk(KERN_NOTICE"Error%d adding eeliod,ver%d\n",err,VERSION);

387

388/*

389*iomem request

390*/

391int i=0;

392while(i){

393if(eeliod_attr[i]http://www.wendangku.net/doc/441a6a03cc175527072208ee.html==NULL)break;

394

395request_mem_region(eeliod_attr[i].addr,eeliod_attr[i].size,eeliod_attr[i].

http://www.wendangku.net/doc/441a6a03cc175527072208ee.html);

396

397i++;

398}

399/*

400*the sysfs and the kobject

401*/

402/*

403void*ptr=NULL;

404ptr=ioremap(0x10500000,4);

405*/

406

407eeliod_kobj=(struct kobject*)kcalloc(1,sizeof(struct kobject),GFP_KERNEL); 408memset(eeliod_kobj,0,sizeof(struct kobject));

409

410kobject_init(eeliod_kobj);

411eeliod_kobj->ktype=&ktype_eeliod;

412eeliod_kobj->parent=NULL;

413eeliod_kobj->kset=NULL;

414

415kobject_set_name(eeliod_kobj,"eeliod_kobj");

416kobject_add(eeliod_kobj);

417

418if(!eeliod_kobj)return-ENOMEM;

419

420syn_wr(eeliod_attr+NUM_1_2);

421syn_wr(eeliod_attr+NUM_3_4);

422syn_wr(eeliod_attr+LED);

423/*

424*the timer

425*/

426int ret;

427

428timer_p=(struct timer_list*)kcalloc(1,sizeof(struct timer_list),GFP_KERNEL); 429init_timer(timer_p);

430timer_p->expires=0;

431timer_p->function=timer_callback;

432//syn_rd(eeliod_attr+3);

433timer_p->data=(unsigned long)(eeliod_attr[3].addr);

434

435

436

437printk(KERN_ALERT"timer_added\n");

438ret=mod_timer(timer_p,jiffies+DELAY);///msecs_to_jiffies(DELAY));

439if(ret)printk("Error in mod_timer\n");

440

441add_timer(timer_p);

442

443

444printk(KERN_ALERT"eeliod2:Hello,world,from eeliod_init,ver%d\n",VERSION);

445return0;

446}

447

448static void eeliod_exit(void){

449cdev_del(eeliod_cdev);

450

451wr_store(0xffff,eeliod_attr+36);

452wr_store(0xffff,eeliod_attr+37);

453wr_store(0xffff,eeliod_attr+38);

454

455/*

456*iomem release

457*/

458

459int i=0;

460while(i){

461if(eeliod_attr[i]http://www.wendangku.net/doc/441a6a03cc175527072208ee.html==NULL)break;

462

463release_mem_region(eeliod_attr[i].addr,eeliod_attr[i].size);

464

465i++;

466}

467printk(KERN_ALERT"Goodbye,cruel world,from eeliod_exit,ver%d\n",VERSION); 468

469/*

470*timer deleted

471*/

472

473int ret;

474

475ret=del_timer(timer_p);

476if(ret)printk("The timer is still in use...\n");

477

478printk("Timer module uninstalling\n");

479

480kobject_del(eeliod_kobj);

481kobject_put(eeliod_kobj);

482}

483

484module_init(eeliod_init);

485module_exit(eeliod_exit);

486MODULE_LICENSE("GPL");

代码15.key.c

3.3getvalue.c文件

1#include

2

3int main(){

4FILE*fp;

5char a[4];

6fp=fopen("/dev/input/mice","r");

7//stdin=fp;

8scanf("%c%c%c%c",a,a+1,a+2,a+3);

9printf("received%d%d%d%d",a[0],a[1],a[2],a[3]); 10fclose(fp);

11return0;

12}

代码16.getvalue.c 3.4ioctl_test.c文件

1#include

2#include

3#include

4#include

5#include

6#include

7

8#include"key.h"

9

10

11int main(int argc,char*argv[]){

12int fd=0;

13int i=0;

14unsigned long cmd=0;

15unsigned long arg=0;

16if((fd=open("./hello",O_RDONLY))<0){

17printf("error when opening file\n");}

18

19if(argc!=3){

20perror("format:test cmd arg\n");

21}else{

22switch(argv[1][0]){

23case NUM_1_2+'0'-NUM_1_2:

24case NUM_3_4+'0'-NUM_1_2:

25case LED+'0'-NUM_1_2:

26cmd=argv[1][0]-'0'+NUM_1_2;

27break;

28

29default:

30perror("unknown command\n");

31break;

32}

33

34while(argv[2][i]!='\0'){

35arg=arg*0x10;

36switch(argv[2][i]){

37case'0':

38case'1':

39case'2':

40case'3':

41case'4':

42case'5':

43case'6':

44case'7':

45case'8':

46case'9':

47arg+=argv[2][i]-'0';

48break;

49

50case'a':

51case'b':

52case'c':

53case'd':

54case'e':

55case'f':

56arg+=argv[2][i]-'a'+0xA;

57break;

58

59case'A':

60case'B':

61case'C':

62case'D':

63case'E':

64case'F':

65arg+=argv[2][i]-'A'+0xA;

66break;

67

68default:

69perror("unknown command\n");

70break;

71}

72i++;

73}

74}

75

76printf("cmd:%x\narg:%x\n",cmd,arg);

77ioctl(fd,cmd,arg);

78

79return0;

80}

代码17.ioctl_test.c文件

参考文献

[1]Jonathan Corbet.Linux Device Drivers,3rd Edition.O’Reilly,2005.