字符设备驱动程序之按键——中断机制

要想用中断方式编写应用程序,首先需要理解中断的流程。之前我们分析过中断流程的,现在再来分析一边,以便加深印象。
内核在start_kernel函数中调用trap_init、init_IRQ两个函数来设置异常的处理函数,首先我们想来看看trap_init函数,部分代码如下:
void __init trap_init(void)
{
.......................................................
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
.......................................................
}
分析:ARM构架CPU的异常向量基址可以是0x00000000,也可以是0xffff0000,linux内核使用后者。而trap_init函数就是用于将异常向量拷贝到0xffff0000处。上面的代码里,vectors为0xffff0000,__vectors_start和__vector_end之间存放的就是异常向量,他们被拷贝到地址 0xffff0000处, 但那只是一些简单的跳转指令,更加复杂的指令存放在__stubs_start和__stubs_end之间,他们被拷贝到 0xffff0000+200处。
下面我们把目光转到 __vectors_start处(arch/arm/kernel文件里),看一下代码:
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset

.globl __vectors_end
__vectors_end: 
其中:.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
分析:当相应的某个异常发生的时候,他会根据上面对应的异常向量跳转指令跳转到相应的位置进行处理。以b vector_irq + stubs_offset为例,当发生中断时,会执行它,然后跳转到vector_irq处(sut bs_offset用于从定位跳转的位置,暂时忽略也没有关系 ),可是我们搜遍代码页没有发现vector_irq这个标号。不过我们发现了如下代码:
vector_stub irq, IRQ_MODE, 4
经分析我们发现这是一条宏定义,相关代码如下:
.macro vector_stub, name, mode, correction=0   @ vector_stub irq, IRQ_MODE, 4
.align 5

vector_\name:                                                                    @ vector_irq
.if \correction
sub lr, lr, #\correction
.endif

stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #8] @ save spsr

mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0

and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
.endm
我们进行一下置换就得到了: vector_irq这个标号,其中最后一段代码是跳转指令,用于跳转到具体的中断入口。我们可以看看这个标号下面的内容:
vector_irq:

.long __irq_usr @  0  (USR_26 / USR_32)
.long __irq_invalid @  1  (FIQ_26 / FIQ_32)
.long __irq_invalid @  2  (IRQ_26 / IRQ_32)
.long __irq_svc @  3  (SVC_26 / SVC_32)
.long __irq_invalid @  4
.long __irq_invalid @  5
.long __irq_invalid @  6
.long __irq_invalid @  7
.long __irq_invalid @  8
.long __irq_invalid @  9
.long __irq_invalid @  a
.long __irq_invalid @  b
.long __irq_invalid @  c
.long __irq_invalid @  d
.long __irq_invalid @  e
.long __irq_invalid @  f
我们以.long __irq_usr为例进一步分析:
我们找到__irq_usr 这个标号,看一下其下面的代码:
__irq_usr:
usr_entry  @ 保存寄存器
................................................................................
irq_handler@ 这也是一个宏,它会最终调用函数asm_do_IRQ,这是个c函数
...............................................................................
接下来是c语言了,我们列出主要框架:
asm_do_IRQ
struct irq_desc *desc = irq_desc + irq; //以中断号为下标得到中断描述结构体数组中的一项
desc_handle_irq(irq, desc); //进入中断处理
desc->handle_irq(irq, desc); //要想知道这个函数是怎么回事,我们可以反推回去,不过为了便于分析,我们采取正叙的方法

void __init s3c24xx_init_irq(void)//初始化中断
set_irq_chip(irqno, &s3c_irqext_chip);
desc = irq_desc + irq;
desc->chip = chip; //设置一些底层硬件操作函数
  set_irq_handler(irqno, handle_edge_irq); 
__set_irq_handler(irq, handle, 0, NULL);
desc = irq_desc + irq;
desc->handle_irq = handle;
到这一步我们将desc->handle_irq=handle_edge_irq,在结合上面的desc->handle_irq(irq, desc),我们知道上面就是执行handle_edge_irq这条指令,那么handle_edge_irq有做了什么呢?我们接着看代码,搭建框架:
handle_edge_irq
handle_IRQ_event(irq, action);
do {
ret = action->handler(irq, action->dev_id); //这就是中断处理函数
action = action->next; //这是中断链表
} while (action);
由此我们看出发生中断后会跳转到入口函数 asm_do_IRQ,之后会根据中断号找到中断处理函数来执行。关于上面提到的三个结构体,我们还要在来分析一下:

struct irq_desc {
irq_flow_handler_t handle_irq; //当前中断的处理入口函数,我们会将中断处理函数赋给它
struct irq_chip *chip; //底层的硬件访问
.................................
struct irqaction *action; //中断处理函数列表
unsigned int status; //中断状态
................................
const char *name; //中断名称

struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq); //启动中断,默认是enable
void (*shutdown)(unsigned int irq); //关闭中断,默认是disable
void (*enable)(unsigned int irq); //使能中断,默认是unmask
void (*disable)(unsigned int irq);/ /禁止中断,默认是mask

void (*ack)(unsigned int irq); //响应中断,通常是清除当前中断使得可以接收下一个中断
void (*mask)(unsigned int irq); //屏蔽中断源
void (*mask_ack)(unsigned int irq); //屏蔽和响应中断
void (*unmask)(unsigned int irq); //开启中断
..................................................
};

struct irqaction {
irq_handler_t handler; //用户注册的中断处理函数
unsigned long flags; //中断标志
cpumask_t mask; //用于SMP(对称多处理器系统)
const char *name; //用户注册的中断名称
void *dev_id; //用户上面传过来的handler参数,还可以用来区分共享中断
struct irqaction *next;
int irq;                 //中断号
struct proc_dir_entry *dir;
};
通过上面的分析我们知道了中断发生后最终调用到desc->action->handler,于是我们就知道注册中断的话必须根据中断号将中断处理函数赋给
desc->action->handler,那么到底是不是呢?我们来查看代码:
以一个中断注册函数为例:
request_irq(IRQ_EINT0,  buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
action->handler = handler; //中断处理函数放在这里了
retval = setup_irq(irq, action); //设置中断
struct irq_desc *desc = irq_desc + irq; //以中断号为下标构造中断描述结构体数组中的一项,发生中断的时候我们会根据中断号取出相应 //的结构体
到此为止,中断的大体流程我们就分析明白了,下面我们就以中断方式来编写按键驱动,请看代码:
驱动程序代码如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

#define DEVICE_NAME "keys"

volatile unsigned long *gpfcon=NULL;
volatile unsigned long *gpfdat=NULL;
volatile unsigned long *gpgcon=NULL;
volatile unsigned long *gpgdat=NULL;

static struct class *keys_class;
static struct class_device *keys_class_devs;

struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
static unsigned char key_val;
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

static volatile int ev_press = 0;
static irqreturn_t buttons_irq(int irq, void *dev_id) 
{
    struct pin_desc * pindesc = (struct pin_desc *)dev_id;
    unsigned int pinval;
    pinval = s3c2410_gpio_getpin(pindesc->pin);
    if (pinval)
{
key_val = 0x80 | pindesc->key_val;
}
else
{
key_val = pindesc->key_val;
}
    ev_press=1;
    wake_up_interruptible(&button_waitq); 
    return IRQ_RETVAL(IRQ_HANDLED);
}

static int s3c24xx_keys_open(struct inode *inode, struct file *file)
{
    request_irq(IRQ_EINT0,  buttons_irq, IRQT_BOTHEDGE, "S2",&pins_desc[0]);
    request_irq(IRQ_EINT2,  buttons_irq, IRQT_BOTHEDGE, "S3",&pins_desc[1]);
    request_irq(IRQ_EINT11,buttons_irq, IRQT_BOTHEDGE, "S4",&pins_desc[2]);
    request_irq(IRQ_EINT19,buttons_irq, IRQT_BOTHEDGE, "S5",&pins_desc[3]);
    return 0;
}

static int s3c24xx_keys_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
{
wait_event_interruptible(button_waitq, ev_press);
       copy_to_user(buff, &key_val, 1);
        ev_press=0;
        return 1;
}


int s3c24xx_keys_close(struct inode *inode, struct file *file)
{
    free_irq(IRQ_EINT0,  &pins_desc[0]);
    free_irq(IRQ_EINT2,  &pins_desc[1]);
    free_irq(IRQ_EINT11,&pins_desc[2]);
    free_irq(IRQ_EINT19,&pins_desc[3]);
    return 0;
}

static struct file_operations s3c24xx_keys_fops = {
    .owner  =   THIS_MODULE,   
    .open   =   s3c24xx_keys_open,     
    .read = s3c24xx_keys_read,   
    .release = s3c24xx_keys_close,
};
int major;
static int __init s3c24xx_keys_init(void)
{
    major=register_chrdev(0, DEVICE_NAME, &s3c24xx_keys_fops);//auto distribute major
    keys_class = class_create(THIS_MODULE, "keys_class");
    keys_class_devs = class_device_create(keys_class, NULL, MKDEV(major, 0), NULL, "keys");
    gpfcon=(volatile unsigned long *)ioremap(0x56000050,16);
    gpfdat=gpfcon+1;
    gpgcon=(volatile unsigned long *)ioremap(0x56000060,16);
    gpgdat=gpgcon+1;
     printk(DEVICE_NAME " initialized\n");
     return 0;
}

static void __exit s3c24xx_keys_exit(void)
{
    unregister_chrdev(major, DEVICE_NAME);
    class_device_unregister(keys_class_devs);
    class_destroy(keys_class);
    iounmap(gpfcon);
    iounmap(gpgcon);
}
module_init(s3c24xx_keys_init);
module_exit(s3c24xx_keys_exit);
MODULE_LICENSE("GPL");

测试程序代码如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char **argv)
{
int fd;
unsigned char key_val;
fd = open("/dev/keys", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}
while (1)
{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
return 0;
}
关于这个程序我们简单讲解一下它的功能:当没有按键按下的时候,会进入休眠,当有按键按下的时候,会进入中断服务程序,在中断服务程序里把进程唤醒。这样的话就不会老是查询,以致cpu占用过多。
有几个小知识点要讲解一下:
(1) DECLARE_WAIT_QUEUE_HEAD(button_waitq),用于定义一个等待队列
(2)wait_event_interruptible(button_waitq, ev_press):当ev_press为0时将当前进程加入等待队列button_waitq中,进入休眠,否则不休眠
(3)wake_up_interruptible(&button_waitq):唤醒等待队列button_waitq中的成员
(4) s3c2410_gpio_getpin(pindesc->pin):获取引脚状态
(5)命令行:./keytest &   :表示在后台运行keytest程序
(6)命令行:kill -9 xxx     :表示强制杀死进程号为xxx的进程
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值