cpu的模式:
模式切换:硬件自动完成
arm在irq/svc/abort几种模式下sp是不能共用的
user
用户模式
fiq
irq
svc
abt
und
sys
mon
31 30 29 28 27 26 25 24 23 20 19 16 15 10 9 8 7 6 5 4 0
N | Z| C |V |Q| IT J reser GE IT E A I F T M
M[4:0]
10000 user
10001 fiq
10010 irq
10011 svc
10111 abt
11011 und
11111 sys
10110 mon
此时lr中保存的实际上是异常的返回地址,异常发生,切换到svc模式后,会将lr保存到svc模式栈中
- (pt_reg->pc),最后从异常返回时再将pt_reg->pc加载如arm寄存器pc中,实现异常返回。本函数只是
- 其中一个步骤,即为将异常发生时刻lr保存到svc模式栈中(pt_reg->pc)做准备。
*5. spsr是异常发生那一刻(即进入异常模式前是什么模式)的cpsr状态,如内核态下发生中断,则spsr是 - svc模式下10011,如用户态下发生中断,则spsr是user模式10000。
*6. 此时cpu正处于异常状态(如中断),此时cpsr为10010。
*7. 要进行真正的异常处理,需要退出异常模式进入svc模式。
工作:
1)cpu状态的切换
cpu -> irq -> svc
2)在不同模式保存数据到栈中
sp — irq 报错lr 和 cpsr 信息
sp — svc
3)调用 handle_arch_irq 函数
.macro vector_stub, name, mode, correction=0
.align 5 //强制对齐32字节对齐
vector_\name:
.if \correction
/*
*需要调整返回值,对应irq异常将lr减去4,因为异常发生时,arm将pc地址+4赋值给了lr。
*/
sub lr, lr, #\correction
.endif
@save r0,lr;将r0和lr保存到异常模式的栈上[sp] = r0;[sp+4] = lr_irq;
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr //得到异常发生时所处模式得信息
/*
*将spsr保存到异常模式的栈上[sp+8]=spsr_irq=lr
*/
str lr, [sp, #8] @ save spsr
/*
*1. dabt处理时,r0=r0^(0x17^0x13)=r0^0x4,bit3取反之后10011变为svc模式;
*2. irq处理时:r0=10010=r0^(0x12^0x13)=r0^0x1=10011变为svc模式
*/
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0
/*
*r0=sp;
*注意:
*1. 此时r0中保存了异常状态下sp栈地址,这个栈上保存了r0,lr(异常返回地址),spsr(异常发生时,cpu
* 的状态,当然异常返回时需要恢复该状态)
*2. 之后的函数会把r0中保存的异常模式的sp上信息,加载到svc模式下的sp栈上。异常处理返回时再将svc
* mode 的栈加载到arm寄存器上。
*/
mov r0, sp
//应该再切换到svc 之前把sp保存到r0中 ***************
/*
*lr中保存发生异常时arm的cpsr状态到spsr
*1. usr模式发生异常则lr=10000&0x0f;lr=pc+lr<<2 pc+0时执行 __irq_usr;
*2. svc模式发生异常则lr=10011&0x0f;lr=pc+lr<<2 pc+12时执行 __irq_svc
*/
ARM( ldr lr, [pc, lr, lsl #2] )
/* movs中s表示把spsr恢复给cpsr,上面可知spsr保存的是svc模式,不过此时中断还是关闭的
* 异常处理一定要进入svc模式原因:
*(1)异常处理一定要PL1特权级。
*(2)使能嵌套中断。
* 如果一个中断模式(例如用户态发生中断,arm从usr进入irq模式)中重新允许中断,
* 且这个中断模式中使用了bl指令,bl会把pc放到lr_irq中,这个地址会被当前模式下产生的中断破坏
* 这种情况下中断无法返回。所以为了避免这种情况,中断处理过程应该切换到svc模式,bl指令可以把
* pc(即子程序返回地址)保存到lr_svc.
*/
movs pc, lr @ branch to handler in SVC mode
ENDPROC(vector_\name)
.long __irq_usr @ 0 (USR_26 / USR_32) 从用户态下进入的irq,执行__irq_usr代码
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32) 从内核态下进入的irq,执行__irq_svc代码
.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
//此时arm处于svc模式执行下面代码
__irq_usr:
usr_entry
kuser_cmpxchg_check
irq_handler //irq处理函数
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq //中断处理完成返回
UNWIND(.fnend )
ENDPROC(__irq_usr)/*
.macro usr_entry, trace=1, uaccess=1
UNWIND(.fnstart )
UNWIND(.cantunwind ) @ don’t unwind the user space
/*
*arch/arm/kernel/asm-offsets.c中定义DEFINE(S_FRAME_SIZE,sizeof(struct pt_regs))
*S_FRAME_SIZE=72
*/
sub sp, sp, #S_FRAME_SIZE
/*
*注释:
*宏CONFIG_MULTI_IRQ_HANDLER在.config中有定义,会将handle_arch_irq里的值赋值给pc去执行。
*给handle_arch_irq请看后面的C语言阶段分析
/
/
- Interrupt handling.
*/
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
badr lr, 9997f
ldr pc, [r1] //进入C语言阶段的中断处理
#else
arch_irq_handler_default
#endif
9997:
.endm
======================================中断向量表和中断处理部分代码的搬移
early_trap_init函数的调用流程为:
start_kernel(init/main.c)—>
setup_arch(arch/arm/kernel/setup.c)—>
paging_init(arch/arm/mm/mmu.c)—>
devicemaps_init(arch/arm/mm/mmu.c)—>
early_trap_init(arch/arm/kernel/traps.c)
static void __init devicemaps_init(const struct machine_desc *mdesc)
{
struct map_desc map;
unsigned long addr;
void vectors;
/
* Allocate the vector page early.
*分配两个页的内存空间,arm中每个页的大小为4K,这两个页的内存空间,一个是为保存中断向量
*表,一个是为了保存中断的处理部分代码,这两部分代码的排布可以在
*(arch/arm/kernel/vmlinux.lds和arch/arm/kernel/entry-armv.S)中可以具体分析出来
*/
vectors = early_alloc(PAGE_SIZE * 2);
early_trap_init(vectors);
/*
*创建一个页的内存地址映射,虚拟地址为0xffff0000,此地址为中断向量表的高端地址
*设置中断向量表的高端地址在汇编的v7_setup中,使用的v7_crval设置了cp15的c1寄存器
*v7_crval定义在arch/arm/mm/proc-v7-2level.S。
*/
map.pfn = __phys_to_pfn(virt_to_phys(vectors));
map.virtual = 0xffff0000;
map.length = PAGE_SIZE;
#ifdef CONFIG_KUSER_HELPERS //此宏有定义
map.type = MT_HIGH_VECTORS;
#else
map.type = MT_LOW_VECTORS;
#endif
create_mapping(&map);
/*
*判断中断向量表的位置是否设置在高端地址,如果中断向量表没有设置在高端地址,
*在映射低端中断向量表地址。
*/
if (!vectors_high()) {
map.virtual = 0;
map.length = PAGE_SIZE * 2;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
}
/* Now create a kernel read-only mapping */
map.pfn += 1;
map.virtual = 0xffff0000 + PAGE_SIZE;
map.length = PAGE_SIZE;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
.align 2
.type v7_crval, #object
v7_crval:
crval clear=0x2120c302, mmuset=0x10c03c7d, ucset=0x00c01c7c
/*
* 将申请的4K先设置为未定义指令,防止在发生其他中断时,没有处理导致cpu错误
*/
for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
((u32 *)vectors_base)[i] = 0xe7fddef1;/*
/*
*将中断向量表和中断处理的代码搬移到申请的两页地址空间内
*/
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);
kuser_init(vectors_base);
flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
void __init early_trap_init(void *vectors_base)
{
unsigned long vectors = (unsigned long)vectors_base;
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
unsigned i;
/*
* 将申请的4K先设置为未定义指令,防止在发生其他中断时,没有处理导致cpu错误
*/
for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
((u32 *)vectors_base)[i] = 0xe7fddef1;
/*
*将中断向量表和中断处理的代码搬移到申请的两页地址空间内
*/
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start);
kuser_init(vectors_base);
flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
//init/main.c
asmlinkage void __init start_kernel(void)
{
……
trap_init(); //空函数
……
early_irq_init();
init_IRQ();
……
}
//arch/arm/kernel/irq/irqdesc.c/
int __init early_irq_init(void)
{
int i, initcnt, node = first_online_node;
struct irq_desc *desc;
init_irq_default_affinity(); //由CONFIG_SMP宏决定是否为空函数,见下面分析一
/* Let arch update nr_irqs and return the nr of preallocated irqs */
initcnt = arch_probe_nr_irqs();//体系结构相关的代码来决定预先分配的中断描述符的个数见分析二
if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS)) //见下面分析三
nr_irqs = IRQ_BITMAP_BITS;
if (WARN_ON(initcnt > IRQ_BITMAP_BITS))
initcnt = IRQ_BITMAP_BITS;
if (initcnt > nr_irqs)
nr_irqs = initcnt;
//事先构造好 irq_desc 结构体
for (i = 0; i < initcnt; i++) {
desc = alloc_desc(i, node, NULL); //分配 struct irq_desc结构,分析四
//用于标记那些irq 申请占用 allocated_irqs 关键变量
set_bit(i, allocated_irqs); //设置相应的位数组,分析三
irq_insert_desc(i, desc); //保存irq_desc指针,分析四
}
return arch_early_irq_init(); //kernel/softirq.c中定义,为空函数
}
//arch/arm/kernel/irq.c
#ifdef CONFIG_SPARSE_IRQ
int __init arch_probe_nr_irqs(void)
{
nr_irqs = machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS;//NR_IRQS 16
return nr_irqs; //分析下面的分析,得知返回的是NR_IRQS
}
#endif
//kernel/irq/internals.h
//include/linux/types.h
#define DECLARE_BITMAP(name,bits)
unsigned long name[BITS_TO_LONGS(bits)]
//arch/arm/include/asm/bitops.h
#define set_bit(nr,p) ATOMIC_BITOP(set_bit,nr,p) //_set_bit
#define clear_bit(nr,p) ATOMIC_BITOP(clear_bit,nr,p)
#define change_bit(nr,p) ATOMIC_BITOP(change_bit,nr,p)
#define test_and_set_bit(nr,p) ATOMIC_BITOP(test_and_set_bit,nr,p)
#define test_and_clear_bit(nr,p) ATOMIC_BITOP(test_and_clear_bit,nr,p)
#define test_and_change_bit(nr,p) ATOMIC_BITOP(test_and_change_bit,nr,p)
/*
- __builtin_constant_p 是编译器gcc内置函数,用于判断一个值是否为编译时常量,
- 如果是常数,函数返回1 ,否则返回0。此内置函数的典型用法是在宏中用于手动编译时优化
/
#ifndef CONFIG_SMP //此宏在.config中未定义
/ - The __* form of bitops are non-atomic and may be reordered.
*/
#define ATOMIC_BITOP(name,nr,p)
(__builtin_constant_p(nr) ? ___atomic##name(nr, p) : _##name(nr,p))
#else
#define ATOMIC_BITOP(name,nr,p) _##name(nr,p)
#endif
//arch/arm/lib/setbit.S
#include <linux/linkage.h>
#include <asm/assembler.h>
#include “bitops.h”
.text
//bitop 为宏
bitop _set_bit, orr
//arch/arm/lib/bitops.h
.macro bitop, name, instr
ENTRY( \name )
UNWIND( .fnstart )
ands ip, r1, #3
strneb r1, [ip] @ assert word-aligned
mov r2, #1
and r3, r0, #31 @ Get bit offset
mov r0, r0, lsr #5
add r1, r1, r0, lsl #2 @ Get word offset
#if LINUX_ARM_ARCH >= 7 && defined(CONFIG_SMP)
.arch_extension mp
ALT_SMP(W(pldw) [r1])
ALT_UP(W(nop))
#endif
mov r3, r2, lsl r3
1: ldrex r2, [r1]
\instr r2, r2, r3
strex r0, r2, [r1]
cmp r0, #0
bne 1b
bx lr
UNWIND( .fnend )
ENDPROC(\name )
.endm
//driver/irqchip/irq-vic.c
/*
*通过IRQCHIP_DECLARE定义一个位于__irqchip_of_table段的全局量struct of_device_id
*__of_table_arm_pl192_vic,linux在中断初始化阶段在设备树中寻找是否有对应的节点,找到
*相对应的节点后,随即执行vic_of_init
*/
IRQCHIP_DECLARE(arm_pl192_vic, “arm,pl192-vic”, vic_of_init);