关键字:字符驱动、动态生成设备节点、helloworld
linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~
此驱动程序基于linux3.0的内核,exynos4412开发板。
首先,打开平台文件,此开发板的平台文件是arch\arm\mach-exynos\mach-itop4412.c,不同平台位置是不一样的。
申明一下设备信息,这里以编译进kernel为例
1 #ifdef CONFIG_HELLO_CHAR_CTL2 struct platform_device s3c_device_hello_char_ctl = {3 .name = "Hello_Jni_Char",4 .id = -1,5 };6 #endif
然后在struct platform_device *smdk4x12_devices[]数组里注册设备信息
1 #ifdef CONFIG_HELLO_CHAR_CTL2 &s3c_device_hello_char_ctl,3 #endif
至此,平台文件配置完毕。
接下来,开始大餐,驱动程序的编写!
注意:在linux的每一个驱动程序里面都需要加上如下信息,表明开源
1 MODULE_LICENSE("Dual BSD/GPL");2 MODULE_AUTHOR("pngcui");
每一个驱动程序都是从init函数开始的,同时也需要编写exit函数,在初始化函数中需要生成主设备号与从设备号,因为linux是根据主从设备号来寻找相应的硬件的
1 #define DEV_MAJOR 0 2 #define DEV_MINOR 0 3 4 int numdev_major = DEV_MAJOR; 5 int numdev_minor = DEV_MINOR; 6 7 8 /*输入主设备号*/ 9 module_param(numdev_major,int,S_IRUSR);10 /*输入次设备号*/11 module_param(numdev_minor,int,S_IRUSR);
进入驱动程序中的init函数中,进行注册设备号,生成设备节点DEVICE_NAME
1 #define DEVICE_NAME "chardevnode"
注意:此驱动由于控制了两个gpio引脚所以出现了gpio_init(),在这里可以忽略,对于gpio的控制,在下一篇博客中会写到。
1 static int scdev_init(void) 2 { 3 int ret = 0,i; 4 dev_t num_dev; 5 6 7 printk(KERN_EMERG "numdev_major is %d!\n",numdev_major); 8 printk(KERN_EMERG "numdev_minor is %d!\n",numdev_minor); 9 10 if(numdev_major){11 num_dev = MKDEV(numdev_major,numdev_minor);12 ret = register_chrdev_region(num_dev,DEVICE_MINOR_NUM,DEVICE_NAME);13 }14 else{15 /*动态注册设备号*/16 ret = alloc_chrdev_region(&num_dev,numdev_minor,DEVICE_MINOR_NUM,DEVICE_NAME);17 /*获得主设备号*/18 numdev_major = MAJOR(num_dev);19 printk(KERN_EMERG "adev_region req %d !\n",numdev_major);20 }21 if(ret<0){22 printk(KERN_EMERG "register_chrdev_region req %d is failed!\n",numdev_major); 23 }24 myclass = class_create(THIS_MODULE,DEVICE_NAME);25 26 27 my_devices = kmalloc(DEVICE_MINOR_NUM * sizeof(struct reg_dev),GFP_KERNEL);28 if(!my_devices){29 ret = -ENOMEM;30 goto fail;31 }32 memset(my_devices,0,DEVICE_MINOR_NUM * sizeof(struct reg_dev));33 34 /*设备初始化*/35 for(i=0;i
然后把设备注册到系统
1 static void reg_init_cdev(struct reg_dev *dev,int index){ 2 int err; 3 int devno = MKDEV(numdev_major,numdev_minor+index); 4 5 /*数据初始化*/ 6 cdev_init(&dev->cdev,&my_fops); 7 dev->cdev.owner = THIS_MODULE; 8 dev->cdev.ops = &my_fops; 9 10 /*注册到系统*/11 err = cdev_add(&dev->cdev,devno,1);12 if(err){13 printk(KERN_EMERG "cdev_add %d is fail! %d\n",index,err);14 }15 else{16 printk(KERN_EMERG "cdev_add %d is success!\n",numdev_minor+index);17 }18 }
dev->cdev.ops = &my_fops;这一句就指明了此驱动程序的函数接口
1 struct file_operations my_fops = {2 .owner = THIS_MODULE,3 .open = chardevnode_open,4 .release = chardevnode_release,5 .unlocked_ioctl = chardevnode_ioctl,6 .read = chardevnode_read,7 .write = chardevnode_write,8 .llseek = chardevnode_llseek,9 };
在这里的所有函数接口必须都实现,否则会编译报错。
其中最重要的两个函数是open、ioctl这两个函数,open函数用来打开设备节点,然后才可以对设备进行驱动。
然后是ioctl函数,一般上层应用都是通过调用此函数来对设备进行操作的。
1 /*打开操作*/ 2 static int chardevnode_open(struct inode *inode, struct file *file){ 3 printk(KERN_EMERG "chardevnode_open is success!\n"); 4 5 return 0; 6 } 7 /*关闭操作*/ 8 static int chardevnode_release(struct inode *inode, struct file *file){ 9 printk(KERN_EMERG "chardevnode_release is success!\n");10 11 return 0;12 }13 /*IO操作*/14 static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg){15 16 switch(cmd)17 {18 case 0:19 case 1:20 if (arg > LED_NUM) {21 return -EINVAL;22 }23 24 gpio_set_value(led_gpios[arg], cmd);25 break;26 27 default:28 return -EINVAL;29 }30 31 printk(KERN_EMERG "chardevnode_ioctl is success! cmd is %d,arg is %d \n",cmd,arg);32 33 return 0;34 }35 36 ssize_t chardevnode_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops){37 return 0;38 }39 40 ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops){41 return 0;42 }43 44 loff_t chardevnode_llseek(struct file *file, loff_t offset, int ence){45 return 0;46 }
到这里一个完整的字符驱动就编写完毕了。
最后附上完整的驱动程序
1 /*包含初始化宏定义的头文件,代码中的module_init和module_exit在此文件中*/ 2 #include3 /*包含初始化加载模块的头文件,代码中的MODULE_LICENSE在此头文件中*/ 4 #include 5 /*定义module_param module_param_array的头文件*/ 6 #include 7 /*定义module_param module_param_array中perm的头文件*/ 8 #include 9 /*三个字符设备函数*/ 10 #include 11 /*MKDEV转换设备号数据类型的宏定义*/ 12 #include 13 /*定义字符设备的结构体*/ 14 #include 15 /*分配内存空间函数头文件*/ 16 #include 17 /*包含函数device_create 结构体class等头文件*/ 18 #include 19 20 /*自定义头文件*/ 21 #include "char_driver_leds.h" 22 23 /*Linux中申请GPIO的头文件*/ 24 #include 25 /*三星平台的GPIO配置函数头文件*/ 26 /*三星平台EXYNOS系列平台,GPIO配置参数宏定义头文件*/ 27 #include 28 /*三星平台4412平台,GPIO宏定义头文件*/ 29 #include 30 31 32 MODULE_LICENSE("Dual BSD/GPL"); 33 /*声明是开源的,没有内核版本限制*/ 34 MODULE_AUTHOR("iTOPEET_dz"); 35 /*声明作者*/ 36 37 static int led_gpios[] = { 38 EXYNOS4_GPL2(0),EXYNOS4_GPK1(1), 39 }; 40 #define LED_NUM ARRAY_SIZE(led_gpios) 41 42 #define DEVICE_NAME "chardevnode" 43 44 #define DEVICE_MINOR_NUM 1 45 46 #define DEV_MAJOR 0 47 #define DEV_MINOR 0 48 #define REGDEV_SIZE 3000 49 50 51 struct reg_dev 52 { 53 char *data; 54 unsigned long size; 55 56 struct cdev cdev; 57 }; 58 59 60 int numdev_major = DEV_MAJOR; 61 int numdev_minor = DEV_MINOR; 62 63 /*输入主设备号*/ 64 module_param(numdev_major,int,S_IRUSR); 65 /*输入次设备号*/ 66 module_param(numdev_minor,int,S_IRUSR); 67 68 static struct class *myclass; 69 struct reg_dev *my_devices; 70 71 /*打开操作*/ 72 static int chardevnode_open(struct inode *inode, struct file *file){ 73 printk(KERN_EMERG "chardevnode_open is success!\n"); 74 75 return 0; 76 } 77 /*关闭操作*/ 78 static int chardevnode_release(struct inode *inode, struct file *file){ 79 printk(KERN_EMERG "chardevnode_release is success!\n"); 80 81 return 0; 82 } 83 /*IO操作*/ 84 static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg){ 85 86 switch(cmd) 87 { 88 case 0: 89 case 1: 90 if (arg > LED_NUM) { 91 return -EINVAL; 92 } 93 94 gpio_set_value(led_gpios[arg], cmd); 95 break; 96 97 default: 98 return -EINVAL; 99 }100 101 printk(KERN_EMERG "chardevnode_ioctl is success! cmd is %d,arg is %d \n",cmd,arg);102 103 return 0;104 }105 106 ssize_t chardevnode_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops){107 return 0;108 }109 110 ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops){111 return 0;112 }113 114 loff_t chardevnode_llseek(struct file *file, loff_t offset, int ence){115 return 0;116 }117 struct file_operations my_fops = {118 .owner = THIS_MODULE,119 .open = chardevnode_open,120 .release = chardevnode_release,121 .unlocked_ioctl = chardevnode_ioctl,122 .read = chardevnode_read,123 .write = chardevnode_write,124 .llseek = chardevnode_llseek,125 };126 127 128 /*设备注册到系统*/129 static void reg_init_cdev(struct reg_dev *dev,int index){130 int err;131 int devno = MKDEV(numdev_major,numdev_minor+index);132 133 /*数据初始化*/134 cdev_init(&dev->cdev,&my_fops);135 dev->cdev.owner = THIS_MODULE;136 dev->cdev.ops = &my_fops;137 138 /*注册到系统*/139 err = cdev_add(&dev->cdev,devno,1);140 if(err){141 printk(KERN_EMERG "cdev_add %d is fail! %d\n",index,err);142 }143 else{144 printk(KERN_EMERG "cdev_add %d is success!\n",numdev_minor+index);145 }146 }147 148 static int gpio_init(void){149 int i=0,ret;150 151 for(i=0;i
以及完整的测试程序
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 8 int main(){ 9 10 int fd;11 12 if((fd = open("/dev/chardevnode0",O_RDWR|O_NDELAY)) < 0)13 printf("Hello_Jni open failed!\n");14 15 ioctl(fd,0,0);16 ioctl(fd,0,1);17 18 close(fd);19 20 return 0;21 }