学习目标:在字符设备驱动学习(1)的按键查询基础上改为按键中断。
1、什么是中断
中断是异常的一种,异常就是可以打断cpu正常运行流程的一些事情,比如外部中断、未定义指令、swi指令。cup暂停当前的程序,先处理异常事件,然后再继续执行被中断的程序。
2、linux内核对异常的设置
内核在strrt_kernel函数中调用了trap_init函数和init_IRQ函数来设置异常的处理函数。
①memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
这两个函数对异常向量的拷贝,__vectors_end 至 __vectors_start之间为异常向量表。
位于arch/arm/kernel/entry-armv.S中:拷贝到0xffff0000,将异常处理函数从__stubs_start至__stubs_end拷贝到0xffff0000+0x200处。
②.equ stubs_offset, __vectors_start + 0x200 - __stubs_start 复制完后,重新定位跳转的位置。(向量被复制到0xffff0000处,跳转的目的代码被复制到xffff0000+0x200处)
注:定义静态符号: .equ 命令用于把常量值设置为可以在文本段中使用的符号
如:
.equ factor, 3
.equ LINUX_SYS_CALL, 0x80
经过设置之后,数据符号值是不能在 程序中改动的。
3、中断处理的体系结构(内核分析)
内核中将所有的中断统一编号,使用一个irq_desc的结构数组来描述中断,数组中每项元素对应一个中断,也可能是一组中断,它们公用一个中断号,里面记录了中断名称、状态、标记(类型、是否共享)并提供中断的底层硬件访问函数(清除、屏蔽、使能中断),可提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。
中断处理体系结构的初始化(irq_desc结构体注组成的数组的初始化):init_IRQ()用于初始化:
void __init init_IRQ(void)
{
int irq;
for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
...
init_arch_irq();
}
1、for循环是初始化数组元素的状态
2、init_arch_irq():跟体系结构相关,不同平台对应函数不一样。最终调用与平台相关的函数——>XXX_init_irq(),主要功能是对数组中每项irq_desc中的元素赋值,chip、handle_irq等。
用户注册中断处理函数(驱动程序):通过request_irq()函数向内核注册处理函数。根据中断号,找到对应中断的irq_desc成员,在irq_desc的成员action(链表)添加一个节点。
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
{
struct irqaction *action;
int retval;
........
action = kmalloc(sizeof(struct irqaction),
...
action->handler = handler;
action->flags = irqflags;
cpus_clear(action->mask);
action->name = devname;
action->next = NULL;
action->dev_id = dev_id;
....
retval = setup_irq(irq, action);
if (retval)
kfree(action);
return retval;
4、在查询基础上改为中断
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <asm/irq.h>
#include <asm/arch/regs-gpio.h>
#define KEY_DRIVER1_MAJOR 0
/*休眠队列*/
static DECLARE_WAIT_QUEUE_HEAD(boutton_qeueue);
/*中断事件标志,中断中进行置1,read中将它清0*/
static volatile int en_press = 0;
/*声明用到的结构体和变量*/
struct cdev key_driver1_cdev;
static int key_driver1_major = KEY_DRIVER1_MAJOR;
dev_t key_dev_num;//dev_t类型设备号
static struct class *key_drv_class;
static struct class_device *key_drv_class_dev;
/*用于地址映射*/
volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;
volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;
#define rGPFCON 0X56000050
#define rGPGCON 0X56000060
struct pin_desc{
unsigned int pin;
unsigned char key_val;
};
/*按下时:0x01,02,03,04*/
struct pin_desc pin_desc[4] = {
{S3C2410_GPF0, 0X01},//刚开始吧0写错为1,导致按下按键总是一个值
{S3C2410_GPF2, 0X02},
{S3C2410_GPG3, 0X03},
{S3C2410_GPG11,0X04},
};
static unsigned char key_val = 0;//按键值
/*
*
*/
static irqreturn_t button_irq(int irq, void *dev_id)
{ struct pin_desc *p = (struct pin_desc *)dev_id;
unsigned int pin_val;
pin_val = s3c2410_gpio_getpin(p->pin);
key_val = 0;
if(pin_val)
{
/*松开*/
key_val = 0x80 | p->key_val;
//printk("kernel key_val = 0x%x\n",key_val);//调试用的
}
else
{
/*按下*/
key_val = p->key_val;
//printk("kernel anxia key_val = 0x%x\n",key_val);//调试用的
}
en_press = 1; //表示中断发生
wake_up_interruptible(&boutton_qeueue);//唤醒休眠的进程
return IRQ_HANDLED;
}
static int key_driver1_open1(struct inode *node, struct file *file)
{
/*采用查询方式,将按键的引脚进行配置为输入*/
//*gpfcon &= ~((0x3 << (0*2)) | (0x3 << (2*2)));
//*gpgcon &= ~((0x3 << (3*2)) | (0x3 << (11*2)));
/*中断注册函数*/
request_irq(IRQ_EINT0,button_irq,IRQT_BOTHEDGE,"S2",&pin_desc[0]);
request_irq(IRQ_EINT2,button_irq,IRQT_BOTHEDGE,"S3",&pin_desc[1]);
request_irq(IRQ_EINT11,button_irq,IRQT_BOTHEDGE,"S4",&pin_desc[2]);
request_irq(IRQ_EINT19,button_irq,IRQT_BOTHEDGE,"S5",&pin_desc[3]);
return 0;
}
static int key_driver1_close(struct inode *inode, struct file *file)
{
free_irq(IRQ_EINT0,&pin_desc[0]);
free_irq(IRQ_EINT2,&pin_desc[1]);
free_irq(IRQ_EINT11,&pin_desc[2]);
free_irq(IRQ_EINT19,&pin_desc[3]);
return 0;
}
static int key_driver1_read (struct file * file, char __user * buf, size_t size, loff_t *ppos)
{
int ret;
if(size != 1)
return -EINVAL;
/* 如果没有按键安动作,休眠,如果有按键按下,就直接返回*/
wait_event_interruptible(boutton_qeueue,en_press);
ret = copy_to_user(buf,&key_val,1);
en_press = 0;
return 1;
}
/*文件操作结构体*/
static const struct file_operations key_driver1_fops =
{
.owner = THIS_MODULE,
.read = key_driver1_read,
.open = key_driver1_open1,
.release = key_driver1_close
};
/*设备驱动模块加载函数*/
int key_driver1_init(void)
{
int result;
int err;
key_dev_num = MKDEV(key_driver1_major, 0);
/* 申请设备号*/
if (key_driver1_major)
result = register_chrdev_region(key_dev_num, 1, "key_driver1");
else /* 动态申请设备号 */
{
result = alloc_chrdev_region(&key_dev_num, 0, 1, "key_driver1");
key_driver1_major = MAJOR(key_dev_num);
}
if (result < 0)
return result;
/*
*进行cdev的初始化,将cdev与key_driver1_fops关联
*/
cdev_init(&key_driver1_cdev, &key_driver1_fops);
key_driver1_cdev.owner = THIS_MODULE;
key_driver1_cdev.ops = &key_driver1_fops;
err = cdev_add(&key_driver1_cdev, key_dev_num, 1);
if (err)
printk(KERN_NOTICE "Error %d ", err);
/*
* 为设备创建类
* 加载驱动模块时在/sys/class 目录下自动创建car_class 文件夹
*/
key_drv_class = class_create(THIS_MODULE, "key_driver1");
if(IS_ERR(key_drv_class))
{
printk("cannot create key_drv_class!\n");
return 0;
}
/*创建设备文件节点,避免需要手动创建*/
key_drv_class_dev = class_device_create(key_drv_class, NULL,key_dev_num, NULL, "key_driver1");
/*将GPIO 的物理地址映射到内核空间*/
gpfcon = (volatile unsigned long *)ioremap(rGPFCON, 16);
gpfdat = gpfcon + 1;
gpgcon = (volatile unsigned long *)ioremap(rGPGCON, 16);
gpgdat = gpgcon + 1;
return 0;
}
/*模块卸载函数*/
void key_driver1_exit(void)
{
cdev_del(&key_driver1_cdev); /*注销cdev*/
unregister_chrdev_region(key_dev_num, 1); /*释放设备号*/
/*删除设备文件和dev/class 目录下的相应文件夹*/
class_device_unregister(key_drv_class_dev);
class_destroy(key_drv_class);
/*解除GPIO 映射*/
iounmap(gpfcon);
iounmap(gpgcon);
}
MODULE_AUTHOR("healer");
MODULE_LICENSE("Dual BSD/GPL");
module_init(key_driver1_init);
module_exit(key_driver1_exit);
应用程序代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main(int arvc,char **argv)
{
int fd;
int count = 1;
unsigned char key_val = 0;
fd = open("/dev/key_driver1",O_RDWR);
if(fd < 0)
{
printf("can not open!\n");
}
while(1)
{
read(fd,&key_val,1);
printf("key_val =0x%x\n",key_val);
}
return 0;
Makefile
ifneq ($(KERNELRELEASE),)
obj-m := inter_key.o
else
PWD := $(shell pwd)
KDIR := /home/healer/myshare/linuxkernel/linux-2.6.22.6
all:
make -C $(KDIR) M=$(PWD)
clean:
rm -rf *.o *.ko *.mod.c *.symvers *.c~ *~
endif
makefile代码:
obj-m := inter_key.o 根据自己编写的驱动文件名来定,xxx.c对应这里obj-m :=xxx.o
KDIR := /home/healer/myshare/linuxkernel/linux-2.6.22.6为内核代码所在目录(根据自己内核目录定),因为我们编写的驱动时以模块的形式,需要内核代码得支持。