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;
}
发布了83 篇原创文章 · 获赞 127 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_40788950/article/details/88763467
今日推荐