u-boot的任务是启动内核,内核的任务是启动应用程序 ,应用程序会涉及不少文件和硬件操做(固然不会直接操做硬件),好比读写文件,点灯、获取按键值。 好比对于控制led灯的用户程序与驱动程序,最简单的实现方法是: 应用程序中须要打开led灯,就须要open函数,在内核中的驱动程序中也有对应的led_open函数,这个led_open函数就是用来负责初始化led的引脚功能,应用程序中要调用read函数读取led灯的状态,内核中的驱动程序也有led_read函数。这是应用程序与内核中驱动程序一种最简单的对应方式. 那么应用程序中的open、read函数最终怎样调用到驱动程序中的led_open、led_read呢,中间有哪些东西? 在linux中共有4层软件,以下图: 如下名词解释:node
例如:linux
int main() { int fd1 fd2; int val=1; fd1 = open(“/dev/led”,O_RDWR); //打开led write(fd1, &val, 4); fd2 = open(“hello.txt”,O_RDWR); //打开文本 write(fd2, &val, 4); }
问:上面的应用程序主要实现点灯与打开文本文件,都是用的一样的函数。可是点灯与打开文本文件的行为显然不同。那么谁来实现这些不同的行为呢? 答:对于LED灯,有led_open驱动程序。对于文本文件存在于flash设备上,也有对于的驱动程序。system_open、system_read最终会根据打开的不一样文件,找到底层的不一样驱动程序,而后调用驱动程序中的硬件操做函数,好比led_open来实现对具体硬件设备的操做。这就是整个的字符设备驱动程序框架。 例如LED,以下图: 在应用层应用程序中有open、read、write 一样,在驱动程序中也对应有led_open、led_read、led_write 剩下的就是驱动框架了。数组
本节目的: <font color=#FF0000>先讲解驱动框架,而后写出first_drv驱动程序,来打印一些信息</font> 写出first_drv驱动程序须要如下几步: 1)写出驱动程序first_drv_open first_drv_write 2)须要定义file_operations结构体来封装驱动函数first_drv_open first_drv_write 对于字符设备来讲,经常使用file_operations如下几个成员: 3) 模块加载函数,经过函数 register_chrdev(major, “first_drv”, &first_drv_fops) 来注册字符设备 4)写驱动的first_drv_init 入口函数来调用这个register_chrdev()注册函数, 5)经过module_init()来修饰入口函数,使内核知道有这个函数 6)写驱动的first_drv_exit出口函数,调用这个unregister_chrdev()函数卸载, 7) 经过module_exit()来修饰出口函数 8) 模块许可证声明, 最多见的是以MODULE_LICENSE( "GPL v2" )来声明 接下来咱们编写并调试驱动程序。网络
代码以下:框架
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/irq.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #include <asm/uaccess.h> #include <asm/io.h> /*1写出驱动程序first_drv_open first_drv_write */ /*inode结构表示具体的文件,file结构体用来追踪文件在运行时的状态信息。*/ static int first_drv_open(struct inode *inode, struct file *file) { printk(“first_drv_open\n”); //打印,在内核中打印只能用printk() return 0; } /*参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度,ppos为当前的偏移位置,这个值一般是用来判断写文件是否越界*/ static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { printk(“first_drv_write\n”); //打印,在内核中打印只能用printk() return 0; } /*2定义file_operations结构体来封装驱动函数first_drv_open first_drv_write */ static struct file_operations first_drv_fops = { .owner = THIS_MODULE, //被使用时阻止模块被卸载 .open = first_drv_open, .write = first_drv_write, }; /*4写first_drv_init入口函数来调用这个register_chrdev()注册函数*/ int first_drv_init(void) { /*3 register_chrdev注册字符设备,并设置major=111*/ /*若是设置major为0,表示由内核动态分配主设备号,函数的返回值是主设备号*/ register_chrdev (111, “first_drv”, &first_drv_fops); //111:主设备号,”first_drv”:设备名 /*register_chrdev做用:在VFS虚拟文件系统中找到字符设备,而后经过主设备号找到内核数组里对应的位置,最后将设备名字和fops结构体填进去*/ return 0; } /*5 module_init修饰入口函数*/ module_init(first_drv_init); /*6 写first_drv_exit出口函数*/ void first_drv_exit(void) { unregister_chrdev (111, “first_drv”); //卸载驱动,只须要主设备号和设备名就行 } /*7 module_exit修饰出口函数*/ module_exit(first_drv_exit); /*8许可证声明, 描述内核模块的许可权限,若是不声明LICENSE,模块被加载时,将收到内核被污染 (kernel tainted)的警告。*/ MODULE_LICENSE( "GPL v2" );
KERN_DIR = /work/system/linux-2.6.22.6 //依赖的内核目录,前提内核是编译好的 all: make -C $(KERN_DIR) M=`pwd` modules // M=`pwd`:指定当前目录 //make -C $(KERN_DIR) 表示将进入(KERN_DIR)目录,执行该目录下的Makefile //等价于在linux-2.6.22.6目录下执行: make M=(当前目录) modules // modules:要编译的目标文件 clean: make -C $(KERN_DIR) M=`pwd` modules clean rm -rf modules.order obj-m += frist_drv.o //obj-m:内核模块文件,指将myleds.o编译成myleds.ko
1)make,编译生成frist_drv.ko文件 2)开发板经过nfs网络文件系统来加载frist_drv.ko 注:加载以前首先经过 cat /proc/devices来查看字符主设备号111是否被占用,而后经过 insmod first_drv.ko来挂载, 经过 cat /proc/devices就能看到first_drv已挂载好函数
测试程序first_driver_test.c代码以下测试
#include <sys/types.h> //调用sys目录下types.h文件 #include <sys/stat.h> //stat.h获取文件属性 #include <fcntl.h> #include <stdio.h> /*输入”./first_driver_test”, agc就等于1, argv[0]= first_driver_test */ /*输入”./first_driver_test on”, agc就等于2, argv[0]= first_driver_test,argv[1]=on; */ int main(int argc,char **argv) { int fd1, fd2; int val=1; fd1 = open("/dev/xxx",O_RDWR); //打开/dev/xxx设备节点 if(fd1<0) //没法打开,返回-1 printf("can't open%d!\n", fd1); else printf("can open%d!\n", fd1); //打开,返回文件描述符 write(fd1, &val, 4); //写入数据1 return 0; }
1)经过“arm-linux-gcc -o first_driver_text first_driver_test.c”指令生成执行文件 2)回到板子串口上使用./first_driver_test来运行,发现若是open()打不开,会返回-1 打印信息:spa
can't open-1!
缘由:这是由于咱们没有建立dev/xxx这个设备节点,而后咱们来建立,使它等于刚刚挂载好的first_drv模块。 3)运行指令:指针
mknod -m 660 /dev/xxx c 111 0 // first_drv模块的主设备号=111 ./first_driver_test
打印信息:调试
first_drv_open can open3! first_drv_write
经过打印信息发现测试程序里的open()函数调用了驱动中的first_drv_open(),write()函数调用了驱动中的first_drv_write(), 其中open()函数返回值为3,是由于描述符0,1,2都已经被控制台占用了,因此从3开始
除了静态装载驱动外,还能够动态装载,让系统自动为咱们驱动设备自动分配设备号 2.5.一、修改first_drv_init入口函数和first_drv_exit 出口函数: 代码以下:
int major; //定义一个全局变量,用来保存主设备号 int first_drv_init(void) { /*设置major为0,由内核动态分配主设备号,函数的返回值是主设备号*/ major =register_chrdev (0, “first_drv”, &first_drv_fops); return 0; } void first_drv_exit(void) { unregister_chrdev (major, “first_drv”); //卸载驱动, 将major填入便可 }
经过动态分配得出它的主设备号是252(此数字随机分配),而后重创252的测试程序 运行指令:
rm dev/xxx mknod -m 660 /dev/xxx c 252 0 ./first_driver_test
打印信息:
first_drv_open can open3! first_drv_write
2.5.二、每次都要手工建立设备节点,你们确定也会以为这样作太麻烦了。 改进方法:可使用自动建立设备节点,Linux有udev、mdev的机制,而咱们的ARM开发板上移植的busybox有mdev机制,而后mdev机制会经过class类来找到相应类的驱动设备来自动建立设备节点 (前提须要有mdev) 问:在哪里设置了mdev机制? 答:在制做根文件系统之使用里有介绍 2.5.三、接下来使用insmod自动建立设备节点, rmmod自动注销设备节点 1)首先建立一个class设备类,class是一个设备的高级视图,它抽象出低级的实现细节,而后在class类下,建立一个class_device,即类下面建立类的设备:(在C语言中class就是个结构体)
static struct class *firstdrv_class; //建立一个class类 static struct class_device *firstdrv_class_devs; //建立类的设备
2)在first_drv_init入口函数中添加:
firstdrv_class= class_create(THIS_MODULE,"firstdrv"); //建立类,它会在sys/class目录下建立firstdrv_class这个类 firstdrv_class_devs=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"xyz"); //建立类设备,会在sys/class/firstdrv_class类下建立xyz设备,而后mdev经过这个自动建立/dev/xyz这个设备节点,
3)在first_drv_exit出口函数中添加:
class_device_unregister(firstdrv_class_devs); //注销类设备,与class_device_create对应 class_destroy(firstdrv_class); //注销类,与class_create对应
从新编译insmod后,会发如今/dev下自动的建立了xyz设备节点 其中在sys/class里有各类类的设备, 好比sys/class/fristdev下就有xyz 而后mdev经过insmod xxx 就去class找到相应类的驱动设备来自动建立设备节点 问:为何内容一更改,mdv就能自动运行建立设备节点呢? 答:是由于之前建立根文件系统时候,在etc/init.d/rcS里添加了这么一段:
echo /sbin/mdev > /proc/sys/kernel/hotplug //支持热拔插
而后kernel每当设备出现变更时,调用/sbin/mdev来处理对应的信息,使mdev应用程序操做/dev目录下的设备,进行添加或删除 4)再修改测试程序里open函数,将/dev/xxx改成/dev/xyz,这样就测试模块,就不须要再mknod了. 驱动程序first_drv_open first_drv_write中只是打印数据,接下便开始来点亮LED.
本节目的: <font color=#FF0000>在上一节搭建的驱动框架下添加硬件的操做</font> 硬件的操做(控制LED)主要分为以下几步: 1)看原理图,肯定引脚 2)看2440手册 3)写代码(须要使用ioremap()函数映射虚拟地址,在linux中只能使用虚拟地址) 4)修改上一节的测试程序 5)使用次设备号来控制设备下不一样的灯
看原理图能够肯定: LED1 ->GPF4 LED2 ->GPF5 LED3 ->GPF6
配置GPFCON15:0的位[8:9]、位[10:11]、位[12:13] 都等于0x01(输出模式) 控制GPFDAT7:0中的位4~6来使灯亮灭(低电平亮)
1)添加全局变量:
volatile unsigned long *GPFcon=NULL; volatile unsigned long *GPFdat=NULL;
2)first_drv_init入口函数中使用ioremap()映射虚拟地址:
GPFcon = ioremap(0x56000050, 16); //ioremap:物理地址映射,返回虚拟地址 GPFdat=GPFcon+1; //long:32位,因此GPFdat=0x56000050+(32/8)
3)first_drv_exit出口函数中注销虚拟地址:
iounmap(GPFcon); //注销虚拟地址
4)first_drv_open函数中添加配置GPFCON:
*GPFcon&=~ ((0X11<<8)| (0X11<<10)| (0X11<<12)); *GPFcon|= ((0X01<<8)| (0X01<<10)| (0X01<<12));
5)first_drv_write函数中添加拷贝应用层数据,而后来控制GPFDAT:
/*copy_to_user():将数据上给用户*/ copy_from_user(&val,buf,count); //从用户(应用层)拷贝数据 if(val==1) //点灯(低电平亮) { *GPFdat&=~((0X1<<4)| (0X1<<5)| (0X1<<6)); } else //灭灯 { *GPFdat|=((0X1<<4)| (0X1<<5)| (0X1<<6)); }
代码以下:
int main(int argc,char **argv) //argc:参数个数,argv数组 { int fd1, fd2; int val=1; fd1 = open("/dev/xyz",O_RDWR); //打开/dev/xxx设备节点 if(fd1<0) //没法打开,返回-1 printf("can't open%d!\n", fd1); if(argc!=2) { printf("Usage:\n"); printf("%s <on|off>",argv[0]); return 0; } if(strcmp(argv[1],"on")==0) //开灯 { printf("led on...\n"); val=1; } else //关灯 { printf("led off...\n"); val=0; } write(fd1, &val, 4); return 0; }
当输入first_driver_text on点3个灯, 不然关3个灯 若参数不等于2时,不能控制点灯 问:若是咱们想分别控制不一样的灯,该怎么作? 答:可使用次设备号,次设备号就是用来区分同一设备下不一样子设备
咱们先来看下面两个函数MAJOR和MINOR,分别是提取主次设备号
minor=MINOR(inode->i_rdev); //open函数中提取次设备号 major=MAJOR(inode->i_rdev); //open函数中提取主设备号 minor=MINOR (file->f_dentry->d_inode->i_rdev); //write/read函数中提取次设备号 major= MAJOR (file->f_dentry->d_inode->i_rdev); //write/read函数中提取主设备号
思路以下: 在测试程序中: 经过dev[1]来open打开不一样的子设备节点,而后经过dev[2]来write写入数据 实例: first_driver_text led1 on //点亮led1 在first_dev.c驱动文件中: first_drv_init函数中建立不一样的子设备节点 first_drv_exti函数中注销不一样的子设备节点 first_drv_open函数中经过MINOR(inode->i_rdev)来初始化不一样的灯 first_drv_write函数中经过MINOR(file->f_dentry->d_inode->i_rdev)来控制不一样的灯 以下图,insmod后自动注册3个设备节点 测试程序以下:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> /* * ledtest <dev> <on|off> */ void print_usage(char *file) //报错打印帮助 { printf("Usage:\n"); printf("%s <dev> <on|off>\n",file); printf("eg. \n"); printf("%s /dev/leds on\n", file); printf("%s /dev/leds off\n", file); printf("%s /dev/led1 on\n", file); printf("%s /dev/led1 off\n", file); } int main(int argc, char **argv) { int fd; char* filename; char val; if (argc != 3) { print_usage(argv[0]); return 0; } filename = argv[1]; fd = open(filename, O_RDWR); if (fd < 0) { printf("error, can't open %s\n", filename); return 0; } if (!strcmp("on", argv[2])) { // 亮灯 val = 0; write(fd, &val, 1); } else if (!strcmp("off", argv[2])) { // 灭灯 val = 1; write(fd, &val, 1); } else //数据输入错误,打印帮助提示 { print_usage(argv[0]); return 0; } return 0; }
驱动程序以下:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/irq.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #include <asm/uaccess.h> #include <asm/io.h> static struct class *firstdrv_class; //建立一个class类 static struct class_device *firstdrv_class_devs[4]; //建立类的设备,led,led1,led2,led3 volatile unsigned long *GPFcon=NULL; volatile unsigned long *GPFdat=NULL; /*1写出驱动程序first_drv_open first_drv_write */ static int first_drv_open(struct inode *inode, struct file *file) { int minor=MINOR(inode->i_rdev); printk("first_drv_open\n"); //打印,在内核中打印只能用printk() GPFcon = ioremap(0x56000050, 16); //ioremap:物理地址映射,返回虚拟地址 GPFdat=GPFcon+1; //long:32位,因此GPFdat=0x56000050+(32/8) switch(minor) { case 0: //进入led设备,控制全部led *GPFcon&=~ ((0X3<<8)| (0X3<<10)| (0X3<<12)); *GPFcon|= ((0X01<<8)| (0X01<<10)| (0X01<<12)); break; case 1: //进入led1设备,控制 led1 *GPFcon&=~ ((0X3<<8) ); *GPFcon|= (0X1<<8) ; break; case 2: //进入led2设备,控制 led2 *GPFcon&=~ ((0X3<<10) ); *GPFcon|= (0X1<<10) ; break; case 3: //进入led3设备,控制 led3 *GPFcon&=~ ((0X3<<12) ); *GPFcon|= ((0X1<<12) ); break; } return 0; } /*参数filp为目标文件结构体指针,buffer为要写入文件的信息缓冲区,count为要写入信息的长度,ppos为当前的偏移位置,这个值一般是用来判断写文件是否越界*/ static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; int minor=MINOR(file->f_dentry->d_inode->i_rdev); copy_from_user(&val,buf,count); //经过用户(应用层)拷贝数据 switch(minor) { case 0: //进入led设备,控制全部led printk("led0,%d\n",val); if(val) //开灯 { *GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6)); *GPFdat|= ((0X0<<4)| (0X0<<5)| (0X0<<6)); } else //关灯 { *GPFdat&=~ ((0X1<<4)| (0X1<<5)| (0X1<<6)); *GPFdat|= ((0X1<<4)| (0X1<<5)| (0X1<<6)); } break; case 1: //进入led1设备,控制 led1 printk("led1,%d\n",val); if(val) //开灯 { *GPFdat&=~ (0X1<<4); *GPFdat|= (0X0<<4); } else //关灯 { *GPFdat&=~ (0X1<<4); *GPFdat|= (0X1<<4); } break; case 2: //进入led2设备,控制 led2 printk("led2,%d\n",val); if(val) //开灯 { *GPFdat&=~ (0X1<<5); *GPFdat|= (0X0<<5); } else //关灯 { *GPFdat&=~ (0X1<<5); *GPFdat|= (0X1<<5); } break; case 3: //进入led3设备,控制 led3 printk("led3,%d\n",val); if(val) //开灯 { *GPFdat&=~ (0X1<<6); *GPFdat|= ( 0X0<<6); } else //关灯 { *GPFdat&=~ (0X1<<6); *GPFdat|= (0X1<<6); } break; } return 0; } /*2定义file_operations结构体来封装驱动函数first_drv_open first_drv_write */ static struct file_operations first_drv_fops = { .owner = THIS_MODULE, //被使用时阻止模块被卸载 .open = first_drv_open, .write = first_drv_write, }; int major; //定义一个全局变量,用来保存主设备号 int first_drv_init(void) { int i; /*3 register_chrdev注册字符设备*/ /*若是设置major为0,表示由内核动态分配主设备号,函数的返回值是主设备号*/ major=register_chrdev (0, "first_drv", &first_drv_fops); firstdrv_class= class_create(THIS_MODULE,"firstdrv"); //建立类,它会在sys目录下建立firstdrv这个类 firstdrv_class_devs[0]=class_device_create(firstdrv_class,NULL,MKDEV(major,0),NULL,"led"); //建立类设备,它会在firstdrv_class类下建立led设备,而后mdev经过这个自动建立/dev/xyz这个设备节点 for(i=1;i<4;i++) //建立led1 led2 led3 设备节点,控制led1 led2 led3 { firstdrv_class_devs[i]=class_device_create(firstdrv_class,NULL,MKDEV(major,i),NULL,"led%d",i); } return 0; } /*6 写first_drv_exit出口函数*/ void first_drv_exit(void) { int i; unregister_chrdev (major, "first_drv"); //卸载驱动,只须要主设备号和设备名就行 class_destroy(firstdrv_class); //注销类,与class_create对应 for(i=0;i<4;i++) //注销类设备led,led1,led2,led3 class_device_unregister(firstdrv_class_devs[i]); iounmap(GPFcon); //注销虚拟地址 } /*5 module_init修饰入口函数*/ module_init(first_drv_init); /*7 module_exit修饰出口函数*/ module_exit(first_drv_exit); MODULE_LICENSE("GPL v2"); //声明许可证
本节目的: <font color=#FF0000>写second程序,内容:经过查询方式驱动按键</font>
1)写file_oprations结构体,second_drv_open函数,second_drv_read函数 2)写入口函数,并自动建立设备节点,修饰入口函数 3)写出口函数,并自动注销设备节点,修饰出口函数 4)写MODULE_LICENSE(“GPL v2”)声明函数许可证 5)在入口函数中,利用class_create和class_device_create自动建立设备节点 6)在出口函数中,利用class_destroy和class_device_unregister注销设备节点
写Makefile并编译后,放在板子上insmod后,看看lsmod、cat /porc/devices、 ls -l /dev/second是否加载成功
1)看原理图和2440手册,肯定用什么寄存器控制按键引脚 肯定按键0~3分别是GPF0,GPF2,GPG3,GPG11 因为是使用查询模式,并非外部中断模式 因此配置 GPFCON(0x56000050)的位[0:1]、位[4:5]等于0x00(输入模式) GPGCON(0x56000060)的位[6:7]、位[22:23]等于0x00(输入模式) 经过GPGDAT (0x56000054) 和GPGDAT(0x56000064)来查询按键状态 2)写代码 init入口函数中使用ioremap()函数映射寄存器虚拟地址 exit出口函数中使用iounmap()函数注销虚拟地址 open函数中配置GPxCON初始化按键 read函数中先检查读出的字符是不是4个,而后获取GPxDAT状态,用key_vals[4]数组保存4个按键值,最后使用 copy_to_user(buf, key_vals,sizeof(key_vals)) 上传给用户层
1)写测试程序Secondtest.c 此测试程序使用read(fd,val,sizeof(val));函数读取内核层的数据 使用此测试程序的用法就是./Secondtest 2)后台运行测试程序 使用./ Secondtest & 后台运行测试程序 后台会一直运行这个程序,当咱们有按键按下时,就会打印数据出来,以下图: 3)top指令观察CPU占有率 经过top命令能够发现这个./ Secondtext占了CPU的99%时间 缘由:咱们的Secondtext测试程序一直在while中经过查询方式读取按键状态,这样的效率是很是低的. 接下来开始使用中断方式来改进按键驱动程序,提升效率。
Secondtest测试程序代码以下:
#include <sys/types.h> //调用sys目录下types.h文件 #include <sys/stat.h> //stat.h获取文件属性 #include <fcntl.h> #include <stdio.h> #include <string.h> /*secondtext while一直获取按键信息 */ int main(int argc,char **argv) { int fd,ret; unsigned char val[4]; fd=open("/dev/buttons",O_RDWR); if(fd<0) { printf("can't open!!!\n"); return -1; } while(1) { ret=read(fd,val,sizeof(val)); if(ret<0) { printf("read err!\n"); continue; } if((val[0]&val[1]&val[2]&val[3])==0) printf("key0=%d,key1=%d,key2=%d,key3=%d\n",val[0],val[1],val[2],val[3]); } return 0; }
second.c按键驱动代码以下:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/irq.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> #include <asm/uaccess.h> #include <asm/io.h> static struct class *seconddrv_class; //建立一个class类 static struct class_device *seconddrv_class_devs; //建立类的设备 volatile unsigned long *GPFcon; volatile unsigned long *GPFdat; volatile unsigned long *GPGcon; volatile unsigned long *GPGdat; static int second_drv_open(struct inode *inode, struct file *file) { /*初始化按键*/ /* 配置 GPFCON(0x56000050)的位[0:1]、位[4:5]等于0x00(输入模式) GPGCON(0x56000060)的位[6:7]、位[22:23]等于0x00*/ *GPFcon&=~((0x3<<0)|(0x3<<4)); *GPGcon&=~((0x3<<6)|(0x3<<22)); return 0; } static int second_drv_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { unsigned char key_vals[4]; /*按键0~3分别是GPF0,GPF2,GPG3,GPG11*/ if(count!=sizeof(key_vals)) return EINVAL; key_vals[0]=(*GPFdat>>0)&0X01; key_vals[1]=(*GPFdat>>2)&0X01; key_vals[2]=(*GPGdat>>3)&0X01; key_vals[3]=(*GPGdat>>11)&0X01; /*上传给用户层*/ if(copy_to_user(buf,key_vals,sizeof(key_vals))) return EFAULT; return 0; } static struct file_operations second_drv_fops={ .owner = THIS_MODULE, .open = second_drv_open, .read = second_drv_read,}; volatile int second_major; //保存主设备号 static int second_drv_init(void) { second_major=register_chrdev(0,"second_drv",&second_drv_fops); //建立驱动 seconddrv_class=class_create(THIS_MODULE,"second_dev"); //建立类名 seconddrv_class_devs=class_device_create(seconddrv_class, NULL, MKDEV(second_major,0), NULL,"buttons"); /*申请虚拟地址,而后配置寄存器*/ /* GPFCON(0x56000050) GPGCON(0x56000060) */ GPFcon=ioremap(0x56000050,16); GPFdat=GPFcon+1; GPGcon=ioremap(0x56000060,16); GPGdat=GPGcon+1; return 0; } static int second_drv_exit(void) { unregister_chrdev(second_major,"second_drv"); //卸载驱动 class_device_unregister(seconddrv_class_devs); //卸载类设备 class_destroy(seconddrv_class); //卸载类 /*注销虚拟地址*/ iounmap(GPFcon); iounmap(GPGcon); return 0; } module_init(second_drv_init); module_exit(second_drv_exit); MODULE_LICENSE("GPL v2");