您的位置:首页 > 其它

字符设备驱动--中断方式下的按键驱动

2017-07-10 17:25 561 查看

LINUX异常处理体系结构

内核在start_kernel中调用trap_init,init_IRQ设置异常的处理函数。

trap_init函数分析:Traps.c(arch/arm/kernel)

traps.c用来设置各种异常的处理向量,向量即固定地址的代码,异常发生,CPU处理执行固定地址的代码。

void __init early_trap_init(void)
{
#endif
unsigned long vectors = CONFIG_VECTORS_BASE;
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
extern char __kuser_helper_start[], __kuser_helper_end[];
int kuser_sz = __kuser_helper_end - __kuser_helper_start;

/*
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
* into the vector page, mapped at 0xffff0000, and ensure these
* are visible to the instruction stream.
*/
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

...
}


分析:

vectors = CONFIG_VECTORS_BASE,去内核的顶层目录里边的“.config”文件搜索,可以发现CONFIG_VECTORS_BASE=0xffff0000

中断向量就存放在0xffff0000地址开始处,中断异常触发cpu怎样找到这个地址?

在ARM920T的使用手册里:协处理控制寄存器CP15的C1寄存器的第[13]位用来设置异常向量的存放位置的,该位为0存放到0x0000000开始处,为1存放到0xffff0000开始处。

_vectors_start到 __vectors_end之间的代码为异常向量,复制到0xffff0000

__stubs_start到__stubs_end存放复杂的代码,复制到0xffff0000+0x200

异常的处理向量设置完成后,cpu怎样处理中断异常?

__vectors_start, __vectors_end在arch/arm/kernel/entry_armv.S中定义,代码如下:

.equ    stubs_offset, __vectors_start + 0x200 - __stubs_start

.globl  __vectors_start
__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:


vector_und、vector_pabt等表示要跳转去执行的代码,接下来通过分析vector_stub的宏定义来理解vector_und。

vector_stub 的宏定义如下:

.macro  vector_stub, name, mode, correction=0
.align  5

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

@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception> 保存一些寄存器
@ (parent CPSR)
@
stmia   sp, {r0, lr}        @ save r0, lr
mrs lr, spsr
str lr, [sp, #8]        @ save spsr

@
@ Prepare for SVC32 mode.  IRQs remain disabled.转换为管理模式
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0

@
@ the branch table must immediately follow this code.
@
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs    pc, lr          @ branch to handler in SVC mode
.endm


vector_stub功能:计算异常处理完后的返回地址、保存一些寄存器,进入管理模式,根据被中断的工作模式调用跳转分支。

“vector_stub und, UND_MODE”

vector_stub und, UND_MODE
@
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>  lr=上一个模式被打断时的PC值,下面三条指令是保护上个模式的现场
@ (parent CPSR)
@
stmia   sp, {r0, lr}    @ save r0, lr
mrs lr, spsr                @准备保存上个模式的cpsr值
str lr, [sp, #8]        @ save spsr

@
@ Prepare for SVC32 mode.  IRQs remain disabled.准备切换到SVC32模式
@
mrs r0, cpsr          @ r0=0x1b  (UND_MODE)
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0

@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f          @ 执行这条指令之前:lr = 上个模式的cpsr值,现在取出其低四位--模式控制位的[4:0]。查看2440芯片手册可以知道,这低4位二进制值为十进制数值的 0-->User_Mode; 1-->Fiq_Mode; 2-->Irq_Mode; 3-->SVC_Mode; 7-->Abort_Mode; 11-->UND_Mode,那些异常处理分支是依赖这4位的值来实现的

mov r0, sp       @ 将SP值保存到R0是为了之后切换到SVC模式时将这个模式下堆栈中的信息转而保存到SVC模式下的堆栈中
ldr lr, [pc, lr, lsl #2]
movs    pc, lr          @ branch to handler in SVCmode   在跳转到第二级分支的同时CPU的工作模式从UND_MODE强制切换到SVC_MODE,这是由于MOVS指令在赋值的同时会将spsr的值赋给cpsr
ENDPROC(vector_und)
.long   __und_usr           @  0 (USR_26 / USR_32)运行用户模式下触发未定义指令异常
.long   __und_invalid           @  1 (FIQ_26 / FIQ_32)
.long   __und_invalid           @  2 (IRQ_26 / IRQ_32)
.long   __und_svc           @  3 (SVC_26 / SVC_32)运行用户模式下触发未定义指令异常
.long   __und_invalid           @  4 其他模式下面不能发生未定义指令异常,否则都使用__und_invalid分支处理这种异常
.long   __und_invalid           @  5
.long   __und_invalid           @  6
.long   __und_invalid           @  7
.long   __und_invalid           @  8
.long   __und_invalid           @  9
.long   __und_invalid           @  a
.long   __und_invalid           @  b
.long   __und_invalid           @  c
.long   __und_invalid           @  d
.long   __und_invalid           @  e
.long   __und_invalid           @  f


注:在arch\arm\include\asm\ptrace.h中有:#define SVC_MODE 0x00000013 和 #define UND_MODE 0x0000001b

Linux的中断管理的设计思路:异常事件触发,cpu自动跳到异常向量表处执行,同时也切换到对应的模式,但是随后立即有段代码强制让cpu切换到SVC管理模式(内核模式)进行异常处理。cpu绝大部分时间是停留在user和svc模式的,要不就是user模式下正常工作,要不就是svc模式下异常处理,那段切换的时间完全被忽略。

中断处理体系结构

① irq_desc(include/linux/irq.h) 结构数组描述这些中断

struct irq_desc {
irq_flow_handler_t  handle_irq; /*中断处理函数入口*/
struct irq_chip     *chip;/*底层的硬件访问*/
struct msi_desc     *msi_desc;
void            *handler_data;
void            *chip_data;
struct irqaction    *action;    /* 用户提供的IRQ action list */
unsigned int        status;     /* IRQ status */

unsigned int        depth;      /* nested irq disables */
unsigned int        wake_depth; /* nested wake enables */
unsigned int        irq_count;  /* For detecting broken IRQs */
unsigned int        irqs_unhandled;
spinlock_t      lock;

...

const char      *name;/*中断名称*/
} ____cacheline_internodealigned_in_smp;


② 发生中断,总中断入口函数asm_do_IRQ根据中断号调用irq_desc数组中的handle_irq.

handle_irq使用chip结构中的函数来清除、屏蔽或重新使能中断,还一一调用用户在action链表中注册的中断处理函数。

struct irq_chip {
const char  *name;
unsigned int    (*startup)(unsigned int irq);/*开启中断*/
void        (*shutdown)(unsigned int irq);/*关闭中断*/
void        (*enable)(unsigned int irq);/*使能中断*/
void        (*disable)(unsigned int irq);/*禁止中断*/

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

...

}


③ 用户注册的每个中断处理函数用一个irqaction结构来表示,irqaction的结构体定义如下

struct irqaction {
irq_handler_t handler;/*用户注册的中断处理函数*/
unsigned long flags;/*中断标志,*/
cpumask_t mask;
const char *name;/*用户注册的中断名*/
void *dev_id;/*用户传给上面handler的参数*/
struct irqaction *next;
int irq;/*中断号*/
struct proc_dir_entry *dir;/* 指向IRQn相关的/proc/irq/n目录的描述符 */
};


irq_desc结构数组,成员 struct irq_chip *chip,中断处理函数struct irqaction *action构成中断处理体系。

中断处理流程:

1)发生中断时,CPU执行异常向量vector_irq的代码。

2)在vector_irq里面,最终会调用中断处理的总入口函数asm_do_IRQ。

3)asm_do_IRQ根据中断号调用irq_desc数组项中的handle_irq。

4)handle_irq会使用chip成员中的函数来设置硬件,比如清除中断,禁止中断,重新使能中断等。

5)handle_irq逐个调用用户在action链表中注册的处理函数。

中断处理体系结构初始化

①init_IRQ()(arch/arm/kernel/irq.c)用来初始化中断处理体系结构

void __init init_IRQ(void)
{
int irq; //中断号用来在irq_desc[NR_IRQS]定位对应的中断

for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE; //初始化irq_desc结构数组每一项的中断状态

init_arch_irq(); //调用架构相关的中断初始化函数,这里是S3C24xx_init_irq
}


S3C24xx_init_irq的定义在linux/arch/arm/plat-s3c24xx/irq.c中,部分程序如下:

for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
irqdbf("registering irq %d (extended s3c irq)\n", irqno);
set_irq_chip(irqno, &s3c_irqext_chip);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}


分析:1.set_irq_chip作用irq_desc[irqno].chip=&s3c_irqext_chip,以后通过irq_desc[irqno].chip的结构指针来设置中断 触发方式

2.设置handle_edge_irq为中断的处理函数入口,irq_desc[irqno].handle_irq=handle_edge_irq

3.RQF_VALID表示可使用

②函数request_irq()项内核注册中断处理函数。根据中断号找到irq_desc数组项,然后在action链表中添加一个表项。

int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id){
action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
...
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);

}


参数irq是设备中断求号,在向irq_desc []数组登记时,它做为数组的下标。把中断号为irq的irqaction结构体的首地址写入irq_desc [irq]->action。这样就把设备的中断请求号与该设备的中断服务例程irqaction联系在一起。

request_irq用这4个参数构成一个irqaction结构体,然后调用setup_irq将irqaction链入链表。

setup_irq作用

1.将新建的irqaction结果链入irq_desc[irq]的action链表中。

2.为irq_desc[irq]结构中的chip(还没有设置的)成员设置指针,指向默认函数

3.设置中断的触发方式。request_irq中的参数irqflags.

4.启动中断。

request_irq作用:

1.irq_desc[irq]结构中action链表中已经链入用户注册的中断处理函数。

2.中断触发方式设置好了

3.中断已使能

③中断处理过程

asm_do_IRQ(linux/arch/arm/kernel/irq.c)函数是中断的总入口函数。

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct irq_desc *desc = irq_desc + irq;/*根据中断号,在irq_desc中断数组里面取到中断项*/
...
irq_enter();
...
desc_handle_irq(irq, desc);
irq_exit();
set_irq_regs(old_regs);
}


发生中断时,INTPND寄存器的某一位置1,INTOFFSET寄存器记录下是哪一位,中断向量调用asm_do_IRQ之前根据INTOFFSET寄存器的值确定irq的值。(举例说明:书P407)



④卸载中断处理函数

free_irq(linux/kernel/irq/manage.c)

free_irq(unsigned int irq, void *dev_id)


irq定位action链表,dev_id在action链表中找到要卸载的表项。

按键驱动(中断方式)

① third_drv_open函数的写法根据

int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)


说明:irq:中断号 handler;处理函数 irqflags;中断处理标志 devname;设备名 dev_id:卸载时用到。

irqflags的定义在include/linux/interrupt.h中

#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001 //上升沿触发中断
#define IRQF_TRIGGER_FALLING 0x00000002 //下降沿触发中断
#define IRQF_TRIGGER_HIGH 0x00000004 //高电平触发中断
#define IRQF_TRIGGER_LOW 0x00000008 //低电平触发中断
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010


配置引脚根据原理图



/* 配置GPF0,2为输入引脚 */
/* 配置GPG3,11为输入引脚 */
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]);


②在
static struct file_operations third_drv_fops = {};


中添加
.release =  third_drv_close


去file_operations的定义查看release的格式

int (*release) (struct inode *, struct 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]);




写中断处理函数
buttons_irq(int irq, void *dev_id)


先定义结构体

struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val;
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};


通过判断pinval(引脚值)的值来确定中断

if (pinval)/*高电平*/
{
/* 松开 */
key_val = 0x80 | pindesc->key_val;
}
else
{
/* 按下 */
key_val = pindesc->key_val;
}


third_drv_read


/* 如果没有按键动作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);

/* 如果有按键动作, 返回键值 */
copy_to_user(buf, &key_val, 1);


驱动程序 third_drv.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>

static struct class *thirddrv_class;
static struct class_device *thirddrv_class_dev;

volatile unsigned long *gpfcon;
volatile unsigned long *gpfdat;

volatile unsigned long *gpgcon;
volatile unsigned long *gpgdat;

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中断事件标志, 中断服务程序将它置1,third_drv_read将它清0 */
static volatile int ev_press = 0;

struct pin_desc{
unsigned int pin;
unsigned int key_val;
};

/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val;

struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};

/*
* 确定按键值
*/
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
struct pin_desc * pindesc = (struct pin_desc *)dev_id;/*定义一个指针=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 third_drv_open(struct inode *inode, struct file *file)
{
/* 配置GPF0,2为输入引脚 */
/* 配置GPG3,11为输入引脚 */
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;
}

ssize_t third_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
if (size != 1)
return -EINVAL;

/* 如果没有按键动作, 休眠 */ wait_event_interruptible(button_waitq, ev_press); /* 如果有按键动作, 返回键值 */ copy_to_user(buf, &key_val, 1);
ev_press = 0;

return 1;
}

int third_drv_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 third_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = third_drv_open,
.read = third_drv_read,
.release = third_drv_close,
};

int major;
static int third_drv_init(void)
{
major = register_chrdev(0, "third_drv", &third_drv_fops);

thirddrv_class = class_create(THIS_MODULE, "third_drv");

thirddrv_class_dev = class_device_create(thirddrv_class, NULL, MKDEV(major, 0), NULL, "buttons"); /* /dev/buttons */

gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
gpfdat = gpfcon + 1;

gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
gpgdat = gpgcon + 1;

return 0;
}

static void third_drv_exit(void)
{
unregister_chrdev(major, "third_drv");
class_device_unregister(thirddrv_class_dev);
class_destroy(thirddrv_class);
iounmap(gpfcon);
iounmap(gpgcon);
return 0;
}

module_init(third_drv_init);

module_exit(third_drv_exit);

MODULE_LICENSE("GPL");


测试程序 thirddrvtest .c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

/* thirddrvtest
*/
int main(int argc, char **argv)
{
int fd;
unsigned char key_val;

fd = open("/dev/buttons", 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);
sleep(5);
}

return 0;
}


测试结果



睡眠状态



按键按下松开



CPU占用2% 中断的优势

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: