1、linux系统中的中断处理
1.0裸板中中断异常是如何处理的?
以s5p6818+按键为例
1)按键中断的触发
中断源级配置
管脚功能
中断使能
中断触发方式
(如果中断源是uart/i2c 只需要配置中断使能)
中断级级设置
中断优先级设置
中断使能
ARMcore级设置
cpsr.I=0
2) 中断异常触发 硬件自动做4件事
备份CPSR
修改CPSR
保存返回地址到LR
PC=VECTOR_BASE + 0x18
#include "../../global.h" #include <linux/interrupt.h>
#include <mach/platform.h>
#include <linux/delay.h> typedef struct btn_desc
{
int irq;//中断号
char *name;//名称
}btn_desc_t; btn_desc_t buttons[]=
{
{IRQ_GPIO_A_START+, "up"},
{IRQ_GPIO_B_START+, "down"},
{IRQ_GPIO_B_START+, "left"},
{IRQ_GPIO_B_START+, "right"}, };
/*1 定义变量*/
struct tasklet_struct btn_tasklet; irqreturn_t btn_isr(int irq, void *dev)
{
btn_desc_t *pdata = dev;
printk("<1>" "enter %s: do emergent things ...\n",__func__);
printk("<1>" "%s key is pressed!\n", pdata->name); /*登记底半部*/
printk("<1>" "register bottom half\n");
tasklet_schedule(&btn_tasklet);
printk("<1>" "exit from top half\n");
return IRQ_HANDLED;
}
int mydata = 0x100; void btn_tasklet_func(unsigned long data)
{
int *pdata = (int *)data; printk("<1>" "enter %s: do no emergent things ...\n",__func__);
printk("<1>" "mydata=%#x\n", *pdata);
(*pdata)++;
}
int __init btn_drv_init(void)
{
int ret = ;
int i =;
for(; i<ARRAY_SIZE(buttons); i++)
{
ret = request_irq(buttons[i].irq, btn_isr,
IRQF_TRIGGER_FALLING,
buttons[i].name,
&(buttons[i]));
if(ret)
{
printk("<1>" "request_irq failed!\n");
while(i)
{
i--;
free_irq(buttons[i].irq, buttons+i);
}
return ret;
}
}
/*2 初始化变量*/
tasklet_init(&btn_tasklet, btn_tasklet_func,
(unsigned long)(&mydata));
return ;
}
void __exit btn_drv_exit(void)
{
int i = ;
for(i=; i<ARRAY_SIZE(buttons); i++)
{
free_irq(buttons[i].irq, buttons+i);
}
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
3) 软件处理异常
异常向量表
0x18: 跳转指令 ldr pc, =irq_handler
irq_handler:
保护现场//压栈寄存器
bl c_irq_handler
恢复现场
c_irq_handler:
判断哪个硬件中断源触发的中断
调用对应硬件处理函数hardware_isr
清空中断源、中断控制器中对应的pending
hardware_isr:
执行相应的硬件操作
1.1 linux中断异常处理
linux中断异常处理过程和裸板中是一致的
但是linux内核中将大部分的处理代码已经实现完毕了
实际驱动编程过程中只需要实现
1)中断触发方式的配置
2)hardware_isr
内核中实现的这部分代码 被称作linux中断子系统
百度搜: linux中断子系统 arm
1.3 linux系统中提供的关于中断处理的API
//中断注册
int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
irq, 中断号
s5p6818中所有中断源的编号s5p6818_irq.h//BSP
handler, 对应硬件处理函数
handler的类型为
irqreturn_t (*)(int, void *)
flags, 中断触发方式的配置 常用的取值
IRQF_TRIGGER_RISING
IRQF_TRIGGER_FALLING
IRQF_TRIGGER_HIGH
IRQF_TRIGGER_LOW
name, 名称
dev, 内核调用handler时传递给handler第二个参数值
返回值,0 注册成功
非0 注册失败
作用:和内核约定
1)irq号中断源硬件什么情况下触发中断
2)irq号中断产生了调用内核handler函数
3)当内核调用handler函数时传递的第二个参数值
//中断注销
void free_irq(unsigned int irq, void *dev)
irq, 中断号
dev, 特别注意 和注册时request_irq(irq, ...,dev)
给定的最后一个参数保持一致
否则注销失败
作用:
取消和内核的约定
实验时将内核中自带的按键驱动程序裁剪掉
cd /home/tarena/driver/kernel
make menuconfig
Device Drivers --->
Input device support --->
[*] Keyboards --->
< > SLsiAP push Keypad support
make uImage
让开发板使用新内核
cp arch/arm/boot/uImage /tftpboot
...
insmod btn_drv.ko
cat /proc/interrupts
中断号 中断产生次数 中断来源 name
134: 0 GPIO up
尝试按几次按键
cat /proc/interrupts
rmmod btn_drv
cat /proc/interrupts
4按键按下触发中断,打印不同的信息出来
up
down
left
right
注意:xxx_init,只要返回0 就意味着模块安装成功
非0 模块安装失败
16道经典嵌入式笔试题 + 高质量的c/c++笔试题
判断一个函数是否具有可重入性的标准:
就是看该函数是否使用了全局变量
如果全局变量,那么它就是不可重入的
中断处理函数的特点:
1)最最重要的特点就是执行速度要非常快(裸板/linux/vxworks ...)
2) linux系统中,中断处理函数工作于中断上下文
其中不能调用引起阻塞或者睡眠的函数,(中断因为不能参与进程调度,睡眠之后就无法得到再次调用)
3) 当执行中断处理函数时使用了独立的栈空间
该栈一般很小 通常为一个物理内存页(4KB)
在中断处理函数中不能静态分配大数组
linux系统中要求中断处理执行非常快
但是往往有些硬件中断处理起来就是耗时
针对这种情况,
linux内核人为的将整个中断过程分为两个半部:
顶半部:top half
做那些最为紧急的工作
往往就是一些特殊功能寄存器的读写操作
中断退出之前,要向内核登记底半部
底半部: bottom half
不紧急且耗时的工作放在底半部中完成
2、内核中登记底半部的方式
中断处理程序包括上半部硬件中断处理程序,下半部处理机制,包括软中断、tasklet、workqueue、中断线程化。
2.1 软中断机制
通过软中机制登记底半部得去修改内核源码
不能以模块的形式完成底半部的登记
使用起来不灵活
硬件驱动编程时很少使用
2.2 tasklet
如何使用tasklet登记半部?
核心数据结构
struct tasklet_struct
{
//记录底半部函数的地址
void (*func)(unsigned long);
//当内核调用func时传递给func的参数值
unsigned long data;
...
}
1)定义tasklet变量
struct tasklet_struct btn_tasklet;
2) 初始化tasklet变量
void tasklet_init(struct tasklet_struct *t,
void (*func)(unsigned long), unsigned long data)
3)使用tasklet变量完成底半部的登记
void tasklet_schedule(struct tasklet_struct *t)
#include "../../global.h" #include <linux/interrupt.h>
#include <mach/platform.h>
#include <linux/delay.h> typedef struct btn_desc
{
int irq;//中断号
char *name;//名称
}btn_desc_t; btn_desc_t buttons[]=
{
{IRQ_GPIO_A_START+, "up"},
{IRQ_GPIO_B_START+, "down"},
{IRQ_GPIO_B_START+, "left"},
{IRQ_GPIO_B_START+, "right"}, };
/*1 定义变量*/
struct tasklet_struct btn_tasklet; irqreturn_t btn_isr(int irq, void *dev)
{
btn_desc_t *pdata = dev;
//printk("<1>" "enter %s: do emergent things ...\n", (btn_desc_t *)dev->name); //将报错不能这样在printk中做强转???
printk("<1>" "enter %s: do emergent things ...\n",__func__);
printk("<1>" "%s key is pressed!\n", pdata->name); /*登记底半部*/
printk("<1>" "register bottom half\n");
tasklet_schedule(&btn_tasklet);
printk("<1>" "exit from top half\n");
return IRQ_HANDLED;
}
int mydata = 0x100; void btn_tasklet_func(unsigned long data)
{
int *pdata = (int *)data;
printk("<1>" "enter %s: do no emergent things ...\n",__func__);
printk("<1>" "mydata=%#x\n", *pdata);
(*pdata)++;
}
int __init btn_drv_init(void)
{
int ret = ;
int i =;
for(; i<ARRAY_SIZE(buttons); i++)
{
ret = request_irq(buttons[i].irq, btn_isr,
IRQF_TRIGGER_FALLING,
buttons[i].name,
&(buttons[i]));
if(ret)
{
printk("<1>" "request_irq failed!\n");
while(i)
{
i--;
free_irq(buttons[i].irq, buttons+i);
}
return ret;
}
}
/*2 初始化变量*/
tasklet_init(&btn_tasklet, btn_tasklet_func,
(unsigned long)(&mydata));
return ;
}
void __exit btn_drv_exit(void)
{
int i = ;
for(i=; i<ARRAY_SIZE(buttons); i++)
{
free_irq(buttons[i].irq, buttons+i);
}
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);