驱动程序:
/*****************************************************************************************Name: keyboard.c*Author: Ma Dongpeng<madongpeng@hrbeu.edu.cn> *Time: 2011-02-17 14:08:10 *Version: 1.0.0*Description: keyborad driver for linux2.6.25*****************************************************************************************/
#include <linux/module.h>#include <linux/kernel.h>#include <linux/fs.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/errno.h>#include <linux/device.h>#include <linux/poll.h>#include <linux/interrupt.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/irq.h>#include <linux/wait.h>#include <asm/irq.h>#include <asm/uaccess.h>#include <asm/arch/regs-gpio.h>#include <asm/hardware.h>
#define DEVICE_NAME "keyboard" // 加载模式后,执行”cat /proc/devices”命令看到的设备名称 #define KEYBOARD_MAJOR 252 // 主设备号 #define KEY_NUM 4 //按键数目#define KEYSTATUS_DOWNX 0#define KEYSTATUS_UP 1#define KEY_TIMER_DELAY 15 //以ms为单位,表示延时15ms#define KEY_TIMER_DELAY1 100#define KEY_TIMER_DELAY2 20
static int keyboard_major=KEYBOARD_MAJOR;
struct key_irq_desc { int irq; //中断号 int pin; //中断引脚 int pin_setting; //设置gpio功能 int number; char *name; };
// 用来指定按键所用的外部中断引脚及中断触发方式, 名字 static struct key_irq_desc key_irqs [] ={ {IRQ_EINT1, S3C2410_GPF1, S3C2410_GPF1_EINT1, 0, "KEY1"}, /* K1 */ {IRQ_EINT4, S3C2410_GPF4, S3C2410_GPF4_EINT4, 1, "KEY2"}, /* K2 */ {IRQ_EINT2, S3C2410_GPF2, S3C2410_GPF2_EINT2, 2, "KEY3"}, /* K3 */ {IRQ_EINT0, S3C2410_GPF0, S3C2410_GPF0_EINT0, 3, "KEY4"}, /* K4 */};
struct key_dev { struct cdev cdev; char key_values[KEY_NUM]; //记录键值为1表示对应的按键被按下 unsigned int key_status[KEY_NUM]; //记录按键状态 wait_queue_head_t key_waitq; //等待队列 int key_values_flag; //记录是否有任何一个按键被按下};struct key_dev *key_devp;
static struct timer_list key_timer[KEY_NUM]; //按键的定时器
static char __initdata info[] = "******************keyborad Driver*****************/n";static struct class *key_class;
/*******************************************************************************************键盘中断处理程序*******************************************************************************************/static irqreturn_t key_interrupt(int irq, void *dev_id){ struct key_irq_desc *key_irqs = (struct key_irq_desc *)dev_id; disable_irq(key_irqs->irq); //关中断进入查询模式
key_devp->key_status[key_irqs->number]=KEYSTATUS_DOWNX; key_timer[key_irqs->number].expires=jiffies+KEY_TIMER_DELAY; add_timer(&key_timer[key_irqs->number]); //启动定时器
return IRQ_RETVAL(IRQ_HANDLED);}
/*******************************************************************************************处理键盘事件,在key_timer_handler中被调用,记录键值唤醒等待队列*******************************************************************************************/static void key_event(int num){ //printk(KERN_ALERT "num=%d/n",num); key_devp->key_values[num]=1; key_devp->key_values_flag=1; wake_up_interruptible(&key_devp->key_waitq); //唤醒等待队列}
/*******************************************************************************************定时器中断处理程序*******************************************************************************************/static void key_timer_handler(unsigned long data){ struct key_irq_desc *key_irqs = (struct key_irq_desc *)data; if(s3c2410_gpio_getpin(key_irqs->pin)==KEYSTATUS_DOWNX) //键盘仍然属于按下状态 { if(key_devp->key_status[key_irqs->number]==KEYSTATUS_DOWNX) { key_devp->key_status[key_irqs->number]=KEYSTATUS_UP; key_timer[key_irqs->number].expires=jiffies+KEY_TIMER_DELAY1;//准备进入连按模式,延时较长 key_event(key_irqs->number); //记录键值唤醒等待队列 add_timer(&key_timer[key_irqs->number]); //启动定时器 } else { key_timer[key_irqs->number].expires=jiffies+KEY_TIMER_DELAY2;//连按模式,延时较短 key_event(key_irqs->number); //记录键值唤醒等待队列 add_timer(&key_timer[key_irqs->number]); //启动定时器 } } else //键盘已经抬起 { key_devp->key_status[key_irqs->number]=KEYSTATUS_UP; enable_irq(key_irqs->irq); }}
/********************************************************************************************应用程序对设备文件/dev/keyboard执行open(...)时,* 就会调用key_open函数 ********************************************************************************************/static int key_open(struct inode *inode, struct file *file){ int i; int err;
for (i = 0; i < KEY_NUM; i++) { set_irq_type(key_irqs[i].irq,IRQF_TRIGGER_LOW); //set_external_irq(key_irqs[i].irq,EXT_LOWLEVEL,GPIO_PULLUP_DIS); //将对应的引脚设置成中断功能 s3c2410_gpio_cfgpin(key_irqs[i].pin,key_irqs[i].pin_setting); // 申请中断,注册中断处理函数 err = request_irq(key_irqs[i].irq, key_interrupt, NULL, key_irqs[i].name, (void *)&key_irqs[i]); //将&key_irqs[i]作为参数传入中断处理程序 if (err) break; }
if (err) { // 释放已经注册的中断 i--; for (; i >= 0; i--) { disable_irq(key_irqs[i].irq); free_irq(key_irqs[i].irq, (void *)&key_irqs[i]); } return -EBUSY; }
return 0;}
/********************************************************************************************* 应用程序对设备文件/dev/keyboard执行close(...)时,* 就会调用key_close函数*********************************************************************************************/static int key_close(struct inode *inode, struct file *file){ int i;
for (i = 0; i < KEY_NUM; i++) { // 释放已经注册的中断 disable_irq(key_irqs[i].irq); free_irq(key_irqs[i].irq, (void *)&key_irqs[i]); }
return 0;}
/*********************************************************************************************应用程序对设备文件/dev/keyboard执行read(...)时,* 就会调用key_read函数*********************************************************************************************/static int key_read(struct file *filp, char __user *buff, size_t count, loff_t *offp){ unsigned long err; retry:if(key_devp->key_values_flag==1) { /* 将按键状态复制给用户,并清0 */ //printk(KERN_ALERT "key value:%d %d %d %d/n",key_devp->key_values[0],key_devp->key_values[1],key_devp->key_values[2],key_devp->key_values[3]); err = copy_to_user(buff, (const void *)key_devp->key_values, min(KEY_NUM, count)); memset((void *)key_devp->key_values, 0, min(KEY_NUM, count)); key_devp->key_values_flag=0; } else { if (filp->f_flags & O_NONBLOCK) return -EAGAIN; else { /* 如果key_values_flag等于0,休眠 */ wait_event_interruptible(key_devp->key_waitq, key_devp->key_values_flag); goto retry; } } return err ? -EFAULT : min(KEY_NUM, count);}
/********************************************************************************************* 轮询函数判断是否能非阻塞的读取或写入* 当用户程序调用select函数时,本函数被调用* 如果有按键数据,则select函数会立刻返回* 如果没有按键数据,本函数使用poll_wait等待********************************************************************************************/static unsigned int key_poll( struct file *file, struct poll_table_struct *wait){ unsigned int mask = 0; poll_wait(file, &(key_devp->key_waitq), wait); // 此处将当前进程加入到等待队列中,但并不阻塞 if (key_devp->key_values_flag) mask |= POLLIN | POLLRDNORM; return mask;}
/**********************************************************************************************这个结构是字符设备驱动程序的核心* 当应用程序操作设备文件时所调用的open、read、write等函数,* 最终会调用这个结构中的对应函数*********************************************************************************************/static struct file_operations key_fops ={ .owner = THIS_MODULE, /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */ .open = key_open, .release = key_close, .read = key_read, .poll = key_poll,};
/**********************************************************************************************注册设备,创建设备节点**********************************************************************************************/static int key_setup_cdev(struct key_dev *dev,int index){ int err; int devno = MKDEV(KEYBOARD_MAJOR,index); cdev_init(&dev->cdev,&key_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &key_fops; err = cdev_add(&dev->cdev,devno,1); //向系统注册设备 if(err) printk(KERN_ALERT "Error %d adding key %d",err,index);
//注册一个类,使mdev可以在"/dev/"目录下面建立设备节点 key_class = class_create(THIS_MODULE, DEVICE_NAME); if(IS_ERR(key_class)) { printk("Err: failed in led class. /n"); return -1; } //创建一个设备节点,节点名为DEVICE_NAME class_device_create(key_class, NULL, MKDEV(keyboard_major, 0), NULL, DEVICE_NAME); return 0;}
/********************************************************************************************* * 执行“insmod key.ko”命令时就会调用这个函数 *********************************************************************************************/static int __init key_init(void){ int ret,i; printk(info);
/* 注册字符设备驱动程序 * 参数为主设备号、设备名字、file_operations结构; * 这样,主设备号就和具体的file_operations结构联系起来了, * 操作主设备为BUTTON_MAJOR的设备文件时,就会调用key_fops中的相关成员函数 * keyboard_major可以设为0,表示由内核自动分配主设备号 */ dev_t devno = MKDEV(keyboard_major,0); if(keyboard_major) ret=register_chrdev_region(devno,1,DEVICE_NAME); //注册主设备号 else //申请主设备号 { ret = alloc_chrdev_region(devno,0,1,DEVICE_NAME); keyboard_major = MAJOR(devno); } if(ret<0) return ret;
key_devp = kmalloc(sizeof(struct key_dev),GFP_KERNEL); // 动态申请设备结构体的内存 if(!key_devp) { ret = -ENOMEM; goto fail_malloc; } memset(key_devp,0,sizeof(struct key_dev)); key_setup_cdev(key_devp,0);
for(i=0;i<KEY_NUM;i++) key_devp->key_status[i]=KEYSTATUS_UP; //初始化键盘状态 for(i=0;i<KEY_NUM;i++) key_devp->key_values[i]=0; //初始化键值 key_devp->key_values_flag=0; //表示四个按键都没有被按下 init_waitqueue_head(&key_devp->key_waitq); for(i=0;i<KEY_NUM;i++) setup_timer(&key_timer[i],key_timer_handler,(void *)&key_irqs[i]); //将&key_irqs[i]作为参数传入定时器中断处理程序 return 0; fail_malloc: unregister_chrdev_region(devno,1); return ret;}
/************************************************************************************************ 执行”rmmod key.ko”命令时就会调用这个函数 ************************************************************************************************/static void __exit key_exit(void){ /* 卸载驱动程序 */ printk(KERN_ALERT "******************unregister keyboard driver*************************/n"); cdev_del(&key_devp->cdev); //注消cdev kfree(key_devp); class_device_destroy(key_class, MKDEV(keyboard_major, 0)); //删掉类 class_destroy(key_class); //注销类结构体 unregister_chrdev_region(MKDEV(keyboard_major,0),1); // 释放设备号}
/* 这两行指定驱动程序的初始化函数和卸载函数 */module_init(key_init);module_exit(key_exit);
/* 描述驱动程序的一些信息,不是必须的 */MODULE_AUTHOR("Ma Dongpeng<madongpeng@hrbeu.edu.cn>"); // 驱动程序的作者MODULE_DESCRIPTION("keyborad Driver"); // 一些描述信息MODULE_LICENSE("GPL"); // 遵循的协议
makefile文件:
CC = arm-linux-gcc
HOSTCC = gcc
#######################################################################
KERNELDIR = /opt/S3C2440/linux-2.6.25.9
#######################################################################
obj-m := keybord.o
#module-objs := keybord.o
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o
应用程序:
/*************************************************************************NAME:test_keyboard.cAUTHOR:Ma Dongpeng<madongpeng@hrbeu.edu.cn> TIME:2011-02-19 17:03:57 VERSION:1.0.0**************************************************************************/#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/ioctl.h>#include <fcntl.h>int main(int argc, char **argv){ char read_data[4]={0,0,0,0}; int fd,i; printf("****************open keyboard***************/n"); fd = open("/dev/keyboard",O_RDONLY); if (fd < 0) { perror("erro open device keyboard"); exit(1); } while(1) { read(fd,read_data,4); printf("/nread data:"); for(i=0;i<4;i++) printf("%d ",read_data[i]); printf("/n"); } printf("*****************close keyboard***************/n"); close(fd); return 0;}