按键字符设备用到了关于中断和等待队列的知识:
基于友善之臂micro2440,linux内核版本为2.6.29.4。
目的:控制六个开关,每个开关能够单独打开和关闭。
源代码如下所示:
头文件 key.h:
#ifndef __KEY_H
#define __KEY_H
#define KEY_MAJOR 200
#define DRIVER_NAME "key_driver"
#define KEY_NR 6 //六个设备,注册六个次设备号
struct irq_key_descriptor{
unsigned int irq;
unsigned int flags;
const char *dev_name;
};
#endif
源文件 key.c:
#include<linux/module.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<linux/fs.h>
#include<linux/interrupt.h>
#include<mach/regs-gpio.h>
#include<mach/hardware.h>
#include<linux/irq.h>
#include<asm/irq.h>
#include<linux/types.h>
#include<linux/platform_device.h>
#include<linux/slab.h>
#include<asm/uaccess.h>
#include "key.h"
unsigned int key_major=KEY_MAJOR;
module_param(key_major,int,S_IRUGO);
dev_t dev_num = MKDEV(KEY_MAJOR,0); // MKDEV宏中必须是常量
volatile bool condition=0;
struct cdev cdevp;
DECLARE_WAIT_QUEUE_HEAD(wait_head); //静态定义一个等待队列
struct irq_key_descriptor key_irq_table[]={
{IRQ_EINT8,IRQF_DISABLED,"key0"},
{IRQ_EINT11,IRQF_DISABLED,"key1"},
{IRQ_EINT13,IRQF_DISABLED,"key2"},
{IRQ_EINT14,IRQF_DISABLED,"key3"},
{IRQ_EINT15,IRQF_DISABLED,"key4"},
{IRQ_EINT19,IRQF_DISABLED,"key5"},
};
//可以不要,因为有专门的函数可以通过中断口得到具体的IO端口
unsigned long GPIO_key_table[]={
S3C2410_GPG0,
S3C2410_GPG3,
S3C2410_GPG5,
S3C2410_GPG6,
S3C2410_GPG7,
S3C2410_GPG11,
};
irqreturn_t key_interrupt(int irq,void *dev_id){
volatile unsigned int *press_count=(volatile unsigned int *)dev_id;
*press_count=*press_count+1;
condition=1;
printk("%d times interrupt has happened!\n",*press_count);
wake_up_interruptible(&wait_head);
return IRQ_HANDLED;
}
int key_open(struct inode *inode,struct file *filp){
long err;
unsigned long dev_number=MINOR(inode->i_rdev);
struct irq_key_descriptor key_irq = key_irq_table[dev_number];
unsigned int *dev_id;
dev_id = kmalloc(sizeof(unsigned int),GFP_KERNEL);
if(!dev_id){
printk(KERN_WARNING "memory kmalloc went wrong!\n");
return 0;
}
memset((void *)dev_id,0,sizeof(unsigned int));
err = request_irq(key_irq.irq,key_interrupt,key_irq.flags,key_irq.dev_name,dev_id);
if (err<0){
printk(KERN_WARNING "irq request went wrong!\n");
return -EBUSY;
}
filp->private_data = dev_id;
printk(KERN_NOTICE "key%ld is opened\n",dev_number);
return 0;
}
int key_release(struct inode *inode,struct file *filp){
unsigned int *dev_id = (unsigned int *)(filp->private_data);
unsigned long dev_number = MINOR(inode->i_rdev);
struct irq_key_descriptor key_irq = key_irq_table[dev_number];
free_irq(key_irq.irq,(void *)dev_id);
return 0;
}
ssize_t key_read(struct file *filp,char __user *buffer,size_t count,loff_t *offp){
long err;
unsigned int *dev_id = (unsigned int*)(filp->private_data);
wait_event_interruptible(wait_head,condition);
condition = 0;
err = copy_to_user(buffer,dev_id,count);
printk(KERN_NOTICE "err is %ld\n",err);
*dev_id = 0;
if(!err)
return 0;
else{
printk(KERN_NOTICE "Data haven't copy to user space\n");
return -EFAULT;
}
}
struct file_operations key_ops={
.owner = THIS_MODULE,
.open = key_open,
.release = key_release,
.read = key_read,
};
int key_cdev_setup(void){
int err;
cdev_init(&cdevp,&key_ops);
cdevp.owner = THIS_MODULE;
cdevp.ops = &key_ops;
cdev_add(&cdevp,dev_num,KEY_NR);
if(IS_ERR(&err)){
printk(KERN_NOTICE"Error %d addint key_driver\n",err);
return err;
}
return 0;
}
static int __init keyd_init(void){
int ret;
unsigned int i;
if(key_major){
ret = register_chrdev_region(dev_num,KEY_NR,DRIVER_NAME);
}
else{
ret = alloc_chrdev_region(&dev_num,0,KEY_NR,DRIVER_NAME);
}
if(ret<0){
printk(KERN_WARNING "key:can't get major device id %d\n",key_major);
return ret;
}
ret = key_cdev_setup();
if(ret!=0){
return ret;
}
for(i=0;i<6;i++){
s3c2410_gpio_cfgpin(GPIO_key_table[i],S3C2410_GPIO_IRQ);
s3c2410_gpio_pullup(GPIO_key_table[i],1); //Disable the pullup function
set_irq_type(key_irq_table[i].irq,IRQ_TYPE_EDGE_FALLING);
s3c2410_gpio_irqfilter(GPIO_key_table[i],1,S3C2410_EINTFLT_EXTCLK|63);
}
printk("device_driver installed!\n");
return 0;
}
static void __exit keyd_exit(void){
cdev_del(&cdevp);
unregister_chrdev_region(dev_num,KEY_NR);
printk("device_driver unistalled successfully!\n");
}
module_init(keyd_init);
module_exit(keyd_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Xxing");
测量文件key_test.c:
#include<stdio.h>
#include<fcntl.h>
#include<sys/stat.h>
int main(int argc,char *argv[]){
volatile unsigned int count=0;
int err;
unsigned int ret=0;
int fd;
fd = open(argv[1],O_RDONLY);
if(fd<0){ printf("can not open file %d\n",argv[1]);
return -1;
}
while(1){
err = read(fd,&ret,1);
if(err){
printf("data is not read\n");
continue;
}
printf("return data is: %d\n",ret);
if(ret){
count++;
printf("key has been press %d times\n",count);
}
}
}
需要注意的问题:
1,file结构体read函数的第二个参数为(char __user *)类型,因此从用户空间传过来的指针会强制转换成此类型,在调用copy_to_user(void *to,const void *from,unsigned long n)函数时,无论传入的第一个参数和第二个是指向什么样的数据类型,都只会拷贝char类型的数据到to所指向的地址。这样理解是正确的吗?因为当我把传入的第一个参数强制转换成我所需要的数据类型(如unsigned int)后(此时n设置为1)依然只拷贝1个字节的数据。
2,不管是测试文件还是驱动文件,使用指针时必须要先初始化指针,即要明确给定一个地址赋给指针。可用内存分配的函数。
3,卸载模块函数,要先删除设备再释放设备号,如果相反,则卸载模块后在/proc/devices文件中还会存在设备号与设备名,即仍然占用着设备号。
4,测试文件中,打开文件要用绝对路径。 不要忘了"/"
5,所写的驱动文件没有加上防抖的功能,因此会出现按一次键发生多次中断的情况。驱动还需要完善。