您的位置 首页 硬件

ARM linux的中止处理进程

一、前言本文主要以ARM体系结构下的中断处理为例,讲述整个中断处理过程中的硬件行为和软件动作。具体整个处理过程分成三个步骤来描述:1、…

一、前语

本文首要以ARM体系结构下的中止处理为例,叙述整个中止处理进程中的硬件行为和软件动作。详细整个处理进程分红三个进程来描绘:

1、第二章描绘了中止处理的预备进程

2、第三章描绘了当产生中的时分,ARM硬件的行为

3、第四章描绘了ARM的中止进入进程

4、第五章描绘了ARM的中止退出进程

本文触及的代码来自3.14内核。别的,本文留意描绘ARM指令集的内容,有些source code为了简略一些,删去了THUMB相关的代码,除此之外,有些debug相关的内容也会删去。

二、中止处理的预备进程

1、中止形式的stack预备

ARM处理器有多种process mode,例如user mode(用户空间的AP所处于的形式)、supervisor mode(即SVC mode,大部分的内核态代码都处于这种mode)、IRQ mode(产生中止后,处理器会切入到该mode)等。关于linux kernel,其中止处理处理进程中,ARM 处理器大部分都是处于SVC mode。可是,实践上产生中止的时分,ARM处理器实践上是进入IRQ mode,因而在进入真实的IRQ反常处理之前会有一小段IRQ mode的操作,之后会进入SVC mode进行真实的IRQ反常处理。由于IRQ mode仅仅一个过度,因而IRQ mode的栈很小,只需12个字节,详细如下:

struct stack {
u32 irq[3];
u32 abt[3];
u32 und[3];
} ____cacheline_aligned;

static struct stack stacks[NR_CPUS];

除了irq mode,linux kernel在处理abt mode(当产生data abort exception或许prefetch abort exception的时分进入的形式)和und mode(处理器遇到一个未界说的指令的时分进入的反常形式)的时分也是选用了相同的战略。也便是经过一个简略的abt或许und mode之后,stack切换到svc mode的栈上,这个栈便是产生反常那个时刻点current thread的内核栈。anyway,在irq mode和svc mode之间总是需求一个stack保存数据,这便是中止形式的stack,体系初始化的时分,cpu_init函数中会进行中止形式stack的设定:

void notrace cpu_init(void)
{

unsigned int cpu = smp_processor_id();------获取CPU ID
struct stack *stk = &stacks[cpu];---------获取该CPU关于的irq abt和und的stack指针

……

#ifdef CONFIG_THUMB2_KERNEL
#define PLC “r”------Thumb-2下,msr指令不答应运用当即数,只能运用寄存器。
#else
#define PLC “I”
#endif

__asm__ (
“msr cpsr_c, %1\n\t”------让CPU进入IRQ mode
“add r14, %0, %2\n\t”------r14寄存器保存stk->irq
“mov sp, r14\n\t”--------设定IRQ mode的stack为stk->irq
“msr cpsr_c, %3\n\t”
“add r14, %0, %4\n\t”
“mov sp, r14\n\t”--------设定abt mode的stack为stk->abt
“msr cpsr_c, %5\n\t”
“add r14, %0, %6\n\t”
“mov sp, r14\n\t”--------设定und mode的stack为stk->und
“msr cpsr_c, %7″--------回到SVC mode
:--------------------上面是code,下面的output部分是空的
: “r” (stk),----------------------对应上面代码中的%0
PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),------对应上面代码中的%1
“I” (offsetof(struct stack, irq[0])),------------对应上面代码中的%2
PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),------以此类推,下面不赘述
“I” (offsetof(struct stack, abt[0])),
PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),
“I” (offsetof(struct stack, und[0])),
PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE)
: “r14”);--------上面是input操作数列表,r14是要clobbered register列表
}

嵌入式汇编的语法格局是:asm(code : output operand list : input operand list : clobber list);咱们对着上面的code就能够分隔各段内容了。在input operand list中,有两种约束符(constraint),”r”或许”I”,”I”表明当即数(Immediate operands),”r”表明用通用寄存器传递参数。clobber list中有一个r14,表明在汇编代码中修正了r14的值,这些信息是编译器需求的内容。

2、SVC形式的stack预备

咱们常常说进程的用户空间和内核空间,关于一个应用程序而言,能够运转在用户空间,也能够经过体系调用进入内核空间。在用户空间,运用的是用户栈,也便是咱们软件工程师编写用户空间程序的时分,保存局部变量的stack。堕入内核后,当然不能用用户栈了,这时分就需求运用到内核栈。所谓内核栈其实便是处于SVC mode时分运用的栈。

Linux kernel在创立进程(包含用户进程和内核线程)的时分都会分配一个(或许两个,和装备相关)page frame,底部是struct thread_info数据结构,顶部(高地址)便是该进程的内核栈。当进程切换的时分,整个硬件和软件的上下文都会进行切换,这儿就包含了svc mode的sp寄存器的值被切换到调度算法选定的新的进程的内核栈上来。

3、反常向量表的预备

关于ARM处理器而言,当产生反常的时分,处理器会暂停其时指令的履行,保存现场,转而去履行对应的反常向量处的指令,当处理完该反常的时分,康复现场,回到本来的那点去持续履行程序。体系悉数的反常向量(合计8个)组成了反常向量表。向量表(vector table)的代码如下:

.section .vectors, “ax”, %progbits
__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, __vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq —————————IRQ Vector
W(b) vector_fiq

关于本文而言,咱们要点重视vector_irq这个exception vector。反常向量表或许被安放在两个方位上:

(1)反常向量表坐落0x0的地址。这种设置叫做Normal vectors或许Low vectors。

(2)反常向量表坐落0xffff0000的地址。这种设置叫做high vectors

详细是low vectors仍是high vectors是由ARM的一个叫做的SCTLR寄存器的第13个bit (vector bit)操控的。关于启用MMU的ARM Linux而言,体系运用了high vectors。为什么不必low vector呢?关于linux而言,0~3G的空间是用户空间,假如运用low vector,那么反常向量表在0地址,那么则是用户空间的方位,因而linux选用high vector。当然,运用Low vector也能够,这样Low vector地点的空间则归于kernel space了(也便是说,3G~4G的空间加上Low vector所占的空间归于kernel space),不过这时分要留意一点,由于悉数的进程同享kernel space,而用户空间的程序常常会产生空指针拜访,这时分,内存保护机制应该能够捕获这种过错(大部分的MMU都能够做到,例如:制止userspace拜访kernel space的地址空间),避免vector table被拜访到。关于内核中由于程序过错导致的空指针拜访,内存保护机制也需求操控vector table被修正,因而vector table地点的空间被设置成read only的。在运用了MMU之后,详细反常向量表放在那个物理地址现已不重要了,重要的是把它映射到0xffff0000的虚拟地址就OK了,详细代码如下:

static void __init devicemaps_init(const struct machine_desc *mdesc)
{
……
vectors = early_alloc(PAGE_SIZE * 2); -----分配两个page的物理页帧

early_trap_init(vectors); -------copy向量表以及相关help function到该区域

……
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); ----------映射0xffff0000的那个page frame

if (!vectors_high()) {---假如SCTLR.V的值设定为low vectors,那么还要映射0地址开端的memory
map.virtual = 0;
map.length = PAGE_SIZE * 2;
map.type = MT_LOW_VECTORS;
create_mapping(&map);
}

map.pfn += 1;
map.virtual = 0xffff0000 + PAGE_SIZE;
map.length = PAGE_SIZE;
map.type = MT_LOW_VECTORS;
create_mapping(&map); ----------映射high vecotr开端的第二个page frame

……
}

为什么要分配两个page frame呢?这儿vectors table和kuser helper函数(内核空间供给的函数,可是用户空间运用)占用了一个page frame,别的反常处理的stub函数占用了别的一个page frame。为什么会有stub函数呢?稍后会讲到。

在early_trap_init函数中会初始化反常向量表,详细代码如下:

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;

vectors_page = vectors_base;

将整个vector table那个page frame填充成未界说的指令。开始vector table加上kuser helper函数并不能彻底的充溢这个page,有些缝隙。假如不这么处理,当极点状况下(程序过错或许HW的issue),CPU或许从这些缝隙中取指履行,然后导致不可知的结果。假如将这些缝隙填充未界说指令,那么CPU能够捕获这种反常。
for (i = 0; i < PAGE_SIZE / sizeof(u32); i++)
((u32 *)vectors_base)[i] = 0xe7fddef1;

复制vector table,复制stub function
memcpy((void *)vectors, __vectors_start, __vectors_end – __vectors_start);
memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end – __stubs_start);

kuser_init(vectors_base); ----copy kuser helper function

flush_icache_range(vectors, vectors + PAGE_SIZE * 2);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}

一旦触及代码的复制,咱们就需求关怀其编译衔接时地址(link-time address)和运转时地址(run-time address)。在kernel完结链接后,__vectors_start有了其link-time address,假如link-time address和run-time address共同,那么这段代码运转时毫无压力。可是,现在关于vector table而言,其被copy到其他的地址上(关于High vector,这是地址便是0xffff00000),也便是说,link-time address和run-time address不相同了,假如依然想要这些代码能够正确运转,那么需求这些代码是方位无关的代码。关于vector table而言,有必要要方位无关。B这个branch instruction自身便是方位无关的,它能够跳转到一个其时方位的offset。不过并非悉数的vector都是运用了branch instruction,关于软中止,其vector地址上指令是“W(ldr) pc, __vectors_start + 0x1000 ”,这条指令被编译器编译成ldr pc, [pc, #4080],这种状况下,该指令也是方位无关的,可是有个约束,offset有必要在4K的范围内,这也是为何存在stub section的原因了。

4、中止操控器的初始化

详细能够参阅GIC代码剖析。

三、ARM HW对中止事情的处理

当悉数预备好之后,一旦翻开处理器的大局中止就能够处理来自外设的各种中止事情了。

当外设(SOC内部或许外部都能够)检测到了中止事情,就会经过interrupt requestion line上的电平或许边缘(上升沿或许下降沿或许both)告诉到该外设衔接到的那个中止操控器,而中止操控器就会在多个处理器中挑选一个,并把该中止经过IRQ(或许FIQ,本文不评论FIQ的状况)分发给process。ARM处理器感知到了中止事情后,会进行下面一系列的动作:

1、修正CPSR(Current Program Status Register)寄存器中的M[4:0]。M[4:0]表明了ARM处理器其时处于的形式( processor modes)。ARM界说的mode包含:

处理器形式 缩写 对应的M[4:0]编码 Privilege level
User usr 10000 PL0
FIQ fiq 10001 PL1
IRQ irq 10010 PL1
Supervisor svc 10011 PL1
Monitor mon 10110 PL1
Abort abt 10111 PL1
Hyp hyp 11010 PL2
Undefined und 11011 PL1
System sys 11111 PL1

一旦设定了CPSR.M,ARM处理器就会将processor mode切换到IRQ mode。

2、保存产生中止那一点的CPSR值(step 1之前的状况)和PC值

ARM处理器支撑9种processor mode,每种mode看到的ARM core register(R0~R15,合计16个)都是不同的。每种mode都是从一个包含悉数的Banked ARM core register中选取。悉数Banked ARM core register包含:

Usr System Hyp Supervisor abort undefined Monitor IRQ FIQ
R0_usr
R1_usr
R2_usr
R3_usr
R4_usr
R5_usr
R6_usr
R7_usr
R8_usr R8_fiq
R9_usr R9_fiq
R10_usr R10_fiq
R11_usr R11_fiq
R12_usr R12_fiq
SP_usr SP_hyp SP_svc SP_abt SP_und SP_mon SP_irq SP_fiq
LR_usr LR_svc LR_abt LR_und LR_mon LR_irq LR_fiq
PC
CPSR
SPSR_hyp SPSR_svc SPSR_abt SPSR_und SPSR_mon SPSR_irq SPSR_fiq
ELR_hyp

在IRQ mode下,CPU看到的R0~R12寄存器、PC以及CPSR是和usr mode(userspace)或许svc mode(kernel space)是相同的。不同的是IRQ mode下,有自己的R13(SP,stack pointer)、R14(LR,link register)和SPSR(Saved Program Status Register)。

CPSR是共用的,尽管中止或许产生在usr mode(用户空间),也或许是svc mode(内核空间),不过这些信息都是体现在CPSR寄存器中。硬件会将产生中止那一刻的CPSR保存在SPSR寄存器中(由于不同的mode下有不同的SPSR寄存器,因而更精确的说应该是SPSR-irq,也便是IRQ mode中的SPSR寄存器)。

PC也是共用的,由于后续PC会被修正为irq exception vector,因而有必要保存PC值。当然,与其说保存PC值,不如说是保存回来履行的地址。关于IRQ而言,咱们希望回来地址是产生中止那一点履行指令的下一条指令。详细的回来地址保存在lr寄存器中(留意:这个lr寄存器是IRQ mode的lr寄存器,能够表明为lr_irq):

(1)关于thumb state,lr_irq = PC

(2)关于ARM state,lr_irq = PC - 4

为何要减去4?我的了解是这样的(不一定对)。由于ARM选用流水线结构,当CPU正在履行某一条指令的时分,其实取指的动作早就履行了,这时分PC值=正在履行的指令地址 + 8,如下所示:

----> 产生中止的指令

产生中止的指令+4

-PC-->产生中止的指令+8

产生中止的指令+12

一旦产生了中止,其时正在履行的指令当然要履行结束,可是现已完结取指、译码的指令则停止履行。当产生中止的指令履行结束之后,本来指向(产生中止的指令+8)的PC会持续添加4,因而产生中止后,ARM core的硬件着手处理该中止的时分,硬件现场如下图所示:

----> 产生中止的指令

产生中止的指令+4 <-------中止回来的指令是这条指令

产生中止的指令+8

-PC-->产生中止的指令+12

这时分的PC值其实是比产生中止时分的指令超前12。减去4之后,lr_irq中保存了(产生中止的指令+8)的地址。为什么HW不帮助直接减去8呢?这样,后续软件不就不必再减去4了。这儿咱们不能孤立的看待问题,实践上ARM的反常处理的硬件逻辑不仅仅处理IRQ的exception,还要处理各种exception,很惋惜,不同的exception希望的回来地址不一致,因而,硬件仅仅帮助减去4,剩下的交给软件去调整。

3、mask IRQ exception。也便是设定CPSR.I = 1

4、设定PC值为IRQ exception vector。基本上,ARM处理器的硬件就只能帮你帮到这儿了,一旦设定PC值,ARM处理器就会跳转到IRQ的exception vector地址了,后续的动作都是软件行为了。

四、怎么进入ARM中止处理

1、IRQ mode中的处理

IRQ mode的处理都在vector_irq中,vector_stub是一个宏,界说如下:

.macro vector_stub, name, mode, correction=0
.align 5

vector_\name:
.if \correction
sub lr, lr, #\correction-------------(1)
.endif

@
@ Save r0, lr_(parent PC) and spsr_
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr--------(2)
mrs lr, spsr
str lr, [sp, #8] @ save spsr

@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr-----------------------(3)
eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0

@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f---lr保存了产生IRQ时分的CPSR,经过and操作,能够获取CPSR.M[3:0]的值

这时分,假如中止产生在用户空间,lr=0,假如是内核空间,lr=3
THUMB( adr r0, 1f )----依据其时PC值,获取lable 1的地址
THUMB( ldr lr, [r0, lr, lsl #2] )-lr依据其时mode,要么是__irq_usr的地址 ,要么是__irq_svc的地址
mov r0, sp------将irq mode的stack point经过r0传递给行将跳转的函数
ARM( ldr lr, [pc, lr, lsl #2] )---依据mode,给lr赋值,__irq_usr或许__irq_svc
movs pc, lr @ branch to handler in SVC mode-----(4)
ENDPROC(vector_\name)

.align 2
@ handler addresses follow this label
1:
.endm

(1)咱们希望在栈上保存产生中止时分的硬件现场(HW context),这儿就包含ARM的core register。上一章咱们现已了解到,当产生IRQ中止的时分,lr中保存了产生中止的PC+4,假如减去4的话,得到的便是产生中止那一点的PC值。

(2)其时是IRQ mode,SP_irq在初始化的时分现已设定(12个字节)。在irq mode的stack上,顺次保存了产生中止那一点的r0值、PC值以及CPSR值(详细操作是经过spsr进行的,其实硬件现已帮咱们保存了CPSR到SPSR中了)。为何要保存r0值?由于随后的代码要运用r0寄存器,因而咱们要把r0放到栈上,只需这样才干完彻底全康复硬件现场。

(3)不幸的IRQ mode少纵即逝,这段代码便是预备将ARM推送到SVC mode。怎么预备?其实便是修正SPSR的值,SPSR不是CPSR,不会引起processor mode的切换(究竟这一步仅仅预备罢了)。

(4)许多反常处理的代码回来的时分都是运用了stack相关的操作,这儿没有。“movs pc, lr ”指令除了字面上意思(把lr的值交给pc),还有一个隐含的操作(movs中‘s’的意义):把SPSR copy到CPSR,然后完成了形式的切换。

2、当产生中止的时分,代码运转在用户空间

Interrupt dispatcher的代码如下:

vector_stub irq, IRQ_MODE, 4 -----减去4,确保回来产生中止之后的那条指令

.long __irq_usr @ 0 (USR_26 / USR_32) <---------------------> base address + 0
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)<---------------------> base address + 12
.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

这其实便是一个lookup table,依据CPSR.M[3:0]的值进行跳转(参阅上一节的代码:and lr, lr, #0x0f)。因而,该lookup table共设定了16个进口,当然只需两项有用,别离对应user mode和svc mode的跳转地址。其他进口的__irq_invalid也是十分要害的,这确保了在其形式下产生了中止,体系能够捕获到这样的过错,为debug供给有用的信息。

.align 5
__irq_usr:
usr_entry---------请参阅本章第一节(1)保存用户现场的描绘
kuser_cmpxchg_check---和本文描绘的内容无关,这些不就介绍了
irq_handler----------中心处理内容,请参阅本章第二节的描绘
get_thread_info tsk------tsk是r9,指向其时的thread info数据结构
mov why, #0--------why是r8
b ret_to_user_from_irq----中止回来,下一章会详细描绘

(1)保存产生中止时分的现场。所谓保存现场其实便是把产生中止那一刻的硬件上下文(各个寄存器)保存在了SVC mode的stack上。

.macro usr_entry
sub sp, sp, #S_FRAME_SIZE--------------A
stmib sp, {r1 – r12} -------------------B

ldmia r0, {r3 – r5}--------------------C
add r0, sp, #S_PC-------------------D
mov r6, #-1----orig_r0的值

str r3, [sp] ----保存中止那一刻的r0

stmia r0, {r4 – r6}--------------------E
stmdb r0, {sp, lr}^-------------------F
.endm

A:代码履行到这儿的时分,ARM处理现已切换到了SVC mode。一旦进入SVC mode,ARM处理器看到的寄存器现已产生变化,这儿的sp现已变成了sp_svc了。因而,后续的压栈操作都是压入了产生中止那一刻的进程的(或许内核线程)内核栈(svc mode栈)。详细保存多少个寄存器值?S_FRAME_SIZE现已给出了答案,这个值是18个寄存器。r0~r15再加上CPSR也只需17个罢了。先保存这个疑问,咱们稍后答复。

B:压栈首要压入了r1~r12,这儿为何不处理r0?由于r0在irq mode切到svc mode的时分被污染了,不过,原始的r0被保存的irq mode的stack上了。r13(sp)和r14(lr)需求保存吗,当然需求,稍后再保存。履行到这儿,内核栈的布局如下图所示:

stmib中的ib表明increment before,因而,在压入R1的时分,stack pointer会先添加4,重要是预留r0的方位。stmib sp, {r1 – r12}指令中的sp没有“!”的修饰符,表明压栈完结后并不会真实更新stack pointer,因而sp坚持本来的值。

C:留意,这儿r0指向了irq stack,因而,r3是中止时分的r0值,r4是中止现场的PC值,r5是中止现场的CPSR值。

D:把r0赋值为S_PC的值。依据struct pt_regs的界说(这个数据结构反响了内核栈上的保存的寄存器的摆放信息),从低地址到高地址顺次为:

ARM_r0
ARM_r1
ARM_r2
ARM_r3
ARM_r4
ARM_r5
ARM_r6
ARM_r7
ARM_r8
ARM_r9
ARM_r10
ARM_fp
ARM_ip
ARM_sp
ARM_lr
ARM_pc<---------add r0, sp, #S_PC指令使得r0指向了这个方位
ARM_cpsr
ARM_ORIG_r0

为什么要给r0赋值?因而kernel不想修正sp的值,坚持sp指向栈顶。

E:在内核栈上保存剩下的寄存器的值,依据代码,顺次是r0,PC,CPSR和orig r0。履行到这儿,内核栈的布局如下图所示:

R0,PC和CPSR来自IRQ mode的stack。实践上这段操作便是从irq stack就中止现场搬移到内核栈上。

F:内核栈上还有两个寄存器没有坚持,别离是产生中止时分sp和lr这两个寄存器。这时分,r0指向了保存PC寄存器那个地址(add r0, sp, #S_PC),stmdb r0, {sp, lr}^中的“db”是decrement before,因而,将sp和lr压入stack中的剩下的两个方位。需求留意的是,咱们保存的是产生中止那一刻(关于本节,这是其时user mode的sp和lr),指令中的“^”符号表明拜访user mode的寄存器。

(2)中心处理

irq_handler的处理有两种装备。一种是装备了CONFIG_MULTI_IRQ_HANDLER。这种状况下,linux kernel答应run time设定irq handler。假如咱们需求一个linux kernel image支撑多个渠道,这是就需求装备这个选项。别的一种是传统的linux的做法,irq_handler实践上便是arch_irq_handler_default,详细代码如下:

.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp--------设定传递给machine界说的handle_arch_irq的参数
adr lr, BSYM(9997f)----设定回来地址
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
9997:
.endm

关于状况一,machine相关代码需求设定handle_arch_irq函数指针,这儿的汇编指令只需求调用这个machine代码供给的irq handler即可(当然,要预备好参数传递和回来地址设定)。

状况二要略微杂乱一些(并且,看起来kernel中运用的越来越少),代码如下:

.macro arch_irq_handler_default
get_irqnr_preamble r6, lr
1: get_irqnr_and_base r0, r2, r6, lr
movne r1, sp
@
@ asm_do_IRQ 需求两个参数,一个是 irq number(保存在r0)
@ 另一个是 struct pt_regs *(保存在r1中)
adrne lr, BSYM(1b)-------回来地址设定为符号1,也便是说要不断的解析irq状况寄存器

的内容,得到IRQ number,直到悉数的irq number处理结束
bne asm_do_IRQ
.endm

这儿的代码现已是和machine相关的代码了,咱们这儿仅仅简略描绘一下。所谓machine相关也便是说和体系中的中止操控器相关了。get_irqnr_preamble是为中止处理做预备,有些渠道底子不需求这个进程,直接界说为空即可。get_irqnr_and_base 有四个参数,别离是:r0保存了本次解析的irq number,r2是irq状况寄存器的值,r6是irq controller的base address,lr是scratch register。

3、当产生中止的时分,代码运转在内核空间

假如中止产生在内核空间,代码会跳转到__irq_svc处履行:

.align 5
__irq_svc:
svc_entry----保存产生中止那一刻的现场保存在内核栈上
irq_handler ----详细的中止处理,同user mode的处理。

#ifdef CONFIG_PREEMPT--------和preempt相关的处理,本文不进行描绘
get_thread_info tsk
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
ldr r0, [tsk, #TI_FLAGS] @ get flags
teq r8, #0 @ if preempt count != 0
movne r0, #0 @ force flags to 0
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
#endif

svc_exit r5, irq = 1 @ return from exception

保存现场的代码和user mode下的现场保存是相似的,因而这儿不再详细描绘,仅仅鄙人面的代码中内嵌一些注释。

.macro svc_entry, stack_hole=0
sub sp, sp, #(S_FRAME_SIZE + \stack_hole – 4)----sp指向struct pt_regs中r1的方位
stmia sp, {r1 – r12} ------寄存器入栈。

ldmia r0, {r3 – r5}
add r7, sp, #S_SP – 4 ------r7指向struct pt_regs中r12的方位
mov r6, #-1 ----------orig r0设为-1
add r2, sp, #(S_FRAME_SIZE + \stack_hole – 4)----r2是发现中止那一刻stack的现场
str r3, [sp, #-4]! ----保存r0,留意有一个!,sp会加上4,这时分sp就指向栈顶的r0方位了

mov r3, lr ----保存svc mode的lr到r3
stmia r7, {r2 – r6} ---------压栈,在栈上构成构成struct pt_regs
.endm

五、中止退出进程

1、中止产生在user mode下的退出进程,代码如下:

ENTRY(ret_to_user_from_irq)
ldr r1, [tsk, #TI_FLAGS]
tst r1, #_TIF_WORK_MASK---------------A
bne work_pending
no_work_pending:
asm_trace_hardirqs_on ------和irq flag trace相关,暂时略过

/* perform architecture specific actions before user return */
arch_ret_to_user r1, lr----有些硬件渠道需求在中止回来用户空间做一些特别处理
ct_user_enter save = 0 ----和trace context相关,暂时略过

restore_user_regs fast = 0, offset = 0------------B
ENDPROC(ret_to_user_from_irq)
ENDPROC(ret_to_user)

A:thread_info中的flags成员中有一些low level的标识,假如这些标识设定了就需求进行一些特别的处理,这儿检测的flag首要包含:

#define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | _TIF_NOTIFY_RESUME)

这三个flag别离表明是否需求调度、是否有信号处理、回来用户空间之前是否需求调用callback函数。只需有一个flag被设定了,程序就进入work_pending这个分支。

B:从字面的意思也能够当作,这部分的代码便是将进入中止的时分保存的现场(寄存器值)康复到实践的ARM的各个寄存器中,然后彻底回来到了中止产生的那一点。详细的代码如下:

.macro restore_user_regs, fast = 0, offset = 0
ldr r1, [sp, #\offset + S_PSR] ----r1保存了pt_regs中的spsr,也便是产生中止时的CPSR
ldr lr, [sp, #\offset + S_PC]! ----lr保存了PC值,一起sp移动到了pt_regs中PC的方位
msr spsr_cxsf, r1 ---------赋值给spsr,进行回来用户空间的预备
clrex @ clear the exclusive monitor

.if \fast
ldmdb sp, {r1 – lr}^ @ get calling r1 – lr
.else
ldmdb sp, {r0 – lr}^ ------将保存在内核栈上的数据保存到用户态的r0~r14寄存器
.endif
mov r0, r0 ---------NOP操作,ARMv5T之前的需求这个操作
add sp, sp, #S_FRAME_SIZE – S_PC----现场现已康复,移动svc mode的sp到本来的方位
movs pc, lr --------回来用户空间
.endm

2、中止产生在svc mode下的退出进程,代码如下:

.macro svc_exit, rpsr, irq = 0
.if \irq != 0
@ IRQs already off
.else
@ IRQs off again before pulling preserved data off the stack
disable_irq_notrace
.endif
msr spsr_cxsf, \rpsr-----将中止现场的cpsr值保存到spsr中,预备回来中止产生的现场

ldmia sp, {r0 – pc}^ -----这条指令是ldm反常回来指令,这条指令除了字面上的操作,

还包含了将spsr copy到cpsr中。
.endm

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/qianrushi/yingjian/277232.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部