刚开始学写驱动,光看看听听,感觉都对;但是动手的时候发现困难重重 , 这算是自己动手写成的第一个驱动模块(helloworld就不算了)。 首先头文件也是一个重点,至少我觉得是,往往写写就会漏,说明自己对驱动的函数还不够了解 #includelinux/module.h#incl
刚开始学写驱动,光看看听听,感觉都对;但是动手的时候发现困难重重,这算是自己动手写成的第一个驱动模块(helloworld就不算了)。
首先头文件也是一个重点,至少我觉得是,往往写写就会漏,说明自己对驱动的函数还不够了解
#include<linux/module.h> #include<linux/kernel.h> //前2个是写驱动必备的 #include<linux/cdev.h> //这个毫无疑问写字符驱动要用,其中用到cdev结构体 #include<linux/fs.h> //fs中定义了第二个重要的数据struct file #include<linux/device.h> #include<mach/gpio.h> #include<linux/gpio.h> #include<asm/io.h> #include<linux/ioctl.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("LJJ EMAIL:346619201@QQ.COM"); unsigned int *GPM4CON; unsigned int *GPM4DAT; dev_t ljj_dev; struct cdev ljj_cdev; //这2个变量,我自己写着就混淆了,比较重要。 //dev_t 用在设备编号的分配和释放,动态设备创建中。 //cdev结构用在其cdev_init,以及add到设备中。 struct class* ljj_class; struct device *parent; //这是用来动态创建设备的变量,暂时不清楚为什么 //fops中自定义的ioctl的实现 static long ljj_ioctl(struct file *fp,unsigned int arg,unsigned long value) { int tmp; if(value == 0) { tmp = ioread32(GPM4DAT); //使用ioread32来读取32位的数据 tmp &= ~(1<<arg); iowrite32(tmp,GPM4DAT); //iowrite32时用来写入32位的数据 printk(KERN_WARNING"led%d on\n",arg); //KERN_WARING的警告级别可以在终端中显示信息 }else{ tmp = ioread32(GPM4DAT); tmp |= (1<<arg); iowrite32(tmp,GPM4DAT); printk(KERN_WARNING"led%d off\n",arg); } return 0; } //fops的定义 struct file_operations ljj_fops={ .owner = THIS_MODULE, //.open = ljj_open, //.release = ljj_close, .unlocked_ioctl = ljj_ioctl, }; //初始化 static int __init init_led(void) { int ret = 0; unsigned int tmp; ret = alloc_chrdev_region(&ljj_dev,0,5,"ljj_cdev"); //设备编号的申请 if(ret!=0) { goto error1; } cdev_init(&ljj_cdev,&ljj_fops); //初始化亦分配到的结构(LDD中这么解释) ljj_cdev.owner = THIS_MODULE; //cdev结构的owner需要单独初始化! ljj_cdev.ops = &ljj_fops; //把自定义的fops结构,传给cdev结构 ret = cdev_add(&ljj_cdev,ljj_dev,1); //添加字符设备 if(ret!=0) { goto error2; } ljj_class = class_create(THIS_MODULE,"ljj_cdev"); //这其实是添加udev动态创建设备的,在驱动初始化的代码里,调用class_create为该设备创建一个class,再为每个设备调用 class_device_create创建对应的设备,在模块加载的时候udev就会自动在/dev目录下创建一个"ljj_cdev"的设备文件 if(IS_ERR((void*)ljj_class)) { goto error3; } parent = device_create(ljj_class,NULL,ljj_dev,NULL,"ljj_cdev"); if(IS_ERR((void*)parent)) { goto error4; } GPM4CON = ioremap(0x110002E0,4); //io口的重映射,(地址,长度),从而使我们可以操作该io口 GPM4DAT = ioremap(0x110002E4,4); //地址信息根据datasheet,和原理图来确定 tmp = ioread32(GPM4CON); tmp |= 0x1111; iowrite32(tmp,GPM4CON); printk("I am ljj's led.ko ! GPM4CON:%x \n",tmp); return 0; error4: class_destroy(ljj_class); error3: cdev_del(&ljj_cdev); error2: unregister_chrdev_region(ljj_dev,5); error1: printk(KERN_WARNING"ljj_cdev error!\n"); return -1; } static void __exit clean_led(void) { iounmap(GPM4DAT); //解除重映射,释放io资源 iounmap(GPM4CON); class_destroy(ljj_class); //摧毁这个东西,反正是用来动态创建设备的 cdev_del(&ljj_cdev); //移除字符设备 unregister_chrdev_region(0,5); //释放设备编号 printk("ljj's led.ko is cleaned! \n"); } module_init(init_led); //提醒内核,哪个是insmod调用的函数 module_exit(clean_led); //同上,这个时rmmod调用的函数
最后也写一段应用程序在自己友善的Tiny4412(A9)开发板,open设备ljj_dev,用ioctl函数来控制led亮灭。其中发现一个小bug,led0,led1,led3都能正常控制,而led2,无法控制,开始以为是硬件问题,后来重启A9开发板,启动rcS中系统自带的led跑马灯,发现led2能够正常亮灭。目前仍然在试图解决该bug。