




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Linux驱动程序设计什么是驱动?驱动程序--DeviceDriver是指挥硬件工作的软件。它是应用程序与硬件之间的一个中层软件层,为应用程序屏蔽硬件的细节。操作系统只能通过驱动程序,才能控制硬件设备的工作,假如某设备的驱动程序未能正确安装,便不能正常工作。简单地说,驱动程序提供了硬件到操作系统的一个接口以及协调二者之间的关系。
主流的操作系统类型微内核体系结构
内核只负责进程管理、内存管理、中断处理等。文件系统、网络协议等其他部分运行与用户空间。可扩展性好,不同层次之间消息传递比较麻烦。宏内核体系结构
内核是一个大程序,包括了操作系统所有的部分,系统的的速度、性能好,但是可扩展性和维护性相对较弱。采用模块机制
内核做的可很小,在内核中设计一些模块接口,可以动态的加载模块,内核管理所有模块的运行,系统功能的扩展性留给模块去完成。Linux内核模块模块全称:动态可加载内核模块(LoadableKernelModule,LKM)模块(module)在内核空间运行。模块实际上是一种目标对象文件,没有链接,不能独立运行,但是其代码可以在运行时链接到系统中作为内核的一部分运行或从内核中取下,从而可以动态扩充内核的功能。模块通常由一组函数和数据结构组成。Linux的驱动设备程序大多采用模块模式实现。采用模块机制的优点操作系统内核更加紧凑灵活系统如果需要新的功能,只要编译成相应的模块,然后插入操作系统即可。模块一旦链接到内核,就会与内核中原有的代码完全等价。驱动程序的安装与卸载方法一:直接修改系统核心的源代码,把设备驱动程序加进操作系统核里。即和操作系统内核一起编译成为映像文件烧写到flash中。(系统自带的驱动)方法二:把设备驱动程序作为可加载的模块,由系统管理员动态地加载它,使之成为核心的一部分,适合在调试阶段使用。(用户安装的驱动)应用程序与内核模块的区别应用程序驱动程序运行空间用户空间内核空间运行入口main()函数module_init()指定出口无module_exit()编译gcc-c,ldmakefile运行直接运行Insmod安装,应用程序或其他模块调用设备驱动程序框架和使用操作系统与设备驱动之间有式进行相互操作的标准接口:驱动设备的加载与卸载:如何把驱动加入到操作系统。(与模块相关的命令)驱动程序与系统的接口:使得操作系统知道如何对硬件进行操作。(有哪些必须的函数)驱动程序与设备的接口:这部分描述了驱动程序如何与设备进行交互,
这与具体设备密切相关。(具体的函数怎么写)内核模块命令介绍insmod:向正在运行的操作系统加载模块。lsmod:显示当前加载的内核模块。rmmod:从当前运行的内核中卸载内核模块。depmod:处理可加载内核模块的依赖关系。modprobe:利用depmod创建的依赖文件来自动加载相关内核模块。modinfo:获取模块信息。Linux设备驱动程序入口框架模块加载函数init_module()/module_init():当通过insmod或modporbe命令加载模块时,会被内核自动执行,主要完成模块的初始化工作。模块卸载函数cleanup_module()/module_exit():当通过rmmod命令卸载模块时自动执行,主要完成模块的卸载工作。模块许可证声明MODULE_LICENSE():描述内核的许可权限,否则加载模块会提示内核被污染。DaulBSD、GPL、GPLv2等模块参数module_param():(可选)模块导出符号EXPORT_SYMBOL_GPL():(可选)模块作者声明MODULE_AUTHOR()等:(可选)#include<linux/init.h>#include<linux/module.h>MODULE_LICENSE("GPLv2");intinit_module(void){printk(KERN_ALERT"helloworld!\n");return0;}voidcleanup_module(void){printk(KERN_ALERT"goodbyeworld!\n");}hello.c最简单的模块(一)相关的头文件许可证声明#include<linux/init.h>#include<linux/module.h>MODULE_LICENSE("GPLv2");intinit_module(void){printk(KERN_INFO"helloworld!\n");return0;}voidcleanup_module(void){printk(KERN_ALERT"goodbyeworld!\n");}最简单的模块(一)模块加载函数,insmod时运行内核中的输出函数,与printf类似printk(KERN_INFO"helloworld!\n");printk("<级别>helloworld!\n");内核中使用的打印信息,相当于printf一般级别中可以使用0-7表示打印信息的等级,其中0级最高。#include<linux/module.h>MODULE_LICENSE("GPLv2");intinit_module(void){printk(KERN_INFO"helloworld!\n");return0;}voidcleanup_module(void){printk(KERN_INFO"goodbyeworld!\n");}hello.c最简单的模块(一)模块卸载函数,rmmod时运行pc端Makefileobj-m=hello.oKERNELDIR:=/lib/modules/$(shelluname-r)/buildPWD:=$(shellpwd)modules: $(MAKE)-C$(KERNELDIR)M=$(PWD)modulesclean: rm-rf*.o~core.depend*.ko*.mod.c*.*.cmd rm–rf.tmp_versions*.order*.symvers要编译的结果,名字和驱动源程序相同正在运行的操作系统内核源码目录。也就是编译模块需要的环境指定源文件的位置获得当前Linux版本号开发板端的Makefileobj-m=hello.oKERNELDIR:=/root/yizhi/boot-kernel-source/iTop4412_Kernel_3.0PWD:=$(shellpwd)modules: $(MAKE)-C$(KERNELDIR)M=$(PWD)modulesclean: rm-rf*.o~core.depend*.ko*.mod.c*.*.cmd rm-rf.tmp_versions*.order*.symvers开发板上的操作系统内核源码目录。ubuntu系统屏蔽了内核信息的输出,方法:打开另一个终端,里面直接输入whiletruedodmesg–csleep1done已经把这些命令写成了一个文件,只要直接运行该shell文件即可。cd/home/xitee/drivers./kernel-show-info.sh这个终端就会每1秒查看当前系统的日志并清空,此时可以在另一个终端运行驱动,同时在这个终端看到输出。在ubuntu系统中如何随时内核的打印信息make--编译成目标文件(*.o或者*.ko)。insmodhello.ko--加载hello.ko驱动模块lsmod--查看模块是否加载rmmodhello--卸载hello模式lsmod--查看模块是否还存在内核操作步骤最简单的模块(二)#include<linux/init.h>#include<linux/module.h>MODULE_LICENSE(“DualBSP/GPL”);static__init
inthello_init(void)//自己定义的初始化函数{
printk(“<0>helloworld!Iamcoming!\n”);return0;}
static
__exitvoid
hello_exit(void)//自己定义的卸载函数{printk(“<0>seeyou!\n”);return;}module_init(hello_init);module_exit(hello_exit);hello.cinsmod时调用,利用宏module_init说明入口rmmod时调用,利用宏module_exit说明入口module_init和module_exit宏定义所在头文件static
int_
_inithello_init(void)static
void_
_exithello_exit(void)static声明,因为这种函数在特定文件之外没有其它意义__init标记,该函数只在初始化期间使用。模块装载后,将该函数占用的内存空间释放,性能优化__exit标记
该代码仅用于模块卸载,性能优化。模块参数module_param()的使用#include<linux/init.h>#include<linux/module.h>MODULE_LICENSE(“GPLv2”);staticintnum=10;staticchar*name=“xit”;module_param(num,int,S_IRUGO);module_param(name,charp,S_IRUGO);static__init
inthello_init(void{printk(KERN_INFO”helloworld!Iamcoming!\n”);printk(KERN_INFO“num=:%d\n",num);printk(KERN_INFO“name=:%s\n",name);return0;}static
__exitvoid
hello_exit(void){printk(KERN_INFO“seeyou!\n”);return;}module_init(hello_init);module_exit(hello_exit);操做步骤make--编译成目标文件(*.o或者*.ko)。insmodhello.ko--不带参数加载hello.ko驱动模块rmmodhello--卸载hello模式insmodhello.konum=20name=‘xiamen’--带参数加载驱动rmmodhello--卸载hello模式模块导出符号EXPORT_SYMBOL()的使用#include<linux/init.h>#include<linux/module.h>MODULE_LICENSE(“GPLv2”);static__init
inthello_init(void){printk(KERN_INFO”helloworld!Iamcoming!\n”);return0;}static
__exitvoid
hello_exit(void){printk(KERN_INFO“seeyou!\n”);return;}voidfunc_for_test(void){printk(KERN_INFO“justfortest!\n”);return;}EXPORT_SYMBOL(func_for_test);module_init(hello_init);module_exit(hello_exit);其他模块引用导出的符号#include<linux/init.h>#include<linux/module.h>externvoidfunc_for_test(void);MODULE_LICENSE(“GPLv2”);static__init
inttest_init(void{printk(KERN_INFO”thisistestdriver!\n”);func_for_test();return0;}static
__exitvoid
test_exit(void){printk(KERN_INFO“seeyou!\n”);return;}module_init(test_init);module_exit(test_exit);testdriver.c操做步骤1、hello_driver.c和对应的Makefile放在hello_driver目录下,编译并运行makeinsmodhello_driver.ko--加载hello.ko驱动模块2、test_driver.c和对应的Makefile放在test_driver目录下,编译并运行makeinsmodtestdriver.ko--加载testdriver.ko驱动模块3、rmmodtestdriver--卸载testdriver模式4、rmmodhello--卸载hello模式注意:如执行步骤2出现“Unknownsymbolfunc_for_test(err-22)”错误,把hello_driver目录下的Module.symvers放test_driver目录下,重新执行步骤2即可,func_for_test的符号信息就会链接进去。Linux驱动程序的实现机制Linux系统中每一类设备都有一个驱动程序,设备驱动程序存放在内核中,不同的应用可以共享这些代码。在Linux系统中,每一个设备体现为/dev目录下的一个文件一个驱动程序就是一个函数和数据结构的集合,是一组特殊的接口,封装了对设备的各种控制,应用程序通过访问对应的设备文件来调用相关的处理函数。驱动的类型在Linux中,驱动分为三种类型:字符设备(chardevice):每次进行输入输出操作时只需要传送1个字符,如鼠标、键盘、串口等。块设备(blockdevice)
:传送的数据量大,而且使用随机访问的方式传输数据,数据总是具有固定大小的块,保存在系统的缓冲块中。如硬盘,光盘。网络设备(netdevice)
:通过SOCKET访问网络设备。Linux设备文件设备文件都保存在/dev目录下,分为字符设备文件和块设备。文件每个设备文件都有主设备号和次设备号。系统通过主、次设备号找到具体的硬件。c:字符设备b:块设备主设备号次设备号Linux设备号29在内核中,设备号是一个数字,是设备的标志,由主设备号和次设备号组成。主设备号表明某一类设备,主设备号相同的设备使用相同的驱动程序;(12位)次设备号用来标识具体设备的实例。(20位)例如,系统中块设备IDE硬盘的主设备号是3,而多个IDE硬盘及其各个分区分别赋予次设备号1、2、……设备文件与设备号30Linux内核中通过主、次设备号识别一个设备,但是从应用程序程序员的角度,设备号用起来很麻烦。Linux应用程序通过设备文件来管理具体硬件。主、次设备号主:250次:0创建设备文件
设备文件的创建和普通文件不同,必须使用特殊的命令mknod来把设备映射为特别文件。其它程序需要使用这个设备的时候,对此特别文件进行操作。
用法:
mknod文件名
设备类型
主设备号
次设备号
例如:mknod/dev/ttyS0c464mknod/dev/democ2540
mknod/dev/democ2540fd=open(“/dev/demo”,O_RDWR);read(fd,buf,100);ioctl(fd,1,2);close(fd);驱动开发流程33
在驱动加载时必须完成设备的注册,并完成设备功能的登记。在驱动卸载时将设备加载。旧的设备注册函数--register_chrdev和设备函卸载数--unregister_chrdev已经很少用。moudle_init()moudle_exit()structcdev{structkobjectkobj;//内嵌的kobject对象structmodule*owner;//所属模块structfile_operations*ops;//文件操作结构体
dev_tdev;//设备号
unsignedintcount;
}cdev结构体在Linux内核中,使用cdev结构体来描述一个字符设备MAJOR(dev_tdev)MINOR(dev_tdev)MKDEV(intmajor,intminor)cdev_alloc()--动态申请一块cdev类型的内存;
也可不用该函数而直接定义一个cdev的变量cdev_init()--初始化cdev成员cdev_add()--向系统添加一个cdev,完成字符设备的注册cdev_del()--从系统中删除一个cdev,完成字符设备的注销对cdev结构体的操作函数structcdev{structkobjectkobj;//内嵌的kobject对象structmodule*owner;//所属模块structfile_operations*ops;//文件操作结构体
dev_tdev;//设备号
unsignedintcount;
}申请设备号在调用cdev_add向系统添加一个cdev前要先申请设备号,可采用两种方式:已知设备号:系统自动分配设备号:释放设备号当调用cdev_del()完成字符设备的注销后,也要调用函数释放设备号向系统申请和释放设备号intregister_chrdev_region(dev_tfrom,unsignedcount,constchar*name)intalloc_chrdev_region(dev_t*dev,unsignedbaseminor,unsignedcount,constchar*name)voidunregister_chrdev_region(dev_tfrom,unsignedcount);该结构体规定该设备可以有什么操作,对应的功能是用户空间中的对设备文件的文件操作。file_operations结构体structfile_operations{structmodule*owner;loff_t(*llseek)(structfile*,loff_t,int);ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);long(*unlocked_ioctl)(structfile*,unsignedint,unsignedlong);int(*open)(structinode*,structfile*);int(*release)(structinode*,structfile*);…}lseek()read()write()ioctl()open()close()对设备文件的操作
设备文件也是一个文件,因此,从应用程序看来,应用程序可以想使用普通文件一样使用硬件设备。常用的文件操作有:intopen(constchar*filename,intflags);
如fd=open(“/dev/demo”,O_RDWR);intclose(intfd);
如:close(fd);intread(intfd,void*buf,size_tcount);intwrite(intfd,void*buf,size_tcount);intioctl(intfd,unsignedintcmd,…);…#include<unistd.h>#include<sys/ioctl.h>#include<stdio.h>#include<stdlib.h>#include<fcntl.h>intmain(){ intfd; intresult=0;charbuf[10]; fd=open("/dev/demo",O_RDWR); if(fd<0){ printf("####DEMOdeviceopenfail####\n"); return(-1); } result=write(fd,buf,5); printf("write%dbytes\n",result); result=read(fd,buf,4); printf("read%dbytes\n",result);ioctl(fd,1,NULL) close(fd);}测试代码gcc
–o
test
test.cfile结构structfile{ structpath f_path;//文件路径 unsignedint f_flags; fmode_t f_mode;//文件的访问模式
loff_t f_pos;//文件光标的当前位置…}驱动程序与设备的接口操作含义read从设备中读取数据。write向字符设备中写入数据。unlocked_ioctl控制设备,除读写操作外的其他控制命令。open打开设备并初始化设备。release关闭设备并释放资源。llseek重新定位读写位置。mmap将设备内存映射到进程地址空间,通常只用于块设备。flush清除内容,一般只用于网络文件系统中。fsync实现内存与设备的同步,如将内存数据写入硬盘。fasync实现内存与设备之间的异步通讯。lock文件锁定,用于文件共享时的互斥访问。内核空间和用户空间之间的数据复制在用户空间不能直接访问内核空间的内存,因此借助下面两个函数:把数据从用户空间拷贝到内核空间,一般用在write函数把数据从内核空间拷贝到用户空间,一般用在read函数unsignedlongcopy_from_user(void*to,constvoid__user*from,unsignedlongcount);unsignedlongcopy_to_user(void__user*to,constvoid*from,unsignedlongcount);编写驱动,该驱动具有以下功能:在内核中有1k的存储空间。用户可以向该驱动写入数据用户可以从驱动读数据用户可以定位从哪个位置开始读数据用户可以将全部数据清0用户可以获取数据的个数用户可以将内核中的数据倒序….编写应用程序测试该驱动。虚拟字符设备驱动虚拟字符设备驱动框架#include<linux/init.h>#include<linux/module.h>#include<linux/fs.h>#include<linux/cdev.h>#include<linux/uaccess.h>MODULE_LICENSE("GPLv2");#defineDEVICE_MAJOR0#defineDEVICE_MINOR0#defineDEVICE_NUM1#defineIO_CMD_CLEAR0#defineIO_CMD_11staticintmajor=DEVICE_MAJOR;//主设备号,次设备号staticintminor=DEVICE_MINOR;structcdevvirtual_dev_cdev;module_param(major,int,S_IRUGO);//主设备号可以通过输入改变VirtuleCharDev.c默认设备号为0定义ioctl的输入命令定义系统的设备变量/****调用open()对应的函数***************/staticintvirtual_dev_open(structinode*inode,structfile*file){ printk(KERN_INFO"deviceopensucess!\n"); return0;}/****调用close()对应的函数***************/staticintvirtual_dev_release(structinode*inode,structfile*filp){ printk("devicerelease\n"); return0;}/*********write对应的函数****/staticssize_tvirtual_dev_write(structfile*file,constchar__user*buf,size_tcount,loff_t*f_ops){ printk(KERN_INFO"userwritedatatodriver\n"); return2;}/*********read()对应的函数****/staticssize_tvirtual_dev_read(structfile*file,char__user*buf,size_tcount,loff_t*f_ops){ printk(KERN_INFO"userreaddatafromdriver\n"); return1;}/******lseek()对应的函数**/staticloff_tvirtual_dev_seek(structfile*file,loff_toffset,intorig){ printk(KERN_INFO"virtual_dev_seek\n"); return0;}/******ioctl()对应的函数**/staticlongvirtual_dev_ioctl(structfile*file,unsignedintcmd,unsignedlongarg){ printk("<4>ioctlruning\n"); switch(cmd){ caseIO_CMD_CLEAR:printk("<4>runingcommand0\n");break; caseIO_CMD_1:printk("<4>runingcommand1\n");break; default:printk("<4>errorcmdnumber\n");break; } return0;}staticstructfile_operationsvirtual_dev_fops={
.owner=THIS_MODULE,
.llseek=virtual_dev_seek,
.write=virtual_dev_write,
.read=virtual_dev_read,
.unlocked_ioctl=virtual_dev_ioctl,
.open=virtual_dev_open,
.release=virtual_dev_release};/******模块初始化函数**/staticint__initvirtual_dev_init(void){intret=0;dev_tdevno;printk(KERN_INFO"virtual_dev_init!\n");if(major>0){ printk(KERN_INFO"staticadev_regionmajoris%d!\n",major); devno=MKDEV(major,minor); ret=register_chrdev_region(devno,DEVICE_NUM,"char_test");}else{/*动态注册设备号*/
ret=alloc_chrdev_region(&devno,minor,DEVICE_NUM,"char_test");
major=MAJOR(devno);/*获得主设备号*/ printk(KERN_INFO"dynamicadev_regionmajoris%d!\n",major);}
如果已经有主设备号,直接向系统申请系统分配一个尚未使用的设备号if(ret<0){ printk(KERN_INFO"register_chrdev%dfailed!\n",major);}else{
cdev_init(&virtual_dev_cdev,&virtual_dev_fops); virtual_dev_cdev.owner=THIS_MODULE; ret=cdev_add(&virtual_dev_cdev,devno,DEVICE_NUM); if(ret<0) { printk(KERN_INFO"cdev_add%disfailed!\n",ret);
unregister_chrdev_region(devno,DEVICE_NUM); }}returnret;}设备号申请成功,则向系统注册设备/******模块卸载函数**/staticvoid__exitvirtual_dev_exit(void){ dev_tdevno=MKDEV(major,minor);
cdev_del(&virtual_dev_cdev);
unregister_chrdev_region(devno,DEVICE_NUM); printk(KERN_INFO"virtual_dev_exit!\n");return;}module_init(virtual_dev_init);module_exit(virtual_dev_exit);系统注销设备系统注销设备号操作步骤:makeinsmodVirtuleCharDev.komknod/dev/virtdevc2500#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<unistd.h>#include<sys/ioctl.h>staticchar*devfile="/dev/virtdev";intMAX_LEN=32;intmain(){ intfd,i,num=0; charbuf[256]; for(i=0;i<MAX_LEN;i++){ buf[i]=i; } 测试程序gcc
–o
test_virtdev
test_virtdev.ctest_virtdev.c
fd=open(devfile,O_RDWR); if(fd<0){ printf("####virtdevdeviceopenfail####\n"); return(-1); }
num=write(fd,buf,MAX_LEN); printf("write%dbytesdatato/dev/virtdev,returnnum=%d\n",MAX_LEN,num);
num=lseek(fd,4,SEEK_SET); printf("seek4bytesfromcur,returnnum=%d\n",num);
num=read(fd,buf,MAX_LEN);printf("Read%dbytesdatafrom/dev/virtdev,returnnum=%d\n",MAX_LEN,num);
ioctl(fd,1,NULL);
ioctl(fd,4,NULL);
close(fd); return0;}测试open测试write测试lseek测试read测试ioctl,命令1和4测试close操作步骤./test_virtdev驱动内核消息测试程序消息rmmodVirtuleCharDev
编写驱动,该驱动具有以下功能:在内核中有1k的存储空间。用户可以向该驱动写入数据用户可以从驱动读数据用户可以定位从哪个位置开始读数据用户可以将全部数据清0用户可以获取数据的个数用户可以将内核中的数据倒序….编写应用程序测试该驱动。虚拟字符设备驱动虚拟字符设备驱动功能实现staticintmajor=DEVICE_MAJOR;//主设备号,次设备号staticintminor=DEVICE_MINOR;structcdevvirtual_dev_cdev;//定义1024的内存区#defineBUFFER_SIZE1024staticcharMemBuff[BUFFER_SIZE];//主设备号可以通过输入改变module_param(major,int,S_IRUGO);虚拟字符设备驱动功能实现staticssize_tvirtual_dev_write(structfile*file,constchar__user*buf,size_tcount,loff_t*f_ops){ inticount=count; unsignedlongcur_pos=*f_ops; printk(KERN_INFO"userwritedatatodriver\n"); if(cur_pos+icount>BUFFER_SIZE)
icount=BUFFER_SIZE-cur_pos;
copy_from_user(&MemBuff[cur_pos],buf,icount); *f_ops=cur_pos+icount; returnicount;num=write(fd,buf,MAX_LEN);虚拟字符设备驱动功能实现staticssize_tvirtual_dev_read(structfile*file,char__user*buf,size_tcount,loff_t*f_ops){ inticount=count; unsignedlongcur_pos=*f_ops; printk(KERN_INFO"userreaddatafromdriver\n"); if(cur_pos+icount>BUFFER_SIZE) icount=BUFFER_SIZE-cur_pos;
copy_to_user(buf,&MemBuff[cur_pos],icount); *f_ops=cur_pos+icount; returnicount;num=read(fd,buf,MAX_LEN);虚拟字符设备驱动功能实现staticloff_tvirtual_dev_seek(structfile*file,loff_toffset,intorig){ intret=0; printk(KERN_INFO"virtual_dev_seek\n"); switch(orig){ case0: if(offset<0||offset>BUFFER_SIZE){ret=-1;} else{
file->f_pos=offset; ret=offset; } break;
num=lseek(fd,4,SEEK_SET);虚拟字符设备驱动功能实现 case1: if(file->f_pos+offset<0||file->f_pos+offset>BUFFER_SIZE) {ret=-1;} else{
file->f_pos=file->f_pos+offset; ret=file->f_pos; } break;
default: printk(KERN_INFO“wrongposition\n"); ret=-1;break; } returnret;}num=lseek(fd,4,SEEK_CUR);虚拟字符设备驱动功能实现staticlongvirtual_dev_ioctl(structfile*file,unsignedintcmd,unsignedlongarg){ switch(cmd){ caseIO_CMD_CLEAR: printk("<4>runingcommand1\n");
memset(MemBuff,0,BUFFER_SIZE); break; caseIO_CMD_2:printk("<4>runingcommand2\n");break; default: printk("<4>errorcmdnumber\n");break; } return0;}num=ioctl(fd,1,NULL);操作步骤makeinsmodVirtuleCharDev.ko./test_virtdev观察驱动内核消息和测试程序消息,检验驱动功能的正确性rmmodVirtuleCharDev
关于IOCTL命令cmd的命名法staticlongvirtual_dev_ioctl(structfile*file,unsignedintcmd,unsignedlongarg){ switch(cmd){ caseIO_CMD_CLEAR: printk("<4>runingcommand1\n"); memset(MemBuff,0,BUFFER_SIZE); break; caseIO_CMD_2:printk("<4>runingcommand2\n");break; default: printk("<4>errorcmdnumber\n");break; } return0;}关于IOCTL命令的命名法#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<unistd.h>#include<sys/ioctl.h>staticchar*devfile="/dev/virtdev";intmain(){ intfd,i,num; fd=open(devfile,O_RDWR); if(fd<0){ printf("####virtdevdeviceopenfail####\n"); return(-1); }i=ioctl(fd,1,NULL); i=ioctl(fd,2,NULL); close(fd); return0;}
命令2的调试信息没有打印出来!每个cmd被分为了4个段,每一段都有各自的意义,随意定cmd的问题:1、有一些cmd被系统过滤2、若有两个不同的设备,但它们的ioctl的cmd相同,如果不小心打开错设备,并且调用ioctl,容易导致系统错误。序数:用这个数来给自己的命令编号,占8bit(_IOC_NRBITS),用户自己使用,可以从1开始排序幻数:说是个0~0xff的数,占8bit(_IOC_TYPEBITS)。这个数是用来区分不同的驱动的,ioctl-number.txt中有说明那些幻数被使用过数据传输方向:占2bit(_IOC_DIRBITS)。如果涉及到要传参,内核要求描述一下传输的方向,传输的方向是以应用层角度来描述。1)_IOC_NONE:值为0,无数据传输。2)_IOC_READ:值为1,从设备驱动读取数据。3)_IOC_WRITE:值为2,往设备驱动写入数据。4)_IOC_READ|_IOC_WRITE:双向数据传输。数据大小:与体系结构相关,ARM下占14bit(_IOC_SIZEBITS),如果数据是int,内核给这个赋的值就是sizeof(int)。内核用于生成cmd的命令:_IO(type,nr)//没有参数的命令_IOR(type,nr,size)//该命令是从驱动读取数据_IOW(type,nr,size)//该命令是从驱动写入数据_IOWR(type,nr,size)//双向数据传输内核用于拆分cmd的命令:_IOC_DIR(cmd)//从命令中提取方向_IOC_TYPE(cmd)//从命令中提取幻数_IOC_NR(cmd)//从命令中提取序数_IOC_SIZE(cmd)//从命令中提取数据大小//VirtualCharDev.h#include<linux/ioctl.h>//内核空间#defineIOC_MAGIC'x‘/*定义设备类型*/#defineIO_CMD_CLEAR_IO(IOC_MAGIC,0)#defineIO_CMD_2_IO(IOC_MAGIC,1)#defineIOC_MAXNR2//test_demo.h#include<sys/ioctl.h>//用户空间#defineIOC_MAGIC'x‘/*定义设备类型*/#defineIO_CMD_CLEAR_IO(IOC_MAGIC,0)#defineIO_CMD_2_IO(IOC_MAGIC,1)#defineIOC_MAXNR2驱动程序VirtualCharDev.c修改#include"VirtualCharDev.h“//#defineIO_CMD_CLEAR1//#defineIO_CMD_22staticlongvirtual_dev_ioctl(structfile*file,unsignedintcmd,unsignedlongarg){ switch(cmd){ caseIO_CMD_CLEAR: printk("<4>runingcommand1\n"); memset(MemBuff,0,BUFFER_SIZE); break; caseIO_CMD_2:printk("<4>runingcommand2\n");break; default: printk("<4>errorcmdnumber\n");break; } return0;}修改测试程序#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<unistd.h>#include“test_demo.h”staticchar*devfile="/dev/virtdev";intmain(){ intfd,i,num; fd=open(devfile,O_RDWR); if(fd<0){ printf("####virtdevdeviceopenfail####\n"); return(-1); }i=ioctl(fd,IO_CMD_CLEAR,NULL); i=ioctl(fd,IO_CMD_2,NULL); close(fd); return0;}
自动生成设备节点#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<unistd.h>#include“test_demo.h”staticchar*devfile="/dev/virtdev";intmain(){ intfd,i,num; fd=open(devfile,O_RDWR); if(fd<0){ printf("####virtdevdeviceopenfail####\n"); return(-1); }i=ioctl(fd,IO_CMD_CLEAR,NULL); i=ioctl(fd,IO_CMD_2,NULL); close(fd); return0;}
修改驱动程序#include<linux/init.h>#include<linux/module.h>#include<linux/fs.h>#include<linux/cdev.h>#include<linux/uaccess.h>#include<linux/device.h>MODULE_LICENSE("GPLv2");#defineDEVICE_MAJOR0#defineDEVICE_MINOR0#defineDEVICE_NUM1#defineDEVICE_NAME“virtdev"staticstructclass*virt_class;staticintmajor=DEVICE_MAJOR;//主设备号,次设备号staticintminor=DEVICE_MINOR;structcdevvirtual_dev_cdev;module_param(major,int,S_IRUGO);//主设备号可以通过输入改变VirtuleCharDev.c定义设备名称/******模块初始化函数**/staticint__initvirtual_dev_init(void){… cdev_init(&virtual_dev_cdev,&virtual_dev_fops); virtual_dev_cdev.owner=THIS_MODULE; ret=cdev_add(&virtual_dev_cdev,devno,DEVICE_NUM); if(ret<0) { printk(KERN_INFO"cdev_add%disfailed!\n",ret); unregister_chrdev_region(devno,DEVICE_NUM); }
else{
virt_class=class_create(THIS_MODULE,DEVICE_NAME);
device_create(virt_class,NULL,devno,NULL,DEVICE_NAME); }…} 向内核申请设备号,并登记了字符设备化后,创建设备文件节点修改驱动程序/******模块卸载函数**/staticvoid__exitvirtual_dev_exit(void){ dev_tdevno=MKDEV(major,minor);
device_destroy(virt_class,devno);
class_unregister(virt_class);
class_destroy(virt_class);
cdev_del(&virtual_dev_cdev);
unregister_chrdev_region(devno,DEVICE_NUM); printk(KERN_INFO"virtual_dev_exit!\n");return;});系统注销设备系统注销设备号删除设备节点修改驱动程序驱动任务:1、输入虚拟字符设备驱动和测试程序并运行,分析驱动代码与测试代码之间的关系。2、在ioctl增加命令:输出自己的学号和姓名。3、在ioctl增加命令:将曾经写入的数据倒序。4、在测试程序中调动对应的ioctl进行测试。作业截图:ioctl的代码、测试代码、运行结果编写LED驱动以及测试程序开发板的LED灯电路
GPIO配置参数宏定义GPIO宏定义文件都是由原厂提供,是已经做好的,属于BSP板级开发包。在源码目录中arch/arm/mach-exynos/include/mach/gpio-exynos4.h所有的GPIO都已经定义Led的宏定义:EXYNOS4_GPL2(0)EXYNOS4_GPK1(1)配置函数在源码目录中使用命令arch/arm/plat-samsung/include/plat/gpio-cfg.h”一般来说带有s3cxxx的函数就是三星平台能够通用的函数。s3c_gpio_cfgpin(unsignedintpin,unsignedintto);参数pin,管脚参数to,配置参数。GPIO的功能配置S3C_GPIO_INPUTS3C_GPIO_OUTPUTS3C_GPIO_SFN()s3c_gpio_cfgpin(EXYNOS4_GPL2(0),S3C_GPIO_OUTPUT);s3c_gpio_cfgpin(EXYNOS4_GPK1(1),S3C_GPIO_OUTPUT);将GPIO配置为输出模式之后,还需要给GPIO赋值,一般就是高电平和低电平两种,高电平为1和低电平为0函数所需要的头文件:linux/gpio.hGPIO赋值voidgpio_set_value(unsignedgpio,intvalue)intgpio_get_value(unsignedgpio)gpio_set_value(EXYNOS4_GPL2(0),1)gpio_set_value(EXYNOS4_GPK1(1),0)编写简单应用调用LED管脚,并测试#include<linux/kernel.h>#include<linux/init.h>#include<linux/module.h>#include<linux/fs.h>#include<linux/cdev.h>#include<linux/uaccess.h>#include<linux/gpio.h>#include<plat/gpio-cfg.h>#include<mach/gpio.h>#include<mach/gpio-exynos4.h>#include<linux/device.h>#include"leds-ioctl.h"MODULE_LICENSE("GPLv2");#defineDEVICE_MAJOR0#defineDEVICE_MINOR0#defineDEVICE_NUM1#defineDEVICE_NAME“led_ctl“//定义设备节点名称leddev.c//定义LED的管脚#defineLED1EXYNOS4_GPL2(0)#defineLED2EXYNOS4_GPK1(1)//主设备号,次设备号staticintmajor=DEVICE_MAJOR;staticintminor=DEVICE_MINOR;structcdevleds_dev_cdev;staticstructclass*leds_class;//主设备号可以通过输入改变module_param(major,int,S_IRUGO);/*************************************************************/staticintleds_dev_open(structinode*inode,structfile*file){
s3c_gpio_cfgpin(LED1,S3C_GPIO_OUTPUT);
s3c_gpio_cfgpin(LED2,S3C_GPIO_OUTPUT);
gpio_set_value(LED1,1);
gpio_set_value(LED2,1); printk(KERN_INFO"ledsdeviceopensucess!\n"); return0;}/**************************************************************/staticintleds_dev_release(structinode*inode,structfile*filp){ printk("ledsdevicerelease\n"); return0;}设置GPIO为输出初始化LED为亮staticlongleds_dev_ioctl(structfile*fi
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 个人诊所合同标准文本
- 知识管理系统规划计划
- 住宅加装电梯合同标准文本
- 儿童合同标准文本标准文本玩具
- 2025年住宅楼购房合同全文(合同范本)
- 2025房地产项目借款合同协议
- 宣传策划方案(6篇)
- 监理工程师考试首要知识点试题及答案
- 2025年中外合作开发合同
- 短视频委托代运营服务合同-模板
- 2024年扬州市事业单位统考招聘笔试真题
- 高中主题班会 高一下学期《自律自主自觉-成就更好的自己》主题班会教案
- 舞蹈简史考试题及答案
- 3.1公民基本权利 课件 2024-2025学年统编版道德与法治八年级下册
- 2025年浙江安防职业技术学院单招职业倾向性考试题库汇编
- 2025年ACR痛风管理指南
- 2024年毕节市东关坡粮食储备有限公司社会招聘笔试真题
- DBJ50T-309-2018 地下管网危险源监控系统技术标准
- 广东省汕头市2025年普通高考第一次模拟考试生物学试题(含答案)
- 酒店服务人员职业道德课件
- 河道清淤人员培训
评论
0/150
提交评论