misc类型 按键驱动及应用- Linux
闲着无聊,再扔个以前抄的水模版骗人
button 驱动:
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/poll.h> #include <linux/sched.h> #include <linux/irq.h> #include <asm/irq.h> #include <asm/io.h> #include <linux/interrupt.h> #include <asm/uaccess.h> #include <mach/hardware.h> #include <linux/platform_device.h> #include <linux/cdev.h> #include <linux/miscdevice.h> #include <linux/gpio.h> #include <mach/map.h> #include <mach/gpio.h> #include <mach/regs-clock.h> #include <mach/regs-gpio.h> #define DEVICE_NAME "av5260-buttons" struct button_desc { int gpio; int number; char *name; struct timer_list timer; }; static struct button_desc buttons[] = { { EXYNOS5260_GPX3(3), 1, "KEY0" }, }; #define BUTTON_NUM ARRAY_SIZE(buttons) static volatile char key_values[] = { '0', '0', '0', '0', '0', '0', '0', '0' }; static DECLARE_WAIT_QUEUE_HEAD(button_waitq); static volatile int ev_press = 0; static void av5260_buttons_timer(unsigned long _data) { struct button_desc *bdata = (struct button_desc *)_data; int down; int number; unsigned tmp; tmp = gpio_get_value(bdata->gpio); /* active low */ down = !tmp; printk(KERN_DEBUG "KEY %d: %08x\n", bdata->number, down); number = bdata->number; if (down != (key_values[number] & 1)) { key_values[number] = '0' + down; ev_press = 1; wake_up_interruptible(&button_waitq); } } static irqreturn_t button_interrupt(int irq, void *dev_id) { struct button_desc *bdata = (struct button_desc *)dev_id; mod_timer(&bdata->timer, jiffies + msecs_to_jiffies(40)); return IRQ_HANDLED; } static int av5260_buttons_open(struct inode *inode, struct file *file) { int irq; int i; int err = 0; for (i = 0; i < BUTTON_NUM; i++) { if (!buttons[i].gpio) continue; setup_timer(&buttons[i].timer, av5260_buttons_timer, (unsigned long)&buttons[i]); irq = gpio_to_irq(buttons[i].gpio); err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_BOTH, buttons[i].name, (void *)&buttons[i]); if (err) break; } if (err) { i--; for (; i >= 0; i--) { if (!buttons[i].gpio) continue; irq = gpio_to_irq(buttons[i].gpio); disable_irq(irq); free_irq(irq, (void *)&buttons[i]); del_timer_sync(&buttons[i].timer); } return -EBUSY; } ev_press = 1; return 0; } static int av5260_buttons_close(struct inode *inode, struct file *file) { int irq, i; for (i = 0; i < BUTTON_NUM; i++) { if (!buttons[i].gpio) continue; irq = gpio_to_irq(buttons[i].gpio); free_irq(irq, (void *)&buttons[i]); del_timer_sync(&buttons[i].timer); } return 0; } static int av5260_buttons_read(struct file *filp, char __user *buff, size_t count, loff_t *offp) { unsigned long err; if (!ev_press) { if (filp->f_flags & O_NONBLOCK) return -EAGAIN; else wait_event_interruptible(button_waitq, ev_press); } ev_press = 0; err = copy_to_user((void *)buff, (const void *)(&key_values), min(sizeof(key_values), count)); return err ? -EFAULT : min(sizeof(key_values), count); } static unsigned int av5260_buttons_poll( struct file *file, struct poll_table_struct *wait) { unsigned int mask = 0; poll_wait(file, &button_waitq, wait); if (ev_press) mask |= POLLIN | POLLRDNORM; return mask; } static struct file_operations dev_fops = { .owner = THIS_MODULE, .open = av5260_buttons_open, .release = av5260_buttons_close, .read = av5260_buttons_read, .poll = av5260_buttons_poll, }; static struct miscdevice misc_av5260_button = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops, }; static int __init button_dev_init(void) { int ret; ret = misc_register(&misc_av5260_button); printk(DEVICE_NAME"\tinitialized\n"); return ret; } static void __exit button_dev_exit(void) { misc_deregister(&misc_av5260_button); } module_init(button_dev_init); module_exit(button_dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("ArashiVision Inc."); /* 1: Arm Linux interrupt 在linux内核中用于申请中断的函数是request_irq(),函数原型在Kernel/irq/manage.c中定义: int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) irq是要申请的硬件中断号。 handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。 irqflags是中断处理的属性,若设置了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版zhon已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若设置了IRQF_SHARED (老版本中的SA_SHIRQ),则表示多个设备共享中断,若设置了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的) devname设置中断名称,通常是设备驱动程序的名称 在cat /proc/interrupts中可以看到此名称。 dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。 request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。 */ /* 2: Arm Linux interrupt timer LINUX内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个函数的一种机制,其实现位于 <linux/timer.h> 和 kernel/timer.c 文件中。被调度的函数肯定是异步执行的,它类似于一种“软件中断”,而且是处于非进程的上下文中,所以调度函数必须遵守以下规则: 1) 没有 current 指针、不允许访问用户空间。因为没有进程上下文,相关代码和被中断的进程没有任何联系。 2) 不能执行休眠(或可能引起休眠的函数)和调度。 3) 任何被访问的数据结构都应该针对并发访问进行保护,以防止竞争条件。 内核定时器的调度函数运行过一次后就不会再被运行了(相当于自动注销),但可以通过在被调度的函数中重新调度自己来周期运行。 在SMP系统中,调度函数总是在注册它的同一CPU上运行,以尽可能获得缓存的局域性。 定时器API 内核定时器的数据结构 struct timer_list { struct list_head entry; unsigned long expires; void (*function)(unsigned long); unsigned long data; struct tvec_base *base; // ... }; 其中 expires 字段表示期望定时器执行的 jiffies 值,到达该 jiffies 值时,将调用 function 函数,并传递 data 作为参数。当一个定时器被注册到内核之后,entry 字段用来连接该定时器到一个内核链表中。base 字段是内核内部实现所用的。 需要注意的是 expires 的值是32位的,因为内核定时器并不适用于长的未来时间点。 初始化 在使用 struct timer_list 之前,需要初始化该数据结构,确保所有的字段都被正确地设置。初始化有两种方法。 方法一: DEFINE_TIMER(timer_name, function_name, expires_value, data); 该宏会静态创建一个名叫 timer_name 内核定时器,并初始化其 function, expires, name 和 base 字段。 方法二: struct timer_list mytimer; setup_timer(&mytimer, (*function)(unsigned long), unsigned long data); mytimer.expires = jiffies + 5*HZ; 方法3: struct timer_list mytimer; init_timer(&mytimer); mytimer ->timer.expires = jiffies + 5*HZ; mytimer ->timer.data = (unsigned long) dev; mytimer ->timer.function = &corkscrew_timer; //timer handler 通过init_timer()动态地定义一个定时器,此后,将处理函数的地址和参数绑定给一个timer_list, 注意,无论用哪种方法初始化,其本质都只是给字段赋值,所以只要在运行 add_timer() 之前,expires, function 和 data 字段都可以直接再修改。 关于上面这些宏和函数的定义,参见 include/linux/timer.h。 注册 定时器要生效,还必须被连接到内核专门的链表中,这可以通过 add_timer(struct timer_list *timer) 来实现。 重新注册 要修改一个定时器的调度时间,可以通过调用 mod_timer(struct timer_list *timer, unsigned long expires)。mod_timer() 会重新注册定时器到内核,而不管定时器函数是否被运行过。 注销 注销一个定时器,可以通过 del_timer(struct timer_list *timer) 或 del_timer_sync(struct timer_list *timer)。其中 del_timer_sync 是用在 SMP 系统上的(在非SMP系统上,它等于del_timer),当要被注销的定时器函数正在另一个 cpu 上运行时,del_timer_sync() 会等待其运行完,所以这个函数会休眠。另外还应避免它和被调度的函数争用同一个锁。对于一个已经被运行过且没有重新注册自己的定时器而言,注销函数其实也没什么事可做。 int timer_pending(const struct timer_list *timer) 这个函数用来判断一个定时器是否被添加到了内核链表中以等待被调度运行。注意,当一个定时器函数即将要被运行前,内核会把相应的定时器从内核链表中删除(相当于注销) */ /* 3:poll 使用非阻塞I/O的应用程序通常会使用select()和poll()系统调用查询是否可对设备进行无阻塞的访问,这两个系统调用最终又会引发设备驱动中的poll()函数被执行 ,所以我们的问题就集中到了如何编写设备驱动中的poll()函数就可以了。二话不说,先来看看设备驱动中的poll()函数原型: unsigned int (*poll)(struct file *filp, struct poll_table *wait); 这个函数要进行下面两项工作。首先,对可能引起设备文件状态变化的等待队列调用poll_wait(),将对应的等待队列头添加到poll_table.然后,返回表示是否能对设备进行无阻塞读写访问的掩码。在上面提到了一个poll_wait()函数,它的原型: void poll_wait(struct file *filp, wait_queue_head_t *queue, poll_table *wait); 它的作用就是把当前进程添加到wait参数指定的等待列表(poll_table)中。需要注意的是这个函数是不会引起阻塞的,呵呵,谁给它取得个名字带wait的,给咱们添这么多麻烦。 */
button应用:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/select.h> #include <sys/time.h> #include <errno.h> int main(void) { int buttons_fd; char buttons[8] = {'0', '0', '0', '0', '0', '0', '0', '0'}; buttons_fd = open("/dev/av5260-buttons", 0); if (buttons_fd < 0) { perror("open device av5260-buttons"); exit(1); } for (;;) { char current_buttons[8]; int count_of_changed_key; int i; if (read(buttons_fd, current_buttons, sizeof current_buttons) != sizeof current_buttons) { perror("read av5260-buttons:"); exit(1); } for (i = 0, count_of_changed_key = 0; i < sizeof buttons / sizeof buttons[0]; i++) { if (buttons[i] != current_buttons[i]) { buttons[i] = current_buttons[i]; printf("%skey %d is %s", count_of_changed_key? ", ": "", i+1, buttons[i] == '0' ? "up" : "down"); count_of_changed_key++; } } if (count_of_changed_key) { printf("\n"); } } close(buttons_fd); return 0; }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。