學習了韋東山一期視頻,關於按鍵驅動部分,他用了查詢的方式,中斷的方式,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;
}