这一节是我学驱动这么多天来,最头疼的一节,事后想了想应该是poll驱动当时没有做练习,再加上对前面的一些知识开始遗忘了,出现了好多不该出现的错误,还有些错误仍然没有解决,但是好歹功能是实现了,看来不动手写程序真的很伤,就是因为电脑当时出了问题,没有写poll,导致我在中断处理函数,poll,read三个驱动函数之间,不断的徘徊也没找到直接的关系。题外话不多了~~~~~进入正题!!!
#include <linux/module.h> #include <linux/types.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/uaccess.h> #include <linux/io.h> #include <mach/map.h> #include <mach/regs-gpio.h> #include <linux/poll.h> #include <linux/irq.h> #include <asm/unistd.h> #include <linux/device.h> #include <linux/sched.h> #include <linux/wait.h> #include "plat_bun_drv.h" #include <mach/gpio-bank-n.h> MODULE_AUTHOR("Hao"); MODULE_LICENSE("Dual BSD/GPL"); volatile unsigned long GPIOM_VA_BASE;//定义一个全局变量 保存ioremap映射的地址 #define GPIOM_PA_BASE 0x7f008820 #define GPIOM_CON_VA GPIOM_VA_BASE #define GPIOM_DAT_VA (GPIOM_VA_BASE+0x4) #define GPIOM_PUD_VA (GPIOM_VA_BASE+0x8) struct resource led_res={ .name = "led io-mem", //设备资源的名字 .start = GPIOM_PA_BASE, .end = GPIOM_PA_BASE + 0xc, .flags = IORESOURCE_MEM, }; int key_num=1; //定义一个全局变量 来存放中断处理函数返回的按键值 int key_flag=0;//默认为0 在poll中不返回掩码 static DECLARE_WAIT_QUEUE_HEAD(waitq); //利用这个宏 定义初始化 等待队列 替代了wait_queue_head_t q和init_waitqueue_head(&q)两个函数 typedef struct { int irq; int num; char *name; } button_irq_t; button_irq_t button_irqs [] = { {0, 0, "KEY1"}, {0, 1, "KEY2"}, {0, 2, "KEY3"}, {0, 3, "KEY4"}, {0, 4, "KEY5"}, {0, 5, "KEY6"}, }; /*中断处理函数 在混杂设备的open函数中request_irq中定义的名字叫buttons_interrupt*/ /*本来其实在这里判断是那个按键中断 然后让led对应点亮就行了 但是驱动的所有判断都应该是给用户空间传递的 所有判断应该在用户空间进行 点亮led应该在用户空间进行*/ static irqreturn_t buttons_interrupt(int irq, void *dev_id) { printk("you are in buttons_interrupt!!!\n"); button_irq_t *button_irqs = (button_irq_t *)dev_id; /*在触发中断的时候 操作系统会根据中断源信号调用对应的处理函数 并同时将中断号irq 和 dev_id传递到这个函数 因为在注册中断的时候dev_id为了保证唯一性 输入的是button_irqs结构体的地址 这里直接使用这地址 是操作系统传递的*/ /*判断是否是这个设备产生中断 本来是为了给share中断用的 这里使用是因为所有按键的外部中断都是这一个中断处理函数*/ key_num = button_irqs->num; //数据处理 key_flag=1; wake_up_interruptible(&waitq);//唤醒poll的阻塞 return IRQ_RETVAL(IRQ_HANDLED); //接收到了准确的中断信号,并且作了相应正确的处理 /*步骤:1,判断本设备是否发送中断 2,数据处理 这个数据一般都是要通过read传递给用户空间的 3,清楚中断标志位 外部中断其实没有中断标志位 只有一个EINT0PEND寄存器 记录那个外部中断发生了 内核应该就是通过这个寄存器的值来判断同一中断组中不同外部中断的 4,唤醒阻塞的进程 这唤醒就是唤醒poll的阻塞的 */ } static int mem_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) { printk("you are in mem_read!!!\n"); int ret; while(!key_flag) { if (filp->f_flags & O_NONBLOCK) //判断文件是否不允许阻塞 { return -EAGAIN; } /*其实这个阻塞的可能性很低 因为都是poll阻塞被唤醒后 才进入read的 完全没必要来一个阻塞型设备驱动 就当练手把*/ wait_event_interruptible(waitq, key_flag); //可以中断的阻塞 第一个参数是阻塞队列 第二个参数是阻塞条件 } key_flag=0;//在下次中断来临前 还是让poll阻塞吧 if (copy_to_user((void *)buff,(void *)(&key_num), count))//把字符设备模块从内核空间拷贝到用户空间 { ret = - EFAULT; } else { ret = count; } return ret; } /*这里面真正找到了poll的意义了 如果没有poll进行文件监控 看那个文件可以读(利用的就是按键被按下 中断处理函数被调用 把poll的标志改变了) 就要不断的用read进行轮询的读取了然后进行阻塞型设备驱动 被挂起 要是多个文件就疯了 各种被挂起就进行不了了*/ static unsigned int mem_poll( struct file *file, struct poll_table_struct *wait) { printk("you are in mem_poll!!!\n"); /*步骤:1,poll_wait把文件的等待队列加入*wait 2,判断标志返回掩码*/ unsigned int mask = 0; poll_wait(file, &waitq, wait); //加入等待队列 if(key_flag) { mask |= POLLIN | POLLRDNORM;//返回可读标志 } return mask; } static void ok6410_led_setup(struct resource *led_resource) { unsigned long temp; led_res=*led_resource; request_mem_region(led_resource->start,(led_resource->end-led_resource->start),led_resource->name);//申请i/o内存 设备资源的名字 //其实我觉得用上面那个资源的结构体意义不打 因为request_mem_region就是在跟系统申请这个资源 等价于了把上面的那个资源结构体拷贝到了内核中的设备资源链表 GPIOM_VA_BASE = (volatile unsigned long )ioremap(led_resource->start, led_resource->end-led_resource->start);// /****************************可以直接对地址进行操作***************************************/ /* (*(volatile unsigned long *)GPIOM_VA_BASE)&=~0xffff; (*(volatile unsigned long *)GPIOM_VA_BASE)|=0x1|(0x1<<4)|(0x1<<8)|(0x1<<12); temp=0; (*(volatile unsigned long*)GPIOM_DAT_VA)=temp; //默认所有灯都亮 */ /*******************也可以用函数api进行操作 貌似这个方式更加安全***************************/ temp&=~0xffff; temp|=0x1|(0x1<<4)|(0x1<<8)|(0x1<<12); writel(temp, GPIOM_CON_VA); temp|=0xf; temp=0; writel(temp, GPIOM_DAT_VA); } static void ok6410_led_release(void) { iounmap((void*)GPIOM_VA_BASE); release_mem_region(led_res.start,led_res.end-led_res.start); } static long memdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { printk("you are in memdev_ioctl!!!\n"); int ret=0; int err=0; int kernel_num=1991; //char kernel_buf[20]="hello kernel!!!"; /*先判断命令号是否正确*/ if (_IOC_TYPE(cmd) != CMD_KTYPE) //获得命令的type类型是否正确 return -EINVAL; if (_IOC_NR(cmd) > LED_KCMD) //获得命令的num类型 是否小于命令个数 return -EINVAL; /*获命令的数据传输方向 根据各自的方向判断*/ if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));/*此函数是根据 内核空间写的 是用来判断 arg应用程序传来的用户空间 是否有效的 所以对于用户空间来说是写*/ else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));//对于用户空间来说是读 成功返回1 失败返回0 if (err) return -EFAULT; /*实现CMD的用法*/ switch(cmd) { case LEDR_KCMD: ret=__put_user(kernel_num, (int *)arg); //把内核中的int型数据读入用户空间 unsigned long arg就是一个地址值 kernel->arg break; case LEDW_KCMD: ret=__get_user(kernel_num, (int *)arg); //arg->kernel_num 把用户空间的数据传递给kernel_num printk(KERN_EMERG "WRITE_KCMD is in kernel!!! kernel_num:%d \n",kernel_num); if(0==kernel_num) { writel(0x1e, GPIOM_DAT_VA);//将4个led全部点亮 } if(1==kernel_num) { writel(0x1d, GPIOM_DAT_VA);//将4个led全部熄灭 } if(2==kernel_num) { writel(0x1b, GPIOM_DAT_VA);//将4个led全部点亮 } if(3==kernel_num) { writel(0x17, GPIOM_DAT_VA);//将4个led全部熄灭 } if(4==kernel_num) { writel(0x0, GPIOM_DAT_VA);//将4个led全部点亮 } if(5==kernel_num) { writel(0x1f, GPIOM_DAT_VA);//将4个led全部熄灭 } break; default: return -EINVAL; break; } return 0; } /*混杂设备中 当关闭文件的时候 应该先释放中断资源*/ int mem_release(struct inode *inode, struct file *filp) { printk("you are in mem_release!!!\n"); int i; for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) { free_irq(button_irqs[i].irq, (void *)&button_irqs[i]); } ok6410_led_release(); return 0; } /*混杂设备中 当打开文件的时候 应该先申请中断资源*/ int mem_open(struct inode *inode,struct file *filp) { printk("you are in mem_open!!!\n"); int i; int err; for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) { /*请求中断设备资源 第一个参数是中断号这里面与硬件平台一一对应 第二个参数是中断处理程序 这里面都 公用一个处理程序 第三个参数是标志位 标志是共享中断,快速中断,触发方式 第四个参数是中断id 是区 分共享中断 不同设备时候用的 这里面用的&button_irqs[i]的地址保证它的唯一性 这里没有安全判断*/ err=request_irq(button_irqs[i].irq, buttons_interrupt, IRQ_TYPE_EDGE_FALLING, button_irqs[i].name, (void *)&button_irqs[i]);//在这里设置成下降沿触发 中断处理函数就简单些 if (err) { printk("request_irq is error!!!\n");//此时其实应该有disable_irq 或者 free_irq等函数把申请过的irq释放掉 但是我嫌麻烦就没写 return err; } } ok6410_led_setup(&led_res); return 0; } static const struct file_operations mem_fops = //定义此字符设备的file_operations { //这里是对结构体整体赋值的方式 .owner = THIS_MODULE, //函数名都可以自己定义 都是函数指针 .open = mem_open, .release = mem_release, .unlocked_ioctl=memdev_ioctl, .read=mem_read, .poll=mem_poll, }; static struct miscdevice misc = { .minor = 0,//设置为0 系统自动分配次设备号 .name = "misc_bun", //我觉得这个是设备节点的名字 就是/dev路径下的文件的名字 .fops = &mem_fops, //文件操作 }; static int bun_drv_probe(struct platform_device *dev) //这里面写功能驱动 { int ret; int i; struct resource *res; printk("Driver found device which my driver can handle!\n"); for(i=0; i<6; i++) { res=platform_get_resource(dev,IORESOURCE_IRQ,i); if(res==NULL) { printk("no memory resource %d\n",i); return 0; } button_irqs[i].irq = res->start; //把设备中的irq资源都带到驱动程序中 赋值给button_irqs结构体数组中 } misc_register(&misc); return ret; } static int bun_drv_remove(struct platform_device *dev) { printk("Driver found device unpluged!\n"); misc_deregister(&misc); return 0; } struct platform_driver bun_drv={ .probe=bun_drv_probe, .remove=bun_drv_remove, .driver={ .owner=THIS_MODULE, .name="plat_bun_dev", //platform总线 里面驱动的名字 这个名字要和设备的名字一样 } }; static int __init platform_bun_drv_int(void) { return platform_driver_register(&bun_drv); } static void __exit platform_bun_drv_exit(void) { platform_driver_unregister(&bun_drv); } module_init(platform_bun_drv_int); module_exit(platform_bun_drv_exit);
#include <linux/device.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/string.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/interrupt.h> MODULE_AUTHOR("Hao"); MODULE_LICENSE("Dual BSD/GPL"); static struct resource ok6410_buttons_resource[] = { //中断设备资源 [0]={ .start = IRQ_EINT(0), .end = IRQ_EINT(0), .flags = IORESOURCE_IRQ, }, [1]={ .start = IRQ_EINT(1), .end = IRQ_EINT(1), .flags = IORESOURCE_IRQ, }, [2]={ .start = IRQ_EINT(2), .end = IRQ_EINT(2), .flags = IORESOURCE_IRQ, }, [3]={ .start = IRQ_EINT(3), .end = IRQ_EINT(3), .flags = IORESOURCE_IRQ, }, [4]={ .start = IRQ_EINT(4), .end = IRQ_EINT(4), .flags = IORESOURCE_IRQ, }, [5]={ .start = IRQ_EINT(5), .end = IRQ_EINT(5), .flags = IORESOURCE_IRQ, }, }; struct platform_device bun_dev={ .name="plat_bun_dev", //platform总线 里面设备的名字 这个名字要和驱动的名字一样 .id=-1, .num_resources=ARRAY_SIZE(ok6410_buttons_resource), //ARRAY_SIZE求资源结构体的个数的 .resource=ok6410_buttons_resource, }; static int __init platform_bun_dev_init(void) { int ret=0; ret=platform_device_register(&bun_dev); if(ret) { printk("platform_device_register failed!!\n"); } return ret; } static void __exit platform_bun_dev_exit(void) { platform_device_unregister(&bun_dev); } module_init(platform_bun_dev_init); module_exit(platform_bun_dev_exit);
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <error.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/select.h> #include <time.h> #include "plat_bun_drv.h" int main(void) { int key_num;//通过read返回给用户空间 那个按键被按下 fd_set rfds;//监控可读文件集 int fd; if (-1==(fd=open ("/dev/misc_bun", O_RDONLY))) { printf("open dev0 error\n"); _exit(EXIT_FAILURE); } //先清空集合 FD_ZERO(&rfds); //设置要监控的文件描述符 FD_SET(fd, &rfds); while(1) { //最后的时间为NULL 表示没有返回mask 马上进入等待队列 if(-1==(select(fd+1, &rfds, NULL, NULL, NULL))) { printf("select error\n"); _exit(EXIT_FAILURE); } if(FD_ISSET(fd, &rfds)) //判断掩码是否是可读的 { read(fd, &key_num, sizeof(key_num)); printf("put key is %d!!!\n",key_num); ioctl(fd,LEDW_KCMD,&key_num); } } close(fd); return 0; }