快乐虾
http://blog.youkuaiyun.com/lights_joy/
lights@hb165.com
本文适用于
ADSP-BF561
uclinux-2008r1.5-rc3(smp patch)
Visual DSP++5.0(update 5)
欢迎转载,但请保留作者信息
1.1 evt_nmi
这个代码是用汇编写的,其实现在arch/blackfin/mach-common/interrupt.s中,用于处理nmi中断:
/* Interrupt routine for evt2 (NMI).
* We don't actually use this, so just return.
* For inner circle type details, please see:
* http://docs.blackfin.uclinux.org/doku.php?id=linux:nmi
*/
ENTRY(_evt_nmi)
.weak _evt_nmi
rtn;
ENDPROC(_evt_nmi)
空语句,没什么可说的。
1.2 trap
这部分代码用于处理Exception中断,对于内核而言是相当重要的,其实现在arch/blackfin/mach-common/entry.S文件中。
ENTRY(_trap) /* Exception: 4th entry into system event table(supervisor mode)*/
/* Since the kernel stack can be anywhere, it's not guaranteed to be
* covered by a CPLB. Switch to an exception stack; use RETN as a
* scratch register (for want of a better option).
*/
EX_SCRATCH_REG = sp;
//GET_PDA_SAFE(sp);
sp.l = lo(DSPID);
sp.h = hi(DSPID);
sp = [sp];
sp = sp << 2;
sp = sp << 2;
sp = sp << 2;
sp = sp << 2;
sp = sp << 2;
sp = sp << 2;
sp = sp << 2;
sp = sp << 2;
sp = sp << 2;
sp = sp << 2;
sp = sp << 2;
sp = sp << 2;
if cc jump _trap_3;
cc = sp == 0x0;
if !cc jump _trap_4;
_trap_1:
/* sp = 0x0; */
cc = !cc; /* restore cc to 0 */
_trap_2:
sp.l = lo(L1_SCRATCH_COREA);
sp.h = hi(L1_SCRATCH_COREA);
jump _trap_5;
_trap_3:
cc = sp == 0x0;
if cc jump _trap_2;
/* sp = 0x1000000; */
cc = !cc; /* restore cc to 1 */
_trap_4:
sp.l = lo(L1_SCRATCH_COREB);
sp.h = hi(L1_SCRATCH_COREB);
_trap_5:
//GET_PDA_SAFE(sp); end
sp = [sp + PDA_EXSTACK];
/* Try to deal with syscalls quickly. */
[--sp] = ASTAT;
[--sp] = (R7:6, P5:4);
DEBUG_STOP_HWTRACE(p5, r7)
r7 = SEQSTAT; /* reason code is in bit 5:0 */
r6.l = lo(SEQSTAT_EXCAUSE);
r6.h = hi(SEQSTAT_EXCAUSE);
r7 = r7 & r6;
p5.h = _ex_table;
p5.l = _ex_table;
p4 = r7;
p5 = p5 + (p4 << 2);
p4 = [p5];
jump (p4);
.Lbadsys:
r7 = -ENOSYS; /* signextending enough */
[sp + PT_R0] = r7; /* return value from system call */
jump .Lsyscall_really_exit;
ENDPROC(_trap)
在这里EX_SCRATCH_REG定义成RETN这个寄存器。
在SMP版本中,每个核都使用了自身的scratch pad sram做为其私有数据区,因而在这段代码的开头就判断DSPID并设置正确的SP指针,使其指向各自内部的SCRATCH PAD SRAM。在原有的代码中,使用了GET_SAFE_PDA(sp)这个宏来达到目的,但移植到VDSP时,由于汇编语法方面的差异,将这个宏展开并进行了适当修改。
_ex_table是在本文件中定义的函数指针的数组,它指明了所有可能的64种异常的入口:
ENTRY(_ex_table)
/* entry for each EXCAUSE[5:0]
* This table must be in sync with the table in ./kernel/traps.c
* EXCPT instruction can provide 4 bits of EXCAUSE, allowing 16 to be user defined
*/
.long _ex_syscall /* 0x00 - User Defined - Linux Syscall */
.long _ex_soft_bp /* 0x01 - User Defined - Software breakpoint */
.long _ex_replaceable /* 0x02 - User Defined */
.long _ex_trap_c /* 0x03 - User Defined - userspace stack overflow */
.long _ex_spinlock /* 0x04 - User Defined - dump trace buffer */
.long _ex_replaceable /* 0x05 - User Defined */
.long _ex_replaceable /* 0x06 - User Defined */
.long _ex_replaceable /* 0x07 - User Defined */
.long _ex_replaceable /* 0x08 - User Defined */
.long _ex_replaceable /* 0x09 - User Defined */
.long _ex_replaceable /* 0x0A - User Defined */
.long _ex_replaceable /* 0x0B - User Defined */
.long _ex_replaceable /* 0x0C - User Defined */
.long _ex_replaceable /* 0x0D - User Defined */
.long _ex_replaceable /* 0x0E - User Defined */
.long _ex_replaceable /* 0x0F - User Defined */
.long _ex_single_step /* 0x10 - HW Single step */
#ifdef CONFIG_DEBUG_BFIN_HWTRACE_EXPAND
.long _ex_trace_buff_full /* 0x11 - Trace Buffer Full */
#else
.long _ex_trap_c /* 0x11 - Trace Buffer Full */
#endif
.long _ex_trap_c /* 0x12 - Reserved */
.long _ex_trap_c /* 0x13 - Reserved */
.long _ex_trap_c /* 0x14 - Reserved */
.long _ex_trap_c /* 0x15 - Reserved */
.long _ex_trap_c /* 0x16 - Reserved */
.long _ex_trap_c /* 0x17 - Reserved */
.long _ex_trap_c /* 0x18 - Reserved */
.long _ex_trap_c /* 0x19 - Reserved */
.long _ex_trap_c /* 0x1A - Reserved */
.long _ex_trap_c /* 0x1B - Reserved */
.long _ex_trap_c /* 0x1C - Reserved */
.long _ex_trap_c /* 0x1D - Reserved */
.long _ex_trap_c /* 0x1E - Reserved */
.long _ex_trap_c /* 0x1F - Reserved */
.long _ex_trap_c /* 0x20 - Reserved */
.long _ex_trap_c /* 0x21 - Undefined Instruction */
.long _ex_trap_c /* 0x22 - Illegal Instruction Combination */
.long _ex_dviol /* 0x23 - Data CPLB Protection Violation */
.long _ex_trap_c /* 0x24 - Data access misaligned */
.long _ex_trap_c /* 0x25 - Unrecoverable Event */
.long _ex_dmiss /* 0x26 - Data CPLB Miss */
.long _ex_dmult /* 0x27 - Data CPLB Multiple Hits - Linux Trap Zero */
.long _ex_trap_c /* 0x28 - Emulation Watchpoint */
.long _ex_trap_c /* 0x29 - Instruction fetch access error (535 only) */
.long _ex_trap_c /* 0x2A - Instruction fetch misaligned */
.long _ex_trap_c /* 0x2B - Instruction CPLB protection Violation */
.long _ex_icplb_miss /* 0x2C - Instruction CPLB miss */
.long _ex_trap_c /* 0x2D - Instruction CPLB Multiple Hits */
.long _ex_trap_c /* 0x2E - Illegal use of Supervisor Resource */
.long _ex_trap_c /* 0x2E - Illegal use of Supervisor Resource */
.long _ex_trap_c /* 0x2F - Reserved */
.long _ex_trap_c /* 0x30 - Reserved */
.long _ex_trap_c /* 0x31 - Reserved */
.long _ex_trap_c /* 0x32 - Reserved */
.long _ex_trap_c /* 0x33 - Reserved */
.long _ex_trap_c /* 0x34 - Reserved */
.long _ex_trap_c /* 0x35 - Reserved */
.long _ex_trap_c /* 0x36 - Reserved */
.long _ex_trap_c /* 0x37 - Reserved */
.long _ex_trap_c /* 0x38 - Reserved */
.long _ex_trap_c /* 0x39 - Reserved */
.long _ex_trap_c /* 0x3A - Reserved */
.long _ex_trap_c /* 0x3B - Reserved */
.long _ex_trap_c /* 0x3C - Reserved */
.long _ex_trap_c /* 0x3D - Reserved */
.long _ex_trap_c /* 0x3E - Reserved */
.long _ex_trap_c /* 0x3F - Reserved */
_ex_table.end:
//END(_ex_table)
因而,这一段程序就是根据不同的EXCEPTION的类型,调用相应的入口函数进行处理。可以看到,对于大部分的异常,都是通过ex_trap_c这个函数来处理的。
1.3 _evt_ivhw
这部分代码在arch/blackfin/mach-common/interrupt.S中,用于处理Hardware Error Interrupt,VDSP文档对此的解释是:
The Hardware Error Interrupt is generated by:
Bus parity errors
Internal error conditions within the core, such as Performance Monitor overflow
Peripheral errors
Bus timeout errors
看看uclinux的处理:
/* interrupt routine for ivhw - 5 */
ENTRY(_evt_ivhw)
SAVE_ALL_SYS
#ifdef CONFIG_FRAME_POINTER
fp = 0;
#endif
#if ANOMALY_05000283
cc = r7 == r7;
p5.h = HI(CHIPID);
p5.l = LO(CHIPID);
if cc jump _evt_ivhw_1;
r7.l = W[p5];
_evt_ivhw_1:
#endif
#ifdef CONFIG_HARDWARE_PM
r7 = [sp + PT_SEQSTAT];
r7 = r7 >>> 0xe;
r6 = 0x1F;
r7 = r7 & r6;
r5 = 0x12;
cc = r7 == r5;
if cc jump .Lcall_do_ovf; /* deal with performance counter overflow */
#endif
//# We are going to dump something out, so make sure we print IPEND properly
p2.l = lo(IPEND);
p2.h = hi(IPEND);
r0 = [p2];
[sp + PT_IPEND] = r0;
/* set the EXCAUSE to HWERR for trap_c */
r0 = [sp + PT_SEQSTAT];
R1.L = LO(VEC_HWERR);
R1.H = HI(VEC_HWERR);
R0 = R0 | R1;
[sp + PT_SEQSTAT] = R0;
r0 = sp; /* stack frame pt_regs pointer argument ==> r0 */
SP += -12;
call _trap_c;
SP += 12;
call _ret_from_exception;
.Lcommon_restore_all_sys:
RESTORE_ALL_SYS
rti;
#ifdef CONFIG_HARDWARE_PM
.Lcall_do_ovf:
SP += -12;
call _pm_overflow;
SP += 12;
jump .Lcommon_restore_all_sys;
#endif
ENDPROC(_evt_ivhw)
在这里,SAVE_ALL_SYS将系统的大部分寄存器的值都压到栈中,且入栈的顺序是和pt_regs这个结构体的成员一一对应的,因此最后可以将此结构体的指针传给函数trap_c。需要注意的是,这个中断处理代码并未重新设置SP的值,也就是说它将使用发生错误的线程的SP。
PT_IPEND和PT_SEQSTAT分别定义了保存IPEND和SEQSTAT这两个寄存器值的成员相应于pt_regs这个结构体的偏移量,因而使用[sp + PT_IPEND]这样的方式可以取得相应寄存器的值。
VEC_HWERR则是定义好的一个常数:
/* The hardware reserves (63) for future use - we use it to tell our
* normal exception handling code we have a hardware error
*/
#define VEC_HWERR (63)
因为出错的原因保存在SEQSTAT寄存器的低5位中,因此通过取或的方式将pt_regs->seqstat的值设置为EXCAUSE,而不是原来的32位整数。
然后调用trap_c进行处理,此函数将决定输出错误信息后返回原位置执行,或者中止执行。
1.4 _ret_from_exception
这个函数将由_evt_ivhw在返回时调用,其实现在arch/blackfin/mach-common/entry.S中:
ENTRY(_ret_from_exception)
p2.l = lo(IPEND);
p2.h = hi(IPEND);
csync;
r0 = [p2];
[sp + PT_IPEND] = r0;
_ret_from_exception_1:
r2 = LO(~0x37) (Z);
r0 = r2 & r0;
cc = r0 == 0;
if !cc jump _ret_from_exception_4; /* if not return to user mode, get out */
/* Make sure any pending system call or deferred exception
* return in ILAT for this process to get executed, otherwise
* in case context switch happens, system call of
* first process (i.e in ILAT) will be carried
* forward to the switched process
*/
p2.l = lo(ILAT);
p2.h = hi(ILAT);
r0 = [p2];
r1 = (EVT_IVG14 | EVT_IVG15) (z);
r0 = r0 & r1;
cc = r0 == 0;
if !cc jump _ret_from_exception_5;
/* Set the stack for the current process */
r7 = sp;
r4.l = lo(ALIGN_PAGE_MASK);
r4.h = hi(ALIGN_PAGE_MASK);
r7 = r7 & r4; /* thread_info->flags */
p5 = r7;
r7 = [p5 + TI_FLAGS];
r4.l = lo(_TIF_WORK_MASK);
r4.h = hi(_TIF_WORK_MASK);
r7 = r7 & r4;
cc = r7 == 0;
if cc jump _ret_from_exception_4;
p0.l = lo(EVT15);
p0.h = hi(EVT15);
p1.l = _schedule_and_signal;
p1.h = _schedule_and_signal;
[p0] = p1;
csync;
raise 15; /* raise evt14 to do signal or reschedule */
_ret_from_exception_4:
r0 = syscfg;
bitclr(r0, 0);
syscfg = r0;
_ret_from_exception_5:
rts;
ENDPROC(_ret_from_exception)
与一般函数不同的是,这个函数的开头没有link指令,也没有入栈和出栈的操作。因而在此函数中使用寄存器必须格外小心!
1.4.1 判断是否有高优先级的中断挂起
在函数的开始,直接取IPEND寄存器的值判断0x37指定的掩码位置的状态,如果为1则直接跳转到_ret_from_exception_4,查一下IPEND这几位的意义:
The IPEND register keeps track of all currently nested interrupts (see Figure 4-12). Each bit in IPEND indicates that the corresponding interrupt is currently active or nested at some level. It may be read in Supervisor mode, but not written. The IPEND[4] bit is used by the Event Controller to temporarily disable interrupts on entry and exit to an interrupt service routine.
When an event is processed, the corresponding bit in IPEND is set. The least significant bit in IPEND that is currently set indicates the interrupt that is currently being serviced. At any given time, IPEND holds the current status of all nested events.
0:EMU中断
1:RST中断
2:NMI
4:Global Interrupt Disable
5:IVHW
当发生上述状况时,内核直接跳转到_ret_from_exception_4。
_ret_from_exception_4:
r0 = syscfg;
bitclr(r0, 0);
syscfg = r0;
_ret_from_exception_5:
rts;
在这里syscfg的最低位表示Supervisor Single Step,如果置1,则每执行一条指令将中断一次,似乎是用于程序调试的,不是很明白。
1.4.2 判断是否有中断14或者中断15
/* Make sure any pending system call or deferred exception
* return in ILAT for this process to get executed, otherwise
* in case context switch happens, system call of
* first process (i.e in ILAT) will be carried
* forward to the switched process
*/
p2.l = lo(ILAT);
p2.h = hi(ILAT);
r0 = [p2];
r1 = (EVT_IVG14 | EVT_IVG15) (z);
r0 = r0 & r1;
cc = r0 == 0;
if !cc jump _ret_from_exception_5;
这段代码将判断是否有软中断或者中断15需要处理,如果没有则直接跳转到ret_from_exception_5返回。
1.4.3 处理软中断或者系统调用中断
当有软中断或者系统调用中断挂起时,将执行下面一段代码:
/* Set the stack for the current process */
r7 = sp;
r4.l = lo(ALIGN_PAGE_MASK);
r4.h = hi(ALIGN_PAGE_MASK);
r7 = r7 & r4; /* thread_info->flags */
p5 = r7;
r7 = [p5 + TI_FLAGS];
r4.l = lo(_TIF_WORK_MASK);
r4.h = hi(_TIF_WORK_MASK);
r7 = r7 & r4;
cc = r7 == 0;
if cc jump _ret_from_exception_4;
p0.l = lo(EVT15);
p0.h = hi(EVT15);
p1.l = _schedule_and_signal;
p1.h = _schedule_and_signal;
[p0] = p1;
csync;
raise 15; /* raise evt14 to do signal or reschedule */
要理解这段代码,先得明白内核中为每一个线程都提供了一个叫thread_union的union来进行描述:
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
且每一个thread_union都是按8K对齐的,这一点通过链接文件或者内核的内存管理来保证。这样只要将SP向下与8K对齐,就可以访问到thread_info这一结构体。再把它加上TI_FLAGS这一偏移量,自然可以访问到thread_info这一结构体中的flags成员,在此判断flags的最低位,如果为0则直接返回。此成员的意义暂时不清。
如果最低位为1则还要执行之后的一段代码,设置好EVT15的入口,然后生成一个软中断。
1.5 evt_timer
这个事件入口在arch/blackfin/mach-common/interrupt.s中实现:
/* interrupt routine for core timer - 6 */
ENTRY(_evt_timer)
TIMER_INTERRUPT_ENTRY(EVT_IVTMR_P)
在这里有:
/* For timer interrupts, we need to save IPEND, since the user_mode
macro accesses it to determine where to account time. */
#define TIMER_INTERRUPT_ENTRY(N) /
[--sp] = SYSCFG; /
/
[--sp] = P0; /*orig_p0*/ /
[--sp] = R0; /*orig_r0*/ /
[--sp] = (R7:0,P5:0); /
p0.l = lo(IPEND); /
p0.h = hi(IPEND); /
r1 = [p0]; /
R0 = (N); /
jump __common_int_entry;
而EVT_IVTMR_P则定义为:
#define EVT_IVTMR_P 0x00000006 /* Timer interrupt bit position */
在此之前还有一句说明:
/**************************************************
* EVT registers (ILAT, IMASK, and IPEND).
**************************************************/
可以猜测得到,它就是以位掩码做为参数调用__common_int_entry。
下面分析一下进入此中断入口时的系统状态。
当进入此中断时,RETI寄存器保存了中断结束时的返回地址。
从上述代码中可以看出,此中断入口没有使用新的SP,此时SP将指向内核线程或者用户线程的Stack。到jump __common_int_entry;为止,此时堆栈中的数据如下:
SYSCFG
P0
R0
R7 – R0
P5 – P0
1.6 __common_int_entry
实际上,uclinux内核对中断6到中断13的处理,都是调用这个函数进行处理的,且在参数中给出中断掩码的位置。
这个函数的实现在arch/blackfin/mach-common/interrupt.S中:
/* Common interrupt entry code. First we do CLI, then push
* RETI, to keep interrupts disabled, but to allow this state to be changed
* by local_bh_enable.
* R0 contains the interrupt number, while R1 may contain the value of IPEND,
* or garbage if IPEND won't be needed by the ISR. */
__common_int_entry:
[--sp] = fp;
[--sp] = usp;
[--sp] = i0;
[--sp] = i1;
[--sp] = i2;
[--sp] = i3;
[--sp] = m0;
[--sp] = m1;
[--sp] = m2;
[--sp] = m3;
[--sp] = l0;
[--sp] = l1;
[--sp] = l2;
[--sp] = l3;
[--sp] = b0;
[--sp] = b1;
[--sp] = b2;
[--sp] = b3;
[--sp] = a0.x;
[--sp] = a0.w;
[--sp] = a1.x;
[--sp] = a1.w;
[--sp] = LC0;
[--sp] = LC1;
[--sp] = LT0;
[--sp] = LT1;
[--sp] = LB0;
[--sp] = LB1;
[--sp] = ASTAT;
[--sp] = r0; /* Skip reserved */
[--sp] = RETS;
r2 = RETI;
[--sp] = r2;
[--sp] = RETX;
[--sp] = RETN;
[--sp] = RETE;
[--sp] = SEQSTAT;
[--sp] = r1; /* IPEND - R1 may or may not be set up before jumping here. */
/* Switch to other method of keeping interrupts disabled. */
#ifdef CONFIG_DEBUG_HWERR
r1 = 0x3f;
sti r1;
#else
cli r1;
#endif
[--sp] = RETI; /* orig_pc */
/* Clear all L registers. */
r1 = 0 (x);
l0 = r1;
l1 = r1;
l2 = r1;
l3 = r1;
#ifdef CONFIG_FRAME_POINTER
fp = 0;
#endif
#if ANOMALY_05000283 || ANOMALY_05000315
cc = r7 == r7;
p5.h = HI(CHIPID);
p5.l = LO(CHIPID);
if cc jump __common_int_entry_1;
r7.l = W[p5];
__common_int_entry_1:
#endif
r1 = sp;
SP += -12;
call _do_irq;
SP += 12;
call _return_from_int;
.Lcommon_restore_context:
RESTORE_CONTEXT
rti;
看起来结构比较简单,就是调用do_irq进行后继处理而已,处理完成后再调用_return_from_int进行一些简单处理。
当从evt_timer跳转过来时,RETI寄存器保存了中断结束时的返回地址,R1中保存了IPEND的值。SP指向内核线程或者用户线程的Stack。
而当从EVT7-EVT13的中断跳转过来时,R0则保存了中断号(7-13)。
此时堆栈中的数据如下:
SYSCFG
P0
R0
R7 – R0
P5 – P0
当程序执行到
call _do_irq;
时,此时堆栈中已经有如下数据:
SYSCFG
P0
R0
R7 – R0
P5 – P0
FP
USP
I0 – I3
M0 – M3
L0 – L3
B0 – B3
A0.x
A0.w
A1.x
A1.w
LC0
LC1
LT0
LT1
LB0
LB1
ASTAT
R0
RETS
RETI
RETX
RETN
RETE
SEQSTAT
R1
RETI
保留12个字节。
在调用call do_irq时,R0中保存了IPEND的值,再注意到
r1 = sp;
SP += -12;
call _do_irq;
看看pt_regs的定义:
/* this struct defines the way the registers are stored on the
stack during a system call. */
struct pt_regs {
long orig_pc;
long ipend;
long seqstat;
long rete;
long retn;
long retx;
long pc; /* PC == RETI */
long rets;
long reserved; /* Used as scratch during system calls */
long astat;
long lb1;
long lb0;
long lt1;
long lt0;
long lc1;
long lc0;
long a1w;
long a1x;
long a0w;
long a0x;
long b3;
long b2;
long b1;
long b0;
long l3;
long l2;
long l1;
long l0;
long m3;
long m2;
long m1;
long m0;
long i3;
long i2;
long i1;
long i0;
long usp;
long fp;
long p5;
long p4;
long p3;
long p2;
long p1;
long p0;
long r7;
long r6;
long r5;
long r4;
long r3;
long r2;
long r1;
long r0;
long orig_r0;
long orig_p0;
long syscfg;
};
因为堆栈空间是从高往低走的,而结构体的存储空间则是从低往高分配的,所以这里pt_regs的成员的顺序与寄存器入栈的顺序相反,从而将它们一一对应起来,这样调用do_irq函数时pt_regs就能得到正确的值。
由于do_irq是个C函数,对其调用后SP值将自动恢复为原来的位置。
最后调用了_return_from_int,这个函数位于entry.s,同样的,在调用前后SP不变。
最后调用RESTORE_CONTEXT:
#define RESTORE_CONTEXT restore_context_with_interrupts
#define restore_context_with_interrupts /
sp += 4; /* Skip orig_pc */ /
sp += 4; /* Skip IPEND */ /
SEQSTAT = [sp++]; /
RETE = [sp++]; /
RETN = [sp++]; /
RETX = [sp++]; /
RETI = [sp++]; /
RETS = [sp++]; /
/
GET_PDA(p0, r0); /
r0 = [p0 + PDA_IRQFLAGS]; /
sti r0; /
/
sp += 4; /* Skip Reserved */ /
/
ASTAT = [sp++]; /
/
LB1 = [sp++]; /
LB0 = [sp++]; /
LT1 = [sp++]; /
LT0 = [sp++]; /
LC1 = [sp++]; /
LC0 = [sp++]; /
/
a1.w = [sp++]; /
a1.x = [sp++]; /
a0.w = [sp++]; /
a0.x = [sp++]; /
b3 = [sp++]; /
b2 = [sp++]; /
b1 = [sp++]; /
b0 = [sp++]; /
/
l3 = [sp++]; /
l2 = [sp++]; /
l1 = [sp++]; /
l0 = [sp++]; /
/
m3 = [sp++]; /
m2 = [sp++]; /
m1 = [sp++]; /
m0 = [sp++]; /
/
i3 = [sp++]; /
i2 = [sp++]; /
i1 = [sp++]; /
i0 = [sp++]; /
/
sp += 4; /
fp = [sp++]; /
/
( R7:0, P5:0) = [ SP ++ ]; /
sp += 8; /* Skip orig_r0/orig_p0 */ /
csync; /
SYSCFG = [sp++]; /
csync; /
//.endm
从而完成了现场的恢复。
1.7 _return_from_int
此函数位于arch/blackfin/mach-common/entry.S:
ENTRY(_return_from_int)
/* If someone else already raised IRQ 15, do nothing. */
csync;
p2.l = lo(ILAT);
p2.h = hi(ILAT);
r0 = [p2];
cc = bittst (r0, EVT_IVG15_P);
if cc jump _return_from_int_2;
/* if not return to user mode, get out */
p2.l = lo(IPEND);
p2.h = hi(IPEND);
r0 = [p2];
r1 = 0x17(Z);
r2 = ~r1;
r2.h = 0;
r0 = r2 & r0;
r1 = 1;
r1 = r0 - r1;
r2 = r0 & r1;
cc = r2 == 0;
if !cc jump _return_from_int_2;
/* Lower the interrupt level to 15. */
p0.l = lo(EVT15);
p0.h = hi(EVT15);
p1.l = _schedule_and_signal_from_int;
p1.h = _schedule_and_signal_from_int;
[p0] = p1;
csync;
#if ANOMALY_05000281
r0.l = _safe_speculative_execution;
r0.h = _safe_speculative_execution;
reti = r0;
#endif
r0 = 0x801f (z);
STI r0;
raise 15; /* raise evt15 to do signal or reschedule */
rti;
_return_from_int_2:
rts;
ENDPROC(_return_from_int)
1.8 evt_evt(7-13)
这7个中断入口的实现都在arch/blackfin/mach-common/interrupt.S中:
ENTRY(_evt_evt7)
INTERRUPT_ENTRY(EVT_IVG7_P)
ENTRY(_evt_evt8)
INTERRUPT_ENTRY(EVT_IVG8_P)
ENTRY(_evt_evt9)
INTERRUPT_ENTRY(EVT_IVG9_P)
ENTRY(_evt_evt10)
INTERRUPT_ENTRY(EVT_IVG10_P)
ENTRY(_evt_evt11)
INTERRUPT_ENTRY(EVT_IVG11_P)
ENTRY(_evt_evt12)
INTERRUPT_ENTRY(EVT_IVG12_P)
ENTRY(_evt_evt13)
INTERRUPT_ENTRY(EVT_IVG13_P)
在这里INTERRUPT_ENTRY的定义为:
///* This is used for all normal interrupts. It saves a minimum of registers
// to the stack, loads the IRQ number, and jumps to common code. */
#define INTERRUPT_ENTRY(N) /
[--sp] = SYSCFG; /
/
[--sp] = P0; /*orig_p0*/ /
[--sp] = R0; /*orig_r0*/ /
[--sp] = (R7:0,P5:0); /
R0 = (N); /
jump __common_int_entry;
注意,这里的R0保存了中断号N,N可取7-13。在对__common_int_entry的分析中我们可以发现,它的R0寄存器一直没有改变,直到调用do_irq。do_irq是一个用C写的函数,其声明为
void do_irq(int vec, struct pt_regs *fp)
而我们又知道,VDSP编译器在函数参数小于3个的情况下会使用R0, R1, R2这三个寄存器来传值,这就是do_irq中vec这个参数的来源。
1.9 do_irq
前面提到,当内核发生IVG6-IVG13这8个中断时,最后都会调用do_irq函数进行处理,下面看看这个函数的实现:
void do_irq(int vec, struct pt_regs *fp)
{
if (vec == EVT_IVTMR_P) {
vec = IRQ_CORETMR;
} else {
struct ivgx *ivg = ivg7_13[vec - IVG7].ifirst;
struct ivgx *ivg_stop = ivg7_13[vec - IVG7].istop;
unsigned long sic_status0, sic_status1;
SSYNC();
if (smp_processor_id()) {
/* This will be optimized out in UP mode. */
sic_status0 = bfin_read_SICB_IMASK0() & bfin_read_SICB_ISR0();
sic_status1 = bfin_read_SICB_IMASK1() & bfin_read_SICB_ISR1();
} else {
sic_status0 = bfin_read_SICA_IMASK0() & bfin_read_SICA_ISR0();
sic_status1 = bfin_read_SICA_IMASK1() & bfin_read_SICA_ISR1();
}
for (;; ivg++) {
if (ivg >= ivg_stop) {
num_spurious++;
return;
} else if ((sic_status0 & ivg->isrflag0) ||
(sic_status1 & ivg->isrflag1))
break;
}
vec = ivg->irqno;
}
asm_do_IRQ(vec, fp);
#ifdef CONFIG_KGDB
kgdb_process_breakpoint();
#endif
}
当调用此函数时,vec保存了内部中断的中断号,即6-13,然后此函数通过读取SIC_ISR和SIC_IMASK,确定外部中断号,并将之转换为内核的中断描述表(irq_desc)的序号。最后使用此序号调用asm_do_IRQ函数。
1.10 asm_do_IRQ
这个函数在arch/kernel/blackfin/irqchip.c中:
/*
* do_IRQ handles all hardware IRQs. Decoded IRQs should not
* come via this function. Instead, they should provide their
* own 'handler'
*/
#ifdef CONFIG_DO_IRQ_L1
__attribute__((l1_text))
#endif
asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs;
struct irq_desc *desc = irq_desc + irq;
unsigned short pending, other_ints;
old_regs = set_irq_regs(regs);
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (irq >= NR_IRQS)
desc = &bad_irq_desc;
irq_enter();
generic_handle_irq(irq);
/* If we're the only interrupt running (ignoring IRQ15 which is for
syscalls), lower our priority to IRQ14 so that softirqs run at
that level. If there's another, lower-level interrupt, irq_exit
will defer softirqs to that. */
CSYNC();
pending = bfin_read_IPEND() & ~0x8000;
other_ints = pending & (pending - 1);
if (other_ints == 0)
lower_to_irq14();
irq_exit();
set_irq_regs(old_regs);
}
进入这个函数的时候,irq参数为irq_desc这个内核数组的序号,而regs则是中断发生时系统寄存器的状态。
这个函数主要就是两个工作,一个是调用generic_handle_irq进行中断处理,再一个就是当没有其它中断的时候,将中断级别降为IRQ14。
/*
* Architectures call this to let the generic IRQ layer
* handle an interrupt. If the descriptor is attached to an
* irqchip-style controller then we call the ->handle_irq() handler,
* and it calls __do_IRQ() if it's attached to an irqtype-style controller.
*/
static inline void generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_desc + irq;
#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
desc->handle_irq(irq, desc);
#else
if (likely(desc->handle_irq))
desc->handle_irq(irq, desc);
else
__do_IRQ(irq);
#endif
}
因为在内核中已经为每一个irq_desc都设置了处理函数,即desc->handle_irq,因此__do_IRQ将不会被调用。
lower_to_irq14的实现则在arch/blackfin/kernel/entry.s中:
ENTRY(_lower_to_irq14)
#if ANOMALY_05000281
r0.l = _safe_speculative_execution;
r0.h = _safe_speculative_execution;
reti = r0;
#endif
r0 = 0x401f;
sti r0;
raise 14;
rti;
这段代码的最后使用了rti指令,因此它将退出中断7到中断13的执行,由于此前已经关闭了所有中断,仅保留中断14,因此在退出中断之后将立即进入中断14执行。此外,在调用lower_to_irq14函数的时候,使用了call指令,因此rets寄存器的值将指向lower_to_irq14这个函数调用的下一条语句。
1.11 _evt14_softirq
此中断入口位于arch/blackfin/mach-common/entry.s:
ENTRY(_evt14_softirq)
cli r0;
[--sp] = RETI;
SP += 4;
rts;
这里挺有意思的一点是它最后使用了一条rts指令,而不是通常的rti指令,这是因为中断14将由_lower_to_irq14实现。在调用lower_to_irq14函数的时候,使用了call指令,因此rets寄存器的值将指向lower_to_irq14这个函数调用的下一条语句。这段代码的最后使用rts指令,此时将导致跳转到lower_to_irq14函数调用的下一条语句执行。
1.12 _evt_system_call
此入口位于arch/blackfin/mach-common/interrupt.s:
/* interrupt routine for system_call - 15 */
ENTRY(_evt_system_call)
SAVE_CONTEXT_SYSCALL
#ifdef CONFIG_FRAME_POINTER
fp = 0;
#endif
call _system_call;
jump .Lcommon_restore_context;
ENDPROC(_evt_system_call)
直接就调用了system_call,这个函数位于arch/blackfin/mach-common/entry.S,它的作用就是根据序号调用_sys_call_table中的指定函数。
ENTRY(_system_call)
/* Store IPEND */
p2.l = lo(IPEND);
p2.h = hi(IPEND);
csync;
r0 = [p2];
[sp + PT_IPEND] = r0;
/* Store RETS for now */
r0 = rets;
[sp + PT_RESERVED] = r0;
/* Set the stack for the current process */
r7 = sp;
r6.l = lo(ALIGN_PAGE_MASK);
r6.h = hi(ALIGN_PAGE_MASK);
r7 = r7 & r6; /* thread_info */
p2 = r7;
p2 = [p2];
[p2+(TASK_THREAD+THREAD_KSP)] = sp;
/* Check the System Call */
r7 = __NR_syscall;
/* System call number is passed in P0 */
r6 = p0;
cc = r6 < r7;
if ! cc jump .Lbadsys;
/* are we tracing syscalls?*/
r7 = sp;
r6.l = lo(ALIGN_PAGE_MASK);
r6.h = hi(ALIGN_PAGE_MASK);
r7 = r7 & r6;
p2 = r7;
r7 = [p2+TI_FLAGS];
CC = BITTST(r7,TIF_SYSCALL_TRACE);
if CC JUMP _sys_trace;
/* Execute the appropriate system call */
p4 = p0;
p5.l = _sys_call_table;
p5.h = _sys_call_table;
p5 = p5 + (p4 << 2);
r0 = [sp + PT_R0];
r1 = [sp + PT_R1];
r2 = [sp + PT_R2];
p5 = [p5];
[--sp] = r5;
[--sp] = r4;
[--sp] = r3;
SP += -12;
call (p5);
SP += 24;
[sp + PT_R0] = r0;
.Lresume_userspace:
r7 = sp;
r4.l = lo(ALIGN_PAGE_MASK);
r4.h = hi(ALIGN_PAGE_MASK);
r7 = r7 & r4; /* thread_info->flags */
p5 = r7;
.Lresume_userspace_1:
/* Disable interrupts. */
[--sp] = reti;
reti = [sp++];
r7 = [p5 + TI_FLAGS];
r4.l = lo(_TIF_WORK_MASK);
r4.h = hi(_TIF_WORK_MASK);
r7 = r7 & r4;
.Lsyscall_resched:
cc = BITTST(r7, TIF_NEED_RESCHED);
if !cc jump .Lsyscall_sigpending;
/* Reenable interrupts. */
[--sp] = reti;
r0 = [sp++];
SP += -12;
call _schedule;
SP += 12;
jump .Lresume_userspace_1;
.Lsyscall_sigpending:
cc = BITTST(r7, TIF_RESTORE_SIGMASK);
if cc jump .Lsyscall_do_signals;
cc = BITTST(r7, TIF_SIGPENDING);
if !cc jump .Lsyscall_really_exit;
.Lsyscall_do_signals:
/* Reenable interrupts. */
[--sp] = reti;
r0 = [sp++];
r0 = sp;
SP += -12;
call _do_signal;
SP += 12;
.Lsyscall_really_exit:
r5 = [sp + PT_RESERVED];
rets = r5;
rts;
ENDPROC(_system_call)
这段代码可以大致分为几个部分:
1.12.1 _system_call之前的工作
在事件处理入口处,执行了一条SAVE_CONTEXT_SYSCALL,这个宏定义为:
#define SAVE_CONTEXT_SYSCALL save_context_syscall
#define save_context_syscall /
[--sp] = SYSCFG; /
/
[--sp] = P0; /*orig_p0*/ /
[--sp] = R0; /*orig_r0*/ /
[--sp] = ( R7:0, P5:0 ); /
[--sp] = fp; /
[--sp] = usp; /
/
[--sp] = i0; /
[--sp] = i1; /
[--sp] = i2; /
[--sp] = i3; /
/
[--sp] = m0; /
[--sp] = m1; /
[--sp] = m2; /
[--sp] = m3; /
/
[--sp] = l0; /
[--sp] = l1; /
[--sp] = l2; /
[--sp] = l3; /
/
[--sp] = b0; /
[--sp] = b1; /
[--sp] = b2; /
[--sp] = b3; /
[--sp] = a0.x; /
[--sp] = a0.w; /
[--sp] = a1.x; /
[--sp] = a1.w; /
/
[--sp] = LC0; /
[--sp] = LC1; /
[--sp] = LT0; /
[--sp] = LT1; /
[--sp] = LB0; /
[--sp] = LB1; /
/
[--sp] = ASTAT; /
/
[--sp] = r0; /* Skip reserved */ /
[--sp] = RETS; /
r0 = RETI; /
[--sp] = r0; /
[--sp] = RETX; /
[--sp] = RETN; /
[--sp] = RETE; /
[--sp] = SEQSTAT; /
[--sp] = r0; /* Skip IPEND as well. */ /
[--sp] = RETI; /*orig_pc*/ /
/* Clear all L registers. */ /
r0 = 0 (x); /
l0 = r0; /
l1 = r0; /
l2 = r0; /
l3 = r0; /
//.endm
这一段代码实际上是把各个寄存器按照struct pt_regs各成员的反向顺序入栈,这样通过SP指针和适当的偏移量就可以访问struct pt_regs的成员了。注意上述代码并没有将IPEND入栈,原因不明?
1.12.2 保存IPEND
这几行代码为:
/* Store IPEND */
p2.l = lo(IPEND);
p2.h = hi(IPEND);
csync;
r0 = [p2];
[sp + PT_IPEND] = r0;
这所以可以用[sp + PT_IPEND]来保存IPEND寄存器的值,是因为在这段代码之前已经将各寄存器的值按struct pt_regs的反向顺序入栈,使用PT_IPEND自然可以访问此成员,只是让人困惑的是为什么不在_system_call函数之前就保存IPEND的值,而放在这里保存?
1.12.3 使用pt_regs::reserved保存RETS
这两行代码为:
/* Store RETS for now */
r0 = rets;
[sp + PT_RESERVED] = r0;
代码比较简单。找一个位置保存rets,为最后的返回做准备,在通常的函数调用中,通过link和unlink两条指令可以实现rets的自动入栈和出栈,但这里由于很难确定要保留的栈空间大小,所以没有使用link指令,自然得手动保存RETS寄存器并在最后恢复。
1.12.4 取当前线程的描述信息
/* Set the stack for the current process */
r7 = sp;
r6.l = lo(ALIGN_PAGE_MASK);
r6.h = hi(ALIGN_PAGE_MASK);
r7 = r7 & r6; /* thread_info */
p2 = r7;
p2 = [p2];
线程信息是按8K对齐的,从低往高是线程信息,从高往低是此线程的栈。因而直接将SP的低13位置0即可得到线程信息,其结果放在P2中。
1.12.5 保存SP
[p2+(TASK_THREAD+THREAD_KSP)] = sp;
在这里有两个定义:
#define THREAD_KSP 0 /* offsetof(struct thread_struct, ksp) // */
#define TASK_THREAD 424 /* offsetof(struct task_struct, thread) // */
这里有点不懂,从这两个定义的注释上看,P2指向的应该是struct task_struct,但是从前一节的注释来看,P2应该指向一个thread_info结构体?难道两者是一个类似于union这样的关系?
1.12.6 检查是否合法的系统调用
/* Check the System Call */
r7 = __NR_syscall;
/* System call number is passed in P0 */
r6 = p0;
cc = r6 < r7;
if ! cc jump .Lbadsys;
在这里有
#define __NR_syscall 357
也就是内核目前实现的系统调用总数。从注释也可以看出在生成中断15之前应当首先设置好系统调用的序号且必须放在P0中。
1.12.7 判断是否需要进行系统调用的跟踪
/* are we tracing syscalls?*/
r7 = sp;
r6.l = lo(ALIGN_PAGE_MASK);
r6.h = hi(ALIGN_PAGE_MASK);
r7 = r7 & r6;
p2 = r7;
r7 = [p2+TI_FLAGS];
CC = BITTST(r7,TIF_SYSCALL_TRACE);
if CC JUMP _sys_trace;
这段代码将检查thread_info结构体里的flags字段。判断其最低位是否为1,如果为1则进入跟踪过程。
1.12.8 执行系统调用
/* Execute the appropriate system call */
p4 = p0;
p5.l = _sys_call_table;
p5.h = _sys_call_table;
p5 = p5 + (p4 << 2);
r0 = [sp + PT_R0];
r1 = [sp + PT_R1];
r2 = [sp + PT_R2];
p5 = [p5];
[--sp] = r5;
[--sp] = r4;
[--sp] = r3;
SP += -12;
call (p5);
SP += 24;
[sp + PT_R0] = r0;
这段代码先根据调用号取得系统调用函数的地址,然后直接call。这里比较有意思的是参数的传递。在不带__stdcall这样的调用修饰符时,vdsp或者gcc的参数传递都是由调用者分配栈空间,用r0, r1, r2这三个寄存器从左到右传递前三个参数。然后将其余的参数按照从右到左的方向入栈,子函数可以直接访问栈中的数据,也就是传递进来的参数(参见《VDSP函数调用时的参数传递》)。
以此规则来对照上面这段代码,很容易可以发现每个系统调用最多可以有6个参数,在进行系统调用之前可以用r0-r5这六个寄存器进行配置。
在函数调用结束时,编译器使用R0来保存函数的返回值,因而在上面这段代码的最后保存了R0的值。
1.12.9 判断当前线程是否需要进行调度
.Lresume_userspace:
r7 = sp;
r4.l = lo(ALIGN_PAGE_MASK);
r4.h = hi(ALIGN_PAGE_MASK);
r7 = r7 & r4; /* thread_info->flags */
p5 = r7;
.Lresume_userspace_1:
/* Disable interrupts. */
[--sp] = reti;
reti = [sp++];
r7 = [p5 + TI_FLAGS];
r4.l = lo(_TIF_WORK_MASK);
r4.h = hi(_TIF_WORK_MASK);
r7 = r7 & r4;
.Lsyscall_resched:
cc = BITTST(r7, TIF_NEED_RESCHED);
if !cc jump .Lsyscall_sigpending;
这段代码检查thread_info->flags中的第三位,判断其是否为1,如果不为1则继续往下执行。这里开关中断是通过reti的入栈和出栈来完成的。看看文档的解释:
Push/pop on RETS has no effect on the interrupt system.
Push/pop on RETI does affect the interrupt system.
Pushing RETI enables the interrupt system, whereas popping RETI disables the interrupt system.
1.12.10 进行调度
/* Reenable interrupts. */
[--sp] = reti;
r0 = [sp++];
SP += -12;
call _schedule;
SP += 12;
jump .Lresume_userspace_1;
简单调用schedule,然后返回上一层判断是否需要调度,仅当不需要调度时才会退出_system_call。
1.12.11 处理信号量
.Lsyscall_sigpending:
cc = BITTST(r7, TIF_RESTORE_SIGMASK);
if cc jump .Lsyscall_do_signals;
cc = BITTST(r7, TIF_SIGPENDING);
if !cc jump .Lsyscall_really_exit;
.Lsyscall_do_signals:
/* Reenable interrupts. */
[--sp] = reti;
r0 = [sp++];
r0 = sp;
SP += -12;
call _do_signal;
SP += 12;
这段代码检测TIF_RESTORE_SIGMASK和TIF_SIGPENDING这两个位以判断是否需要进行信号量的处理。
1.12.12 退出
.Lsyscall_really_exit:
r5 = [sp + PT_RESERVED];
rets = r5;
rts;
这段代码将退出_system_call调用。