- 论坛徽章:
- 0
|
一、arm中断的相关硬件知识:
正常的程序执行流程发生暂时的停止时,称之为异常。例如处理一个外部的中断请求。在处理异常之前,当前处理器的状态必须保留,这样当异常处理完成之后,当前程序可以继续执行。处理器允许多个异常同时发生,它们将会按固定的优先级进行处理。ARM体系结构中的异常,与8位/16位体系结构的中断有很大的相似之处,但异常与中断的概念并不完全等同。
ARM的异常有七种
复位异常、SWI异常、未定义指令异常、数据中止和指令中止异常。外部中断分为FIQ和IRQ两种,分别为快速中断和通用中断。都可以通过CPSR中的相应位来屏蔽。
CPU知道一个source触发了中断,怎么调用执行一些函数(汇编,或者c语言),就是靠异常向量表(事实上,exception vector table 也是由汇编组成的)
![]()
Arm的7种异常对应的模式
地 址
异 常
进入模式
5个异常模式
优先级
6最低
0x0000,0000
复位
管理模式
1
0x0000,0004
未定义指令
未定义模式
6
0x0000,0008
软件中断
管理模式
6
0x0000,000C
中止(预取指令)
中止模式
5
0x0000,0010
中止(数据)
中止模式
2
0x0000,0014
保留
保留
未使用
0x0000,0018
IRQ
IRQ
4
0x0000,001C
FIQ
FIQ
3
arm异常表,对应模式及向量表偏移
(摘自arm体系结构与编程一书)
当一个异常/中断出现后, 4020系统中ARM处理器对其的响应过程如下:
(1) 保存处理器当前状态、中断屏蔽位以及各条件标志位。将当前程序状态寄存器CPSR的内容保存到将要执行的异常中断对应的SPSR寄存器中。
(2) 设置当前程序状态寄存器CPSR中相应的位。包括设置CPSR中的位,使处理器进入相应的执行模式;设置CPSR中的位,禁止IRQ中断,当进入FIQ模式时,禁止FIQ中断。
(3) 将寄存器lr_mode设置成返回地址。
(4) 将程序计数器值(PC),设置成该异常中断的中断向量地址,从而跳转到相应的异常中断处理程序执行。即处理器跳转到异常向量表中相应的入口(对于IRQ , 显然pc=0x18) 。
所以当触发IRQ后,CPU会最后跳入0x18 这个入口,定制kernel时只需在这个入口填入自己的指令(当然是汇编语句) ,即可调用中断处理函数,可能这样:
而对于4020 linux系统中断向量的含义是:
触发IRQ—CPU jump 到0x18,同时要把irqno传入相应的寄存器调用一个中断通用处理函数如:asm_do_IRQ(unsigned int irqno)
asm_do_IRQ() 这个函数根据irqno 就可以找到对应的中断描述符,然后调用中断描述符里面的handler()了。
二、linux中中断向量表的初始化
ARM linux内核启动时,通过start_kernel()->trap_init()的调用关系,初始化内核的中断异常向量表。
linux/init/main.c Start_kernel中的中断向量表初始化
asmlinkage void
__init start_kernel(void)
{
.....
trap_init();
init_IRQ();
....
}
中断的初始化主要和这两个函数相关
(1)trap_init函数
这个trap_init函数主要起到搬运中断向量到高地址的作用
void __init
trap_init(void)
{
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
*)0xffff0000, __vectors_start, __vectors_end - __vectors_start);
memcpy((void
*)0xffff0200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void
*)0xffff1000 - kuser_sz, __kuser_helper_start, kuser_sz);
/*
*
Copy signal return handlers into the vector page, and
*
set sigreturn to be a pointer to these.
*/
memcpy((void *)KERN_SIGRETURN_CODE,
sigreturn_codes,
sizeof(sigreturn_codes));
flush_icache_range(0xffff0000, 0xffff0000
+ PAGE_SIZE);
modify_domain(DOMAIN_USER,
DOMAIN_CLIENT);
}
(2)init_IRQ函数
void __init
init_IRQ(void)
{
struct irqdesc *desc;
int irq;
#ifdef
CONFIG_SMP
bad_irq_desc.affinity = CPU_MASK_ALL;
bad_irq_desc.cpu = smp_processor_id();
#endif
// irq_desc数组是用来描述IRQ的请求队列,每一个中断号分配一个
//irq_desc结构,组成了一个数组。
for (irq = 0, desc = irq_desc; irq
*desc =
bad_irq_desc; /* 把每个中断先初始化为bad_irq*/
INIT_LIST_HEAD(&desc->pend);
}
init_arch_irq(); /* 调用4020自己sep4020_init_irq函数来初始化各个中断句柄为do_level_IRQ*/
}
其中关键函数为:init_arch_irq();
在/arch/arm/setup.c函数中有定义:init_arch_irq = mdesc->init_irq;
而mdesc->init_irq就是在arch/arm/mach-sep4020/irq.c中定义的
void __init sep4020_init_irq(void)
{
unsigned int i;
unsigned
long flags;
local_save_flags(flags);
*(RP)(INTC_IER_V)
= 0XFFFFFFFF;使能所有的4020中断
*(RP)(INTC_IMR_V)
= 0XFFFFFFFF;同时屏蔽所有的中断
*(RP)(INTC_IPLR_V)
= 0X0;
local_irq_restore(flags);
for(i = 0; i
{
set_irq_handler(i, do_level_IRQ);
/*设置4020各个中断的初始处理句柄*/
set_irq_chip(i, &sep4020_chip);/*设置中断屏蔽和应答的处理句柄*/
set_irq_flags(i, IRQF_VALID |
IRQF_PROBE);/*设置初始化标志*/
}
}
static struct irqchip sep4020_chip=
{
.ack = sep4020int_ack,
.mask = sep4020int_mask,
.unmask = sep4020int_unmask,
};
其实系统中是有个全局变量来保持中断结构指针数组,它是定义在arch/arm/kernel/irq.c
struct irqdesc
irq_desc[NR_IRQS]; /*正真的中断处理函数指针数组*/
而表示中断类型的结构体是:
include/linux/irq.h
/**
* struct irq_desc - interrupt
descriptor
*
* @handle_irq: highlevel irq-events handler [if
NULL, __do_IRQ()]
* @chip: low level interrupt hardware access
* @msi_desc: MSI descriptor
* @handler_data: per-IRQ data for the irq_chip methods
* @chip_data: platform-specific per-chip private
data for the chip
* methods, to allow shared chip
implementations
* @action: the irq action chain
* @status: status information
* @depth: disable-depth, for nested
irq_disable() calls
* @wake_depth: enable depth, for multiple
set_irq_wake() callers
* @irq_count: stats field to detect stalled irqs
* @irqs_unhandled: stats field for spurious unhandled
interrupts
* @last_unhandled: aging timer for unhandled count
* @lock: locking for SMP
* @affinity: IRQ affinity on SMP
* @cpu: cpu index useful for balancing
* @pending_mask: pending rebalanced interrupts
* @dir:
/proc/irq/ procfs entry
* @affinity_entry: /proc/irq/smp_affinity procfs entry on
SMP
* @name: flow handler name for
/proc/interrupts output
*/
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;
unsigned long last_unhandled; /* Aging timer for unhandled count */
spinlock_t lock;
#if
defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
cpumask_t pending_mask;
#endif
#ifdef
CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name;
}
____cacheline_internodealigned_in_smp;
三、Linux异常处理流程:
1、
硬件发生中断后,怎样找到中断入口函数
我们看下中断向量表它是定义在arch/arm/kernel/entry-armv.S中:
.equ stubs_offset,
__vectors_start + 0x200 - __stubs_start
@stubs_offset表示中断处理函数与中断向量表的偏移
.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:
从entry-armv.S中的中断向量表定义可以看到,发生中断时系统会跳到 vector_irq
+ stubs_offset处运行,这个位置实际上就是中断入口函数。
vector_irq已经是中断的入口函数了,为什么又要加上 stubs_offset?是因为b指令实际上是相对当前PC的跳转,也就是说当汇编器看到B指令后会把要跳转的标签转化为相对于当前PC的偏移量写入指令码。从上面的代码可以看到中断向量表和stubs都发生了代码搬移,所以如果中断向量表中仍然写成b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq处,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写成搬移后的。
我们把搬移前的中断向量表中的irq入口地址记irq_PC,它在中断向量表的偏移量就是irq_PC-vectors_start,
vector_irq在stubs中的偏移量是vector_irq-stubs_start,这两个偏移量在搬移前后是不变的。搬移后 vectors_start在0xffff0000处,而stubs_start在0xffff0200处,所以搬移后的vector_irq相对于中断向量中的中断入口地址的偏移量就是:200+vector_irq在stubs中的偏移量再减去中断入口在向量表中的偏移量,即200+ vector_irq-stubs_start-irq_PC +vectors_start =
(vector_irq-irq_PC) + vectors_start+200-stubs_start,对于括号内的值实际上就是中断向量表中写的vector_irq,减去irq_PC是由汇编器完成的,而后面的
vectors_start+200-stubs_start就应该是stubs_offset,实际上在entry-armv.S中也是这样定义的 。
2、根据原来发生中断时CPU所处的工作模式找到相应的入口函数
arch/arm/kernel/entry-armv.S—vector_irq是通过宏来vector_stub定义的
.macro vector_stub, name, mode, correction=0 @这个宏展开就是各个异常模式的处理函数
.align 5
vector_\name:
.if \correction
sub lr,
lr, #\correction
.endif
@
@ Save r0, lr_ (parent
PC) and spsr_
@ (parent CPSR)保存前一个状态的lr,spsr,r0因为马要将返回模式改为SVC,防止中断嵌套
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr,
[sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@ 马上就要进入SVC模式下,准备切换模式,但此时的IRQ还是被禁止的
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
@
@ the branch table must immediately
follow this code
@ 分支表必须紧靠下面的代码后面,这是由于表的地址是根据pc计算出来的,这个分支表是为了分别处理各种模式下进入异常的情况
and lr,
lr, #0x0f /*lr作为一个分支表的索引值*/
mov r0,
sp /*r0保存中断前状态的堆栈指针*/
ldr lr,
[pc, lr, lsl #2]
movs pc,
lr @ 转去执行lr处的分支指令,同时由于带有“S”所以模式会切换到SVC模式下执行指令
.endm
.globl __stubs_start
__stubs_start:
/*
* 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
......
从上面这段代码可以看出,vector_irq把发生中断时的r0,PC-4以及CPSR压栈(注意,压入的的irq模式的堆栈),把中断栈的栈指针赋给 r0,最后根据原来发生中断时CPU所处的工作模式(CPSR的低4位)找到相应的入口函数,在进入svc模式后进一步处理中断。
2、
用户进程被中断的处理情况:
(1)__irq_usr完成的工作
.align 5
__irq_usr:
usr_entry /*在当前SVC模式的堆栈上分配一个pt_regs(72个字节)结构,保存被中断模式的寄存器现场*/
get_thread_info tsk
#ifdef
CONFIG_PREEMPT @在我们4020中没有开进程抢占
ldr r8,
[tsk, #TI_PREEMPT] @ get
preempt count
add r7,
r8, #1 @ increment it
str r7,
[tsk, #TI_PREEMPT]
#endif
irq_handler /*查询sep4020的中断寄存器获得中断号,和获得pt_regs,然后调用asm_do_IRQ*/
mov why,
#0
b ret_to_user
.ltorg
它主要通过调用宏usr_entry进一步保存现场,然后调用irq_handler进行中断处理,保存的用户模式pt_regs的栈现场结构如下:
-1
CPSR
PC-4
LR
SP
R12
...
R2
R1
R0
其中的LR,SP是USR模式下的,R0,CPSR,PC-4是从中断栈中拷贝的
(2)usr_entry宏调用
.macro usr_entry
sub sp,
sp, #S_FRAME_SIZE /*sizeof(pt_regs),分配一个pt_regs结构*/
stmib sp,
{r1 - r12} /*保存r1-r12*/
ldmia r0,
{r1 - r3} /*r0保存的是被中断模式的堆栈指针*/
add r0,
sp, #S_PC @ S_SP为sp寄存器在pt_regs中的偏移
mov r4,
#-1 @ ""
"" "" ""
str r1,
[sp] @ r0为中断前的堆栈指针,将成为pt_regs中的sp的值
#if
__LINUX_ARM_ARCH__
#ifndef
CONFIG_MMU
#warning
"NPTL on non MMU needs fixing"
#else
@ make sure our user space atomic helper
is aborted
cmp r2, #TASK_SIZE
bichs r3, r3, #PSR_Z_BIT
#endif
#endif
@
@ We are now ready to fill in the
remaining blanks on the stack:
@
@
r2 - lr_, already fixed up for correct return/restart
@
r3 - spsr_
@
r4 - orig_r0 (see pt_regs definition in ptrace.h)
@
@ Also, separately save sp_usr and lr_usr
@
stmia r0, {r2 - r4} 分别将lr,spsr,orig_r0压入堆栈
stmdb r0, {sp, lr}^
@
@ Enable the alignment trap while in
kernel mode
@
alignment_trap r0
@
@ Clear FP to mark the first stack frame
@
zero_fp
.endm
(2)irq_handler的作用
它首先通过宏 get_irqnr_and_base获得中断号,存入r0,然后把上面建立的pt_regs结构的指针,也就是sp值赋给r1,把调用宏 get_irqnr_and_base的位置作为返回地址(为了处理下一个中断),然后调用 asm_do_IRQ进一步处理中断,以上这些操作都在建立在获得中断号的前提下,也就是有中断发生,
即当某个外部硬件触发中断的时候,kernel最终会调用到:asm_do_IRQ
linux/arch/arm/kernel/entry-armv.S
/*
*
Interrupt handling. 中断处理函数入口
Preserves r7, r8, r9
*/
.macro irq_handler
1: get_irqnr_and_base
r0, r6, r5, lr /*获得4020的中断号*/
movne r1, sp //将sp的值赋给r1,即pt_regs结构的指针
@
@ routine called with r0 = irq number, r1
= struct pt_regs *
@
adrne lr, 1b
/*返回到1处,asm_do_IRQ返回后将再次查询发生的中断,这样可以处理优先级比较低的中断*/
bne asm_do_IRQ /*调用c语言的中断处理函数asm_do_IRQ*/
#ifdef
CONFIG_SMP
/*
*
XXX
*
*
this macro assumes that irqstat (r6) and base (r5) are
*
preserved from get_irqnr_and_base above
*/
test_for_ipi
r0, r6, r5, lr
movne r0, sp
adrne lr, 1b
bne do_IPI
#ifdef CONFIG_LOCAL_TIMERS
test_for_ltirq
r0, r6, r5, lr
movne r0, sp
adrne lr, 1b
bne do_local_timer
#endif
#endif
.endm
(3)get_irqnr_and_base获得4020的中断号的宏
下面这个宏查询ISPR(IRQ待定中断服务寄存器,当有需要处理的中断时,这个寄存器的相应位会置位,任意时刻,最多一个位会置位),计算出的中断号放在irqnr指定的寄存器中;这个宏在不同的ARM芯片上是不一样的,下面是sep4020的定义
.macro
get_irqnr_and_base, irqnr, irqstat, base, tmp
/*获得4020的中断号 */
//gfd
ldr \base, =INTC_IFSR_V /*宏参数引用时要用"\"符号 */
ldr \base, [\base]
mov \irqnr,#0
1001:
cmp \base, #0 @
no flags set?
beq 1002f @ keep "eq" status
when finishing!
tst \base, #1 @
lsb set?测试最低位是否为1,这样就可以知道是哪个中断
addeq \irqnr, \irqnr, #1 @ if not, incr IRQ#
moveq \base, \base, LSR #1 @ r = r >> 1
beq 1001b
1002:
.endm
.macro
irq_prio_table
.endm
(4) asm_do_IRQ的c语言中断处理函数:
Arch/arm/kernel/irq.c
asmlinkage void
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct irqdesc *desc = irq_desc
+ irq; //从中断数组中获得相应中断类型结构指针
/*
*
Some hardware gives randomly wrong interrupts.
Rather
*
than crashing, do something sensible.
*/
if (irq >= NR_IRQS)
desc = &bad_irq_desc;
irq_enter(); /*禁止被其他任务抢占*/
spin_lock(&irq_controller_lock);
desc_handle_irq(irq, desc, regs);/*调用我们设备驱动中自己注册的中断处理函数*/
/*
*
Now re-run any pending interrupts.
*/
if (!list_empty(&irq_pending))
do_pending_irqs(regs);
irq_finish(irq); //中断结束处理函数,在这里我们是空的
spin_unlock(&irq_controller_lock);
irq_exit();
}
(5)irq_enter()函数
Include/linux/hardirq.h
#define irq_enter() \
do
{ \
account_system_vtime(current); \
add_preempt_count(HARDIRQ_OFFSET); \
} while (0)
(6) desc_handle_irq函数
这个函数正真开始调用我们在各个驱动中注册的中断处理函数
Include/asm/mach/irq.h
/*
* Helpful inline function for calling irq
descriptor handlers.
*/
static inline
void desc_handle_irq(unsigned int irq, struct irqdesc *desc, struct pt_regs
*regs)
{
desc->handle(irq, desc, regs);/*调用我们设备驱动中自己注册的中断处理函数*/
}
(7)irq_exit函数
linux/kernel/softirq.c
/*
* Exit an interrupt context. Process softirqs
if needed and possible:
*/
void
irq_exit(void)
{
account_system_vtime(current);
sub_preempt_count(IRQ_EXIT_OFFSET);
if (!in_interrupt() &&
local_softirq_pending())
invoke_softirq();
preempt_enable_no_resched();
}
3、
内核进程被中断的处理情况(待续)
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/99507/showart_2130311.html |
|