字符设备驱动--中断方式下的按键驱动
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% 中断的优势
相关文章推荐
- 字符设备驱动笔记——中断方式按键驱动之linux中断处理结构(五)
- 字符设备驱动程序之中断方式的按键驱动
- 第12课第4.1节 字符设备驱动程序之中断方式的按键驱动_Linux异常处理结构
- 字符设备驱动笔记——中断方式按键驱动之linux异常处理结构(四)
- 字符设备驱动笔记——中断方式按键驱动之代码(六)
- 第12课第4.3节 字符设备驱动程序之中断方式的按键驱动_编写代码
- 韦东山驱动视频笔记——2.字符设备驱动程序之中断方式的按键驱动程序
- 字符设备驱动-中断方式操控按键
- 字符设备驱动程序按键驱动---中断方式
- 第12课第4.2节 字符设备驱动程序之中断方式的按键驱动_Linux中断处理结构
- 字符设备驱动学习笔记----查询方式取得按键值
- 字符设备驱动程序开发之基于中断的按键驱动加去抖动
- arm 驱动基础:字符设备之异步通信:按键中断,通知应用程序
- 字符设备驱动之按键中断——FS2410
- 韦东山驱动视频笔记——1.字符设备驱动程序之查询方式的按键驱动程序
- 字符设备驱动学习笔记-----中断方式取得按键值
- 嵌入式linux:字符设备驱动-----按键驱动(中断+poll机制)
- 字符设备驱动之中断按键驱动
- 字符设备驱动-----按键驱动(中断+poll机制)
- 字符设备驱动-----按键驱动(中断+poll机制)