12th.Linux驱动程序开发
驱动程序
一个驱动程序,从上到下,可以分为:应用程序,库,内核,驱动程序。开发人员专注于自己熟悉的部分,对于相邻层,只需要了解其接口即可。
驱动程序框架
驱动程序框架大致分为四层
-
- 最底层硬件操作程序
- 创建并填充结构体
- 安装函数与卸载函数
- 修饰安装函数与卸载函数
1 /* 【1】最底层驱动函数 2 * 在函数内实施相应的硬件操作 3 */ 4 static int first_drv_open(struct inode *inode, struct file *file); 5 static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos); 6 7 8 /*【2】 将驱动程序的函数填充进入结构体 9 * 让上层可以调用,相应的函数有open,write,read,ioctl.....应用程序里使用open("/dev/xxx")时,就会通过这个结构体,调用open所对应的函数 10 */ 11 static struct file_operations first_drv_fops = { 12 .owner = THIS_MODULE, // 这是一个宏,推向编译模块时自动创建的__this_module变量 13 .open = first_drv_open, 14 .write = first_drv_write, 15 }; 16 17 /* 【3】 驱动程序的安装与卸载函数 18 * 系统加载模块时,会调用到这两个函数 19 */20 int first_drv_init(void); 21 int first_drv_exit(void); 22 23 24 /* 【4】修饰封装 25 * 系统使用insmod,rmmod这两个安装卸载的指令时,调用到上面两个函数,所以把上面的init,exit两个函数放在这里 26 */27 module_init(first_drv_init); 28 module_exit(first_drv_exit); 29 MODULE_LICENSE("GPL");
重点函数分析
first_drv_init(void)函数
1 int major; 2 static struct class *firstdrv_class; 3 static struct class_device *firstdrv_class_devs; 4 volatile unsigned long *gpfcon = NULL; 5 volatile unsigned long *gpfdat = NULL; 6 7 int first_drv_init(void) 8 { 9 /*注册驱动程序使用register_chrdev函数 10 *参数依次为,主设备号,设备名,file_operations结构(即上面定义的结构体) 11 *执行完该函数后,即将三者联系起来 12 *当主设备号设为0时,内核会自动分配主设备号 13 *返回值为主设备号,当返回值为负数时则分配失败 14 */ 15 16 major=register_chrdev(0,"first_drv",&first_drv_fops);//注册驱动程序 17 18 /*class_create()、class_device_create()、为自动创建设备目录 19 *安装驱动程序后,需要在/dev目录下创建一个驱动目录, 20 *执行后会自动创建 21 */ 22 23 firstdrv_class = class_create(THIS_MODULE, "firstdrv"); 24 25 firstdrv_class_devs = class_device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xxx"); 26 27 28 /* 硬件地址重映射操作 29 * 驱动程序与单片机程序不同地方在于,驱动程序不能直接操作寄存器的物理地址 30 * 需要通过ioremap()重新映射一个虚拟地址进行操作 31 */ 32 gpfcon = (volatile unsigned long *)ioremap (0x56000050,16); 33 34 gpfdat = gpfcon+1; 35 36 37 //注册完成 38 printk("Setup successfully!") 39 40 return 0; 41 }
first_drv_exit(void)函数
1 int first_drv_exit(void) 2 { 3 4 /*将注册在内核里的东西移除即可 5 *卸载驱动、移除/dev设备文件、取消映射 6 */ 7 unregister_chrdev(major,"first_drv");//卸载驱动程序 8 9 class_device_unregister(firstdrv_class_devs); 10 class_destroy(firstdrv_class); 11 iounmap(gpfcon); 12 return 0; 13 }
first_drv_open(....)函数
1 static int first_drv_open(struct inode *inode, struct file *file) 2 { 3 /* 当应用程序执行open("/dev/...")函数时,会调用到这个函数 4 * 一般在这里写上硬件的初始化操作,而不在init函数里写 5 * 参数在调用时已经被内核处理了,驱动程序看不到参数 6 */ 7 8 *gpfcon &= ~((0x3<<(4*2))|(0x3<<(5*2))|(0x3<<(6*2))); 9 *gpfcon |= ((0x1<<(4*2))|(0x1<<(5*2))|(0x1<<(6*2))); 10 return 0; 11 }
first_drv_write(....)函数
1 static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) 2 { 3 int val; 4 5 /* 写操作程序,需要将传入参数掉出来 6 * 通过copy_from_user(..)函数 7 * 参数依次是目标,源,长度 8 * 将buf拷贝到val,count为长度 9 */ 10 copy_from_user(&val, buf, count); 11 12 //相应操作 13 if(val==1) 14 { 15 *gpfdat |= (1<<4)|(1<<5)|(1<<6); 16 } 17 else 18 { 19 *gpfdat &= ~((1<<4)|(1<<5)|(1<<6)); 20 } 21 22 return 0; 23 }
编译驱动程序
编译驱动程序,除了这个C文件外,还需要一个MakeFile,代码如下
1 ##需要依赖编译好的这个驱动程序所使用的内核目录。 2 KERN_DIR = /work/system/linux-2.6.22.6 3 4 all: 5 make -C $(KERN_DIR) M=`pwd` modules 6 7 clean: 8 make -C $(KERN_DIR) M=`pwd` modules clean 9 rm -rf modules.order 10 11 obj-m += first_drv.o
安装与使用
生成 *.ko文件,即安装文件,在操作系统下安装,使用 insmod xxx.ko 安装驱动。
通过编写好的应用程序调用该驱动即可。
应用程序示例:
1 #include <sys/types.h> 2 #include <sys/stat.h> 3 #include <fcntl.h> 4 #include <stdio.h> 5 6 /* firsttest on 7 * firsttest off 8 */ 9 int main(int argc, char **argv) 10 { 11 int fd; 12 int val=1; 13 fd = open("/dev/xxx",O_RDWR); 14 if(fd<0) 15 16 {printf("can't open!\n");} 17 18 if(argc!=2) 19 { 20 printf("Usage :\n"); 21 printf("%s <on|off>\n",argv[0]); 22 return 0; 23 } 24 25 if(strcmp(argv[1],"off")==0) 26 { 27 val=1; 28 } 29 else 30 val=0; 31 write(fd, &val, 4); 32 return 0; 33 34 35 }