Omapl138實現中斷方式的按鍵驅動

學習了韋東山一期視頻,關於按鍵驅動部分,他用了查詢的方式,中斷的方式,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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章