学习了韦东山一期视频,关于按键驱动部分,他用了查询的方式,中断的方式,poll机制,和异步通知的方式来做,并渗透了同步互斥,阻塞的知识。其中,我对中断的方式和异步通知很感兴趣,这两种方式对于推进自己所做的项目有很大的指导意义。但是课程中使用的开发板是JZ 2440,并且内核版本为2.6,所以需根据手头的omapl138开发板和Linux3.3内核做一些改进。
本文分为两部分,第一部分总结一下异常处理流程,第二部分完成驱动程序和测试程序的编写。
一:异常处理流程
内核在start_kernel中调用trap_init(),init_IRQ两个函数来设置异常的处理函数,
/*
* 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);
arm架构CPU的异常向量基址是0xffff0000,vectors是一个配置项,实际上就是0xffff0000,所以就相当于把__vectors_end - __vectors_start的变量内容拷贝到0xffff0000地址上。
再来搜索一下__vectors_start,在文件arch\arm\kernel\entry-armv.s中,内容如下:
__vectors_start:
ARM( swi SYS_ERROR0 )
THUMB( svc #0 )
THUMB( nop )
W(b) vector_und + stubs_offset
W(ldr) pc, .LCvswi + stubs_offset
W(b) vector_pabt + stubs_offset
W(b) vector_dabt + stubs_offset
W(b) vector_addrexcptn + stubs_offset
W(b) vector_irq + stubs_offset
W(b) vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
我们关心的是发生中断之后的跳转,W(b) vector_irq + stubs_offset
直接搜vector_irq搜不到,这是一个宏定义,
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
.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
将这个宏按照下面的定义展开:
/*
* Vector stubs.
*
* This code is copied to 0xffff0200 so we can use branches in the
* vectors, rather than ldr's. Note that this code must not
* exceed 0x300 bytes.
*
* Common stub entry macro:
* Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*
* SP points to a minimal amount of processor-private memory, the address
* of which is copied into r0 for the mode specific abort handler.
*/
.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 | PSR_ISETSTATE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
THUMB( adr r0, 1f )
THUMB( ldr lr, [r0, lr, lsl #2] )
mov r0, sp
ARM( ldr lr, [pc, lr, lsl #2] )
movs pc, lr @ branch to handler in SVC mode
ENDPROC(vector_\name)
.align 2
@ handler addresses follow this label
1:
.endm
展开为:
vector_irq:
sub lr,lr,#4 //计算返回地址
...//转换到管理模式
...//继续跳转
.long __irq_usr @ 0 (USR_26 / USR_32)//发生用户态的中断
.long __irq_svc @ 3 (SVC_26 / SVC_32)
搜索__irq_usr,
__irq_usr:
usr_entry
kuser_cmpxchg_check
irq_handler
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq
usr_entry宏展开如下:
.macro usr_entry
UNWIND(.fnstart )
UNWIND(.cantunwind ) @ don't unwind the user space
sub sp, sp, #S_FRAME_SIZE
ARM( stmib sp, {r1 - r12} )
THUMB( stmia sp, {r0 - r12} )
ldmia r0, {r3 - r5}
add r0, sp, #S_PC @ here for interlock avoidance
mov r6, #-1 @ "" "" "" ""
str r3, [sp] @ save the "real" r0 copied
@ from the exception stack
irq_handler宏展开如下:
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
adr lr, BSYM(9997f)
ldr pc, [r1]
#else
arch_irq_handler_default
接下来就会跳转到c语言入口去执行相应的处理函数。
二、驱动程序和测试程序的编写
驱动程序:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/irqreturn.h>
#include <mach/irqs.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <mach/gpio-davinci.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/sched.h>
static struct class *thirddrv_class;
static struct device *thirddrv_class_dev;
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);//这是静态定义的方法。该宏会定义一个wait_queue_head,并且初始化结构中的锁以及等待队列
static volatile int ev_press=0;//中断事件标志,中断服务程序将它置1,third_drv_read将它清0
struct pin_desc{
unsigned int pin;
unsigned char key_val;
};//定义一个引脚描述的结构体
static unsigned char key_val;
//按下时键值为0x01,0x02,松开时键值为0x81,0x82
struct pin_desc pins_desc[2]={
{GPIO_TO_PIN(6, 1),0x01},
{GPIO_TO_PIN(0, 6),0x02},
};
static irqreturn_t buttons_irq(int irq,void *dev_id)//中断处理函数
{
struct pin_desc *pindesc=(struct pin_desc*)dev_id;
unsigned int pinval;
pinval=gpio_get_value(pindesc->pin);//可以通过该函数直接读取寄存器的值
if(pinval)//如果引脚值等于1,表示是松开的
{
key_val=0x80|pindesc->key_val;
}
else //等于0的话,是按下的
{
key_val=pindesc->key_val;
}
ev_press=1;//表示中断发生
wake_up_interruptible(&button_waitq);//将button_waitq这个队列里面的进程唤醒
return IRQ_RETVAL(IRQ_HANDLED);
}
struct gpio_irq_desc {
int irq;
unsigned long flags;
char *name;
};
struct gpio_irq_desc press_dev_desc0 = {
IRQ_DA8XX_GPIO6,
IRQ_TYPE_EDGE_BOTH,
"sw9_push_button"
};
struct gpio_irq_desc press_dev_desc1 = {
IRQ_DA8XX_GPIO0,
IRQ_TYPE_EDGE_BOTH,
"sw8_push_button"
};
static int third_drv_open(struct inode *inode,struct file *file)
{
press_dev_desc0.irq=gpio_to_irq(GPIO_TO_PIN(6, 1));
//由GPIO号得到中断号的函数
request_irq(press_dev_desc0.irq,buttons_irq,press_dev_desc0.flags,press_dev_desc0.name,&pins_desc[0]);
//最后一个参数是dev_id类型的,用于在free_irq的时候和IRQ号联合来确定卸载哪个结构,同时buttons_irq这个中断处理函数有两个参数,一个是irq号,一个是dev_id。所以会将dev_id这个参数传入buttons_irq.
press_dev_desc1.irq=gpio_to_irq(GPIO_TO_PIN(0, 6));
request_irq(press_dev_desc1.irq,buttons_irq,press_dev_desc1.flags,press_dev_desc1.name,&pins_desc[1]);
return 0;
}
static int 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 0;
}
int third_drv_close(struct inode *inode,struct file *file)
{
free_irq(press_dev_desc0.irq,&pins_desc[0]);
free_irq(press_dev_desc1.irq,&pins_desc[1]);
return 0;
}
static struct file_operations third_drv_fops={
.owner = THIS_MODULE,
.open =third_drv_open,
.read = third_drv_read,
.release=third_drv_close,
};
int major=0;
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=device_create(thirddrv_class,NULL,MKDEV(major, 0),NULL,"buttons");
return 0;
}
static int third_drv_exit(void)
{
unregister_chrdev(major,"third_drv");
device_destroy(thirddrv_class,MKDEV(major, 0));
class_destroy(thirddrv_class);
return 0;
}
module_init(third_drv_init);
module_exit(third_drv_exit);
MODULE_LICENSE("GPL");
测试程序:
#include <fcntl.h>
#include <stdio.h>
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);
}
return 0;
}