linux 中断与异常---mips基础(一)

时间:2024-04-11 17:05:39
MIPS体系结构采用的是精确异常处理模式
这是什么意思呢?下面来看从“See MIPS Run”一书中的摘录:“In a precise-exception CPU, on any
exception we get pointed at one instruction(the exception victim). All instructions preceding the
exception victim in execution sequence are complete; any work done on the victim and on any
subsequent instructions (BNN NOTE: pipeline effects) has no side effects that the software need worry about. The software that handles exceptions can ignore all the timing effects of the CPU's implementations”
上面的意思其实很简单:在发生这个异常之前的一切计算行为会完整的结束并体现效果。 在发生这个异常之后的一切计算行为(包含当前这条指令)将不会产生任何效果。
另外一种解释是:
A precise exception is one in which the EPC (CP0, Register 14, Select 0) can be used to identify the instruction that caused the exception. For imprecise exceptions, the instruction that caused the exception cannot be identified. Most exceptions are precise. Bus error exceptions may be imprecise.

  • 异常处理一般过程
With the exception of Reset, Soft Reset, NMI, and Debug exceptions, which have their own special processing as
described below, exceptions have the same basic processing flow:
• If the EXL bit in the Status register is cleared, the EPC register is loaded with the PC at which execution will be
restarted and the BD bit is set appropriately in the Cause register. If the instruction is not in the delay slot of a branch,
the BD bit in Cause will be cleared and the value loaded into the EPC register is the current PC. If the instruction is
in the delay slot of a branch, the BD bit in Cause is set and EPC is loaded with PC-4. If the EXL bit in the Status
register is set, the EPC register is not loaded and the BD bit is not changed in the Cause register.
• The CE and ExcCode fields of the Cause registers are loaded with the values appropriate to the exception. The CE
field is loaded, but not defined, for any exception type other than a coprocessor unusable exception.
• The EXL bit is set in the Status register.
• The processor is started at the exception vector.
The value loaded into EPC represents the restart address for the exception and need not be modified by exception handler
software in the normal case. Software need not look at the BD bit in the Cause register unless is wishes to identify the
address of the instruction that actually caused the exception.
Note that individual exception types may load additional information into other registers. This is noted in the description
of each exception type below.

EPC中存放的是异常发生时执行的指令地址,或者分支延时发生异常,则存放的是分支的指令地址,不管怎么样,异常处理函数返回都从EPC开始恢复执行,如果在分支延时指令发生异常,则需要在cause寄存器中存放相应标志,这样就可以准确的知道发生异常的指令地址了。

Operation:
if StatusEXL = 0 then
    if InstructionInBranchDelaySlot then
        EPC <- PC - 4
        CauseBD <- 1
    else
        EPC <- PC
        CauseBD <- 0
    endif
    if ExceptionType = TLBRefill then
        vectorOffset <- 0x000
    elseif (ExceptionType = Interrupt) and
        (CauseIV = 1) then
        vectorOffset <- 0x200
    else
        vectorOffset <- 0x180
    endif
else
    vectorOffset <- 0x180
endif
CauseCE <- FaultingCoprocessorNumber
CauseExcCode <- ExceptionType
StatusEXL <- 1
if StatusBEV = 1 then
    PC <- 0xBFC0_0200 + vectorOffset
else
    PC <- 0x8000_0000 + vectorOffset
endif

As with any procedure, the exception handler must save any registers it may modify, and then restore them
before returning control to the interrupted program. Saving registers in memory poses a problem in MIPS:
addressing the memory requires a register (the base register) in which the address is formed. This means that a register must be modified before any register can be saved! The MIPS register usage convention (see Laboratory
4) reserves registers $26 and $27 ($k0 and $k1) for the use of the interrupt handler. This means
that the interrupt handler can use these registers without having to save them first. A user program that uses
these registers may find them unexpectedly changed.

The CPU operates in one of the two possible modes, user and kernel. User programs run in user mode. The
CPU enters the kernel mode when an exception happens. Coprocessor 0 can only be used in kernel mode.

说明:为何分支延时槽中的指令发生异常要从分支指令重新执行呢,这是因为mips的指令执行是流水线结构,分析指令的执行结果不会影响到延时槽中指令的执行,也就是说不管分支指令往哪里跳,延时槽的指令都会执行,如果EPC保存延时指令地址,则分析指令执行的结果将会丢失,这样异常处理结束后恢复执行的结果就不正确

  • 异常入口(向量)

The Reset, Soft Reset, and NMI exceptions are always vectored to location 0xBFC0_0000. Debug exceptions are
vectored to location 0xBFC0_0480 or to location 0xFF20_0200 if the ProbTrap bit is 0 or 1, respectively, in the EJTAG
Control register (ECR). Addresses for all other exceptions are a combination of a vector offset and a base address.

Table4-2 gives the base address as a function of the exception and whether the BEV bit is set in the Status register.
linux 中断与异常---mips基础(一)

Table 4-3 gives the offsets from the base address as a function of the exception.
linux 中断与异常---mips基础(一)

CauseIV:

Setting the CP0 CauseIV bit to 1 causes Interrupt exceptions to use a dedicated exception vector offset (0x200), rather than having to use the general exception vector offset (0x180).

Table 4-4 combines these two tables into one that
contains all possible vector addresses as a function of the state that can affect the vector selection.
linux 中断与异常---mips基础(一)

In MIPS32® Release 2 and higher architectures, software is allowed to specify the vector base address via the CP0 Ebase register for exceptions that occur when CP0 StatusBEV equals 0.
  • StatusBEV = 1: Exceptions vector to an uncached entry point in KSEG1: 0xBFC00xxx
  • StatusBEV = 0: Exceptions vector to cached entry points in KSEG0: defined by CP0 Ebase register, plus some offset
Note: StatusBEV = 1 at reset.
If Ebase is to be changed, it must be done with StatusBEV = 1 (i.e. at system boot). The operation of the CPU is UNDEFINED if Ebase is written when StatusBEV = 0.
The Ebase default is 0x8000_0000 after reset.
EBase寄存器是一个可读写寄存器,包含例外向量基地址和一个只读的CPU号。
linux 中断与异常---mips基础(一)
linux 中断与异常---mips基础(一)
linux 中断与异常---mips基础(一)
对Cache Error这个特殊的异常来说,需要给他安排一个任何时候都是Uncached的基地址了。因为发生这个异常时Cache已经不可靠了,在处理它是就不能使用它了。因此这个异常的入口基地址为:
    BEV = 1 : BFC0,0300    (系统启动地址空间 : kseg1)
    BEV = 0 :
  [SP]:  A000,0000 (物理内存地址 :  kseg1)
  [MP]:  EBASE[31.30] || 1 || EBASE[28...12] || 0x000 (物理内存地址 :  kseg1)

上面的总结一下:
Reset, Soft Reset和NMI: 不受任何配置的影响,异常向量位置总是在0XBFC0_0000
General Exception异常向量在0xBFC0_0200 + 0x180  或 Ebase + 0x180
Interrupt: IV 表示是否使用专用的异常处理向量, IV=0,采用General Exception中断向量, IV=1,则采用int专用的中断向量
TLB refill: EXL为0时,采用TLB refill专用的异常处理向量,EXL为1时,采用General Exception中断向量


  • 异常优先级
所谓的优先级是指:当在某个时刻,同时多个异常或中断出现时,CPU将会 按照上述的优先级来处理。
linux 中断与异常---mips基础(一)
linux 中断与异常---mips基础(一)

linux 中断与异常---mips基础(一)



前面一列为exception的编号,后面一列为改异常的描述


  • 异常相关寄存器

linux 中断与异常---mips基础(一)

The BadVAddr register
This register (its name stands for Bad Virtual Address) will contain the memory address where the exception
has occurred. An unaligned memory access, for instance, will generate an exception and the address where
the access was attempted will be stored in BadVAddr.

SR(Status Register,状态寄存器)
EXL
Exception Level; set by the processor when any exception other than Reset, Soft Reset, NMI, or Cache Error exception are taken. 0: normal 1: exception
当EXL被置位时,
- 中断是被禁止的。 换句话说,这时SR[IE]位是不管用了,相当于所有的中断都被屏蔽
了。
- TLB Refill异常将会使用General Exception Vector而不是缺省的TLB Refill Vector.
- 如果再次发生异常,EPC将不会被自动更新。这一点要非常注意。如果想支持嵌套异 常,要在异常处理例程中清EXL位。当然要先保存EPC的值。另外要注意的:MIPS当陷 入Exception/Interrupt时,并不改变SR[UX],SR[KX]或SR[SX]的值。SR[EXL]为1自动的 将CPU mode运行在核心模式下。这一点要注意。

ERL
Error Level; set by the processor when Reset, Soft Reset, NMI, or Cache Error exception are taken. 0: normal 1: error
当ERL被置位时,
- 中断被禁止。
- 中断返回ERET使用的是ErrorEPC而不是EPC。需要非常注意这个区别。
- Kuseg和xkuseg 被认为是没有映射(Mapped)的和没有缓存(Un-Cached)。可以这样理
解,MIPS CPU只有在这个时刻才是一种实模式(real mode),可以不需要TLB的映射, 就直接使用kuseg的地址空间。
The ERET instruction to return from exception is used for returning from
exception level (Status.EXL) and error level (Status.ERL). If both bits
are set however we should be returning from ERL first, as ERL can
interrupt EXL, for example when an NMI is taken.
都是通过eret返回的,如果EXL和ERL同时设置了,则应该首先从ERL返回,PC设置为ErrorPC,清除ERL,注意这时不会清除EXL

ERET指令用模拟器实现的代码大致如下:
if (kvm_read_c0_guest_status(cop0) & ST0_ERL) {
      kvm_clear_c0_guest_status(cop0, ST0_ERL);
      vcpu->arch.pc = kvm_read_c0_guest_errorepc(cop0);
  } 
else if (kvm_read_c0_guest_status(cop0) & ST0_EXL) {
     kvm_clear_c0_guest_status(cop0, ST0_EXL);
     vcpu->arch.pc = kvm_read_c0_guest_epc(cop0);
}

IE
Interrupt Enable 0: disable interrupts 1: enable interrupts。请记住: 当SR[EXL]或SR[ERL]被SET时, SR[IE]是无效的。
BEV
 Normal/Bootstrap exception vectors location
SR
Soft Reset,如果是soft reset,该位置1,表明是软件复位
NMI
如果是NMI,该位置1,表明是不可屏蔽中断
IM[7:0]
Interrupt Mask
UM
Kernel/User Mode, UM=1用户模式,中断发生时不改变该bit的值
UM:ERL:EXL Mode
100: User
000: Kernel
-10: Kernel (exception handling)
-01: Kernel (error handling)

Cause
linux 中断与异常---mips基础(一)
BD: Exception happened in a branch delay slot
IV: Use general vs special interrupt vector
IP[7:0]: Interrupt(s) pending
Exc Code: Exception code
linux 中断与异常---mips基础(一)

EPC
存放返回地址


  • 常见的异常处理

Reset Exception
A reset exception occurs when the SI_ColdReset signal is asserted to the processor. This exception is not maskable.
When a Reset exception occurs, the processor performs a full reset initialization, including aborting state machines,
establishing critical state, and generally placing the processor in a state in which it can execute instructions from
uncached, unmapped address space. On a Reset exception, the state of the processor in not defined, with the following
exceptions:
• The Random register is initialized to the number of TLB entries - 1 (4Kc core).
• The Wired register is initialized to zero (4Kc core).
• The Config register is initialized with its boot state.
• The RP, BEV, TS, SR, NMI, and ERL fields of the Status register are initialized to a specified state.
• The I, R, and W fields of the WatchLo register are initialized to 0.
• The ErrorEPC register is loaded with PC-4 if the state of the processor indicates that it was executing an instruction
in the delay slot of a branch. Otherwise, the ErrorEPC register is loaded with PC. Note that this value may or may
not be predictable.
• PC is loaded with 0xBFC0_0000.
Cause Register ExcCode Value:
None
Additional State Saved:
None
Entry Vector Used:
Reset (0xBFC0_0000)
Operation:
Random <- TLBEntries - 1
Wired <- 0
Config <- ConfigurationState
StatusRP <- 0
StatusBEV <- 1
StatusTS <- 0
StatusSR <- 0
StatusNMI <- 0
StatusERL <- 1
WatchLoI <- 0
WatchLoR <- 0
WatchLoW <- 0
if InstructionInBranchDelaySlot then
ErrorEPC <- PC - 4
else
ErrorEPC <- PC
endif
PC <- 0xBFC0_0000

Soft Reset Exception
A soft reset exception occurs when the SI_Reset signal is asserted to the processor. This exception is not maskable. When
a soft reset exception occurs, the processor performs a subset of the full reset initialization. Although a soft reset
exception does not unnecessarily change the state of the processor, it may be forced to do so in order to place the
processor in a state in which it can execute instructions from uncached, unmapped address space. Since bus, cache, or
other operations may be interrupted, portions of the cache, memory, or other processor state may be inconsistent. In
addition to any hardware initialization required, the following state is established on a soft reset exception:
• The BEV, TS, SR, NMI, and ERL fields of the Status register are initialized to a specified state.
• The ErrorEPC register is loaded with PC-4 if the state of the processor indicates that it was executing an instruction
in the delay slot of a branch. Otherwise, the ErrorEPC register is loaded with PC. Note that this value may or may
not be predictable.
• PC is loaded with 0xBFC0_0000.
Cause Register ExcCode Value:
None
Additional State Saved:
None
Entry Vector Used:
Reset (0xBFC0_0000)
Operation:
StatusBEV <- 1
StatusTS <- 0
StatusSR <- 1
StatusNMI <- 0
StatusERL <- 1
if InstructionInBranchDelaySlot then
ErrorEPC <- PC - 4
else
ErrorEPC <- PC
endif
PC <- 0xBFC0_0000

Non-Maskable Interrupt (NMI) Exception
A non-maskable interrupt exception occurs when the SI_NMI signal is asserted to the processor. SI_NMI is an edge
sensitive signal - only one NMI exception will be taken each time it is asserted. An NMI exception occurs only at
instruction boundaries, so it does not cause any reset or other hardware initialization. The state of the cache, memory,
and other processor states are consistent and all registers are preserved, with the following exceptions:
• The BEV, TS, SR, NMI, and ERL fields of the Status register are initialized to a specified state.
• The ErrorEPC register is loaded with PC-4 if the state of the processor indicates that it was executing an instruction
in the delay slot of a branch. Otherwise, the ErrorEPC register is loaded with PC.
• PC is loaded with 0xBFC0_0000.
Cause Register ExcCode Value:
None
Additional State Saved:
None
Entry Vector Used:
Reset (0xBFC0_0000)
Operation:
StatusBEV <- 1
StatusTS <- 0
StatusSR <- 0
StatusNMI <- 1
StatusERL <- 1
if InstructionInBranchDelaySlot then
ErrorEPC <- PC - 4
else
ErrorEPC <- PC
endif
PC <- 0xBFC0_0000

Machine Check Exception (4Kc core)
A machine check exception occurs when the processor detects an internal inconsistency. The following condition causes
a machine check exception;
• The detection of multiple matching entries in the TLB in a TLB-based MMU. The core detects this condition on a
TLB write and prevents the write from being completed. The TS bit in the Status register is set to indicate this
condition. This bit is only a status flag and does not affect the operation of the device. Software clears this bit at the
appropriate time. This condition is resolved by flushing the conflicting TLB entries. The TLB write can then be
completed.
Cause Register ExcCode Value:
MCheck
Additional State Saved:
None
Entry Vector Used:
General exception vector (offset 0x180)

Interrupt Exception
外部中断。它是唯一一个异步发生的异常。之所以说中断是异步发生的,是因为相对于其他异常来说,从时序上看,中断的发生是不可预料的,无法确定中断的发生是在流水线的哪一个阶段。MIPS的五级流水线设计如下:
IF, RD, ALU, MEM, WB。MIPS处理器的中断控制部分有这样的设计:在中断发生时,如果该指令已经完成了MEM阶段的操作,则保证该指令执行完毕。反之,则丢弃流水线对这条指令的工作。除NMI外,所有的内部或外部硬件中断(Hardware Interrupt)均共用这一个异常向量(Exception Vector)。前面提到的CP0中的Counter/Compare这一对计数寄存器,当Counter计数值和Compare门限值相等时,即触发一个硬件中断。
The interrupt exception occurs when one or more of the eight interrupt requests is enabled by the Status register and the
interrupt input is asserted. The delay from assertion of an unmasked interrupt to fetch of the first instructions at the
exception vector is a minimum of 5 clock cycles. More may be needed if a committed instruction has to complete before
the exception can be taken. A SYNC instruction which has already started flushing the cache and write buffers must wait
until this is completed before the interrupt exception can be taken.
Register ExcCode Value:
Int
Additional State Saved:
Entry Vector Used:
General exception vector (offset 0x180) if the IV bit in the Cause register is 0;
interrupt vector (offset 0x200) if the IV bit in the Cause register is 1.

TLB Refill Exception — Instruction Fetch or Data Access (4Kc core)
TLB Miss Load/Write,如果试图访问没有在MMU的TLB中映射的内存地址,会触发这个异常。在支持虚拟内存的操作系统中,这会触发内存的页面倒换,系统的Exception Handler会将所需要的内存页从虚拟内存中调入物理内存,并更新相应的TLB表项。
During an instruction fetch or data access, a TLB refill exception occurs when no TLB entry in a TLB-based MMU
matches a reference to a mapped address space and the EXL bit is 0 in the Status register. Note that this is distinct from
the case in which an entry matches but has the valid bit off. In that case, a TLB Invalid exception occurs.
Cause Register ExcCode Value:
TLBL: Reference was a load or an instruction fetch
TLBS: Reference was a store
Additional State Saved:
linux 中断与异常---mips基础(一)
Entry Vector Used:
TLB refill vector (offset 0x000) if StatusEXL = 0 at the time of exception;
general exception vector (offset 0x180) if StatusEXL = 1 at the time of exception

TLB Invalid Exception — Instruction Fetch or Data Access (4Kc core)
During an instruction fetch or data access, a TLB invalid exception occurs in one of the following cases:
 No TLB entry in a TLB-based MMU matches a reference to a mapped address space; and the EXL bit is 1 in the
Status register.
• A TLB entry in a TLB-based MMU matches a reference to a mapped address space, but the matched entry has the
valid bit off.
Cause Register ExcCode Value:
TLBL: Reference was a load or an instruction fetch
TLBS: Reference was a store
Additional State Saved:
linux 中断与异常---mips基础(一)
Entry Vector Used:
General exception vector (offset 0x180)

TLB Modified Exception — Data Access (4Kc core)
内存修改异常。如果一块内存在TLB映射时,其属性设定为Read Only,那么,在试图修改这块内存内容时,处理器就会进入这个异常。显然,这个异常是在Memory阶段发生的。但是,按“精确异常”的原则,在异常发生时,ALU阶段的操作均无效,也就是说,向内存地址中的写入操作,实际上是不会被真正执行的。这一判断原则,也适用于后面的内存读写相关的异常,包括TLB Miss/Address Error/Watch等。
During a data access, a TLB modified exception occurs on a store reference to a mapped address if the following
condition is true:
• The matching TLB entry in a TLB-based MMU is valid, but not dirty.
Cause Register ExcCode Value:
Mod
Additional State Saved:
linux 中断与异常---mips基础(一)

Entry Vector Used:
General exception vector (offset 0x180)


Address Error Exception — Instruction Fetch/Data Access
如果试图访问一个非对齐的地址,例如lw/sw指令的地址非4字节对齐,或lh/sh的地址非2字节对齐,就会触发这个异常。一般地,操作系统在Exception Handler中对这个异常的处理,是分开两次读取/写入这个地址。虽然一般的操作系统内核都处理了这个异常,最后能够完成期待的操作,但是由于会引起用户态到内核态的切换,以及异常的退出,当这样非对齐操作较多时会严重影响程序的运行效率。因此,编译器在定义局部和全局变量时,都会自动考虑到对齐的情况,而程序员在设计数据结构时,则需要对对齐做特别的斟酌。
An address error exception occurs on an instruction or data access when an attempt is made to execute one of the
following:
• Fetch an instruction, load a word, or store a word that is not aligned on a word boundary
• Load or store a halfword that is not aligned on a halfword boundary
• Reference the kernel address space from user mode
Note that in the case of an instruction fetch that is not aligned on a word boundary, PC is updated before the condition
is detected. Therefore, both EPC and BadVAddr point to the unaligned instruction address. In the case of a data access
the exception is taken if either an unaligned address or an address that was inaccessible in the current processor mode
was referenced by a load or store instruction.

Cause Register ExcCode Value:
ADEL: Reference was a load or an instruction fetch
ADES: Reference was a store
Additional State Saved:
linux 中断与异常---mips基础(一)
Entry Vector Used:
General exception vector (offset 0x180)


Bus Error Exception — Instruction Fetch or Data Access
一般地原因是Cache尚未初始化的时候访问了Cached的内存空间所致。因此,要注意在系统上电后,Cache初始化之前,只访问Uncached的地址空间,也就是0xA0000000-0xBFFFFFFF这一段。默认地,上电初始化的入口点0xBFC00000就位于这一段。(某些MIPS实现中可以通过外部硬线连接修改入口点地址,但为了不引发无法预料的问题,不要将入口点地址修改为Uncached段以外的地址)
A bus error exception occurs when an instruction or data access makes a bus request (due to a cache miss or an
uncacheable reference) and that request terminates in an error. The bus error exception can occur on either an instruction
fetch or a data access. Bus error exceptions that occur on an instruction fetch have a higher priority than bus error
exceptions that occur on a data access.
Bus errors taken on the requested (critical) word of an instruction fetch or data load are precise. Other bus errors, such
as stores or non-critical words of a burst read, can be imprecise. These errors are taken when the EB_RBErr or
EB_WBErr signals are asserted and may occur on an instruction that was not the source of the offending bus cycle.
Cause Register ExcCode Value:
IBE: Error on an instruction reference
DBE: Error on a data reference
Additional State Saved:
None
Entry Vector Used:
General exception vector (offset 0x180)


  • 详细异常处理流程图
linux 中断与异常---mips基础(一)
linux 中断与异常---mips基础(一)

linux 中断与异常---mips基础(一)


linux 中断与异常---mips基础(一)

linux 中断与异常---mips基础(一)



  • 异常的嵌套 
在有的情况下,希望在异常或中断中,系统可以继续处理其他的异常或中断。 这需要系统软件处理如下事情:
*进入处理程序后,保存Context, EPC, status, cause等寄存器的值到内存栈中,然后设置UM=0,设置CPU模式为核心态,(异常并不会改status中UM的值,EXL=1, ERL=1,UM=0,任意一个条件成立都是内核态,为何要设置为内核态呢,因为只有在内核态才能访问cp0的特权资源),然后清除SR[EXL],从而支持EPC会被 更新,从而支持嵌套处理。但是当还没来得及清除SR[EXL],另外一个异常立即就来了怎么办呢(中断不可能来,只可能是执行异常),那么所有寄存器的值都不会更新,直接跳转到异常向量处开始执行
在任何情况下,reset, softreset,NMI,都会无条件响应,并且设置ERL=1,也就是说错误处理可以无条件相应,当然错误处理也是可以嵌套的。
在ERL=1时,禁止任何中断和异常,除了reset, softreset,NMI
*SR[IE]是一个很重要的位来处理嵌套异常。值得注意的,或容易犯错的一点是:
在做恢复上下文时,要避免重入问题。比如,要用eret返回时, 要建立EPC的值。在此之 前,一定要先关闭中断disable interrupt. 否 则,EPC可能被冲掉。
下面是一段异常中断返回的例子代码:
/* 读取SR的当前值*/
mfc0 t0,C0[SR]
/*加一个delay slot指令 */
nop
/* 清楚SR[IE],关闭中断 */
li t1,~SR[IE]
and t0,t0,t1
mtc0 t0,C0[SR]
nop
/* 可以安全的恢复EPC的值*/
ld t1,R_EPC(sp)
mtc0 t1,C0[EPC]
nop
lhu k1, /* 恢复老的中断屏蔽码,被暂时保留在k1里*/
or t0,t0,k1
/*从新对SR[EXL]置位。ERET会自动将其清除。一定要理解 ,为什么中断例程要在前面 要清除 EXL。如果不的话。就不能支持嵌套异常。为什么,希望读者能思考并回答。并 且,在清EXL之前,我们一定要先把CPU模式变为核心模式。*/
ori t0,t0,SR[EXL]
/*一切就绪,恢复中断屏蔽码和对EXL置位*/
mtc0 t0,C0[SR]
nop
ori t0,t0,SR[IE]
/* 置为IE */
ori t0,t0,SR[IMASK7 ]
mtc0 t0,C0[SR ]
nop
/*恢复CPU模式 */
ori t0, t0,SR[USERMODE]
mtc0, t0, C0[SR ]
eret
/*eret将对EXL清零。所以要注意,如果你在处理程序中改变了CPU的模式,一定要确
保,在重新设置EXL位后,恢复CPU的原来模式,否则用户进程将会在核心态下运行。


  • 代码实例分析
cfe中的异常处理
fsbl部分:
LEAF(do_chip_init)
    move    s0, ra
    li    t0, INITIAL_SR               //#define INITIAL_SR      ((/*CP0_STATUS_SR_MASK |*/ CP0_STATUS_CU1_MASK | CP0_STATUS_BEV_MASK | CP0_STATUS_IE_MASK) & ~( CP0_STATUS_ERL_MASK | CP0_STATUS_EXL_MASK))       

    mtc0  t0, CP0_STATUS
    nop
    nop
    mtc0  zero, CP0_CAUSE           # clear software interrupts
    nop
    nop

在这里将ERL和EXL都清除掉了,并且使能了中断
#define        CP0_STATUS_KSU_MASK                            _MM_MAKEMASK(2,3)
#define        CP0_STATUS_KSU_SHIFT                        (3)
复位后,处在内核态,并且cfe中一直处在内核态中,没有发生cpu模块的转换
ssbl部分:
void cfe_main(int a,int b)
{
    /*
     * Set up the exception vectors
     */
    cfe_setup_exceptions();
}
void cfe_setup_exceptions(void)
{
    _exc_setvector(XTYPE_TLBFILL,  (void *) cfe_exception);
    _exc_setvector(XTYPE_XTLBFILL, (void *) cfe_exception);
    _exc_setvector(XTYPE_CACHEERR, (void *) _exc_cache_crash_sim);
    _exc_setvector(XTYPE_EXCEPTION,(void *) cfe_exception);
    _exc_setvector(XTYPE_INTERRUPT,(void *) cfe_exception);
    _exc_setvector(XTYPE_EJTAG,    (void *) cfe_exception);
    exc_handler.catch_exc = 0;
    q_init( &(exc_handler.jmpbuf_stack));
#if (!CFG_BOOTRAM) && (CFG_RUNFROMKSEG0)
    /*
     * Install RAM vectors, and clear the BEV bit in the status
     * register.  Don't do this if we're running from PromICE RAM
     */
    exc_install_ram_vectors();
#endif
}
#define XTYPE_RESET    0
#define XTYPE_TLBFILL    8
#define XTYPE_XTLBFILL    16
#define XTYPE_CACHEERR    24
#define XTYPE_EXCEPTION    32
#define XTYPE_INTERRUPT    40
#define XTYPE_EJTAG    48

        .globl    _exc_vectab
_exc_vectab:    _LONG_    0        # XTYPE_RESET
        _LONG_    0        # XTYPE_TLBFILL  (not used)        
        _LONG_    0        # XTYPE_XTLBFILL
        _LONG_    0        # XTYPE_CACHEERR (not used)
        _LONG_    0        # XTYPE_EXCEPTION
        _LONG_    0        # XTYPE_INTERRUPT
        _LONG_    0        # XTYPE_EJTAG
LEAF(_exc_setvector)

        la    v0,_exc_vectab
        srl    a0,3        /* convert 8-byte index to array index */
        sll    a0,BPWSIZE    /* convert back to index appropriate for word size */
        add    v0,a0
        SR    a1,(v0)
        j    ra

END(_exc_setvector)

void cfe_exception(int code,uint64_t *info)
{
    int idx;
    if(exc_handler.catch_exc == 1) {      //允许异常处理被捕获
    /*Deal with exception without restarting CFE.*/
    
    /*Clear relevant SR bits*/
    _exc_clear_sr_exl();
    _exc_clear_sr_erl();
    
    /*Reset flag*/
    exc_handler.catch_exc = 0;
    exc_longjmp_handler();       
      }
    //仅仅打印异常引起异常的原因和信息
    xprintf("**Exception %d: EPC=%08X, Cause=%08X, VAddr=%08X\n",
        code,(uint32_t)info[XCP0_EPC],
        (uint32_t)info[XCP0_CAUSE],(uint32_t)info[XCP0_VADDR]);
    xprintf("                RA=%08X, PRID=%08X\n",
        (uint32_t)info[XGR_RA],(uint32_t)info[XCP0_PRID]);
    xprintf("\n");
    for (idx = 0;idx < 32; idx+= 2) {
    xprintf("        %2s ($%2d) = %08X     %2s ($%2d) = %08X\n",
        regnames+(idx*2),
        idx,(uint32_t)info[XGR_ZERO+idx],
        regnames+((idx+1)*2),
        idx+1,(uint32_t)info[XGR_ZERO+idx+1]);
    }

    xprintf("\n");
    xprintf("\n*** Waiting for system reset ***\n");    //直接挂机
    while(1)
        ;
}
将异常向量保存在_exc_vectab这个表中,那么这个表由谁去调用呢?
LEAF(_exc_entry)

        .set noreorder
        .set noat

        subu    k1,sp,EXCEPTION_SIZE
        SRL    k1,3
        SLL    k1,3

        SREG    zero,XGR_ZERO(k1)                //保存现场  #define SREG     sw
        SREG     AT,XGR_AT(k1)

        SREG    v0,XGR_V0(k1)
        SREG    v1,XGR_V1(k1)

        SREG    a0,XGR_A0(k1)
        SREG    a1,XGR_A1(k1)
        SREG    a2,XGR_A2(k1)
        SREG    a3,XGR_A3(k1)

        SREG    t0,XGR_T0(k1)
        SREG    t1,XGR_T1(k1)
        SREG    t2,XGR_T2(k1)
        SREG    t3,XGR_T3(k1)
        SREG    t4,XGR_T4(k1)
        SREG    t5,XGR_T5(k1)
        SREG    t6,XGR_T6(k1)
        SREG    t7,XGR_T7(k1)

        SREG    s0,XGR_S0(k1)
        SREG    s1,XGR_S1(k1)
        SREG    s2,XGR_S2(k1)
        SREG    s3,XGR_S3(k1)
        SREG    s4,XGR_S4(k1)
        SREG    s5,XGR_S5(k1)
        SREG    s6,XGR_S6(k1)
        SREG    s7,XGR_S7(k1)

        SREG    t8,XGR_T8(k1)
        SREG    t9,XGR_T9(k1)

        SREG    gp,XGR_GP(k1)
        SREG    sp,XGR_SP(k1)
        SREG    fp,XGR_FP(k1)
        SREG    ra,XGR_RA(k1)

        mfc0    t0,C0_CAUSE
        mfc0    t1,C0_SR
        mfc0    t2,C0_BADVADDR
        mfc0    t3,C0_EPC
        mfc0    t4,C0_PRID
        mflo    t5
        mfhi    t6    
        SREG    t0,XCP0_CAUSE(k1)
        SREG    t1,XCP0_SR(k1)
        SREG    t2,XCP0_VADDR(k1)
        SREG    t3,XCP0_EPC(k1)
        SREG    t4,XCP0_PRID(k1)
        SREG    t5,XGR_LO(k1)
        SREG    t6,XGR_HI(k1)

#if CFG_EMBEDDED_PIC
        la        gp,PHYS_TO_K0(CFE_LOCORE_GLOBAL_GP)
        LR        gp,0(gp)        # get our GP handle from low memory vector
#else
        la        gp,_gp            # Load up GP, not relocated so it's easy
#endif

        /* Exception occurred in CFE */
        move    a0,k0            # Pass exception type
        move    a1,k1            # Pass frame to exception handler
        la        t0,_exc_vectab        # get base of exception vectors
        srl        k0,3            # convert 8-byte index to array index
        sll        k0,BPWSIZE        # convert back to index appropriate for word size
        addu    t0,k0            # get vector address
        LR        t0,(t0)            # to call handler

        move    sp,k1            # "C" gets fresh stack area
        jalr    t0            # Call exception handler
        nop

        move    k1, sp
        LREG      AT,XGR_AT(k1)

        LREG    t0,XGR_LO(k1)
        LREG    t1,XGR_HI(k1)
        mtlo    t0
        mthi    t1

        LREG    a0,XGR_A0(k1)
        LREG    a1,XGR_A1(k1)
        LREG    a2,XGR_A2(k1)
        LREG    a3,XGR_A3(k1)

        LREG    t0,XGR_T0(k1)
        LREG    t1,XGR_T1(k1)
        LREG    t2,XGR_T2(k1)
        LREG    t3,XGR_T3(k1)
        LREG    t4,XGR_T4(k1)
        LREG    t5,XGR_T5(k1)
        LREG    t6,XGR_T6(k1)
        LREG    t7,XGR_T7(k1)

        LREG    s0,XGR_S0(k1)
        LREG    s1,XGR_S1(k1)
        LREG    s2,XGR_S2(k1)
        LREG    s3,XGR_S3(k1)
        LREG    s4,XGR_S4(k1)
        LREG    s5,XGR_S5(k1)
        LREG    s6,XGR_S6(k1)
        LREG    s7,XGR_S7(k1)

        LREG    t8,XGR_T8(k1)
        LREG    t9,XGR_T9(k1)

        LREG    gp,XGR_GP(k1)
        LREG    sp,XGR_SP(k1)
        LREG    fp,XGR_FP(k1)
        LREG    ra,XGR_RA(k1)

/* do any CP0 cleanup here */

        LREG    v0,XGR_V0(k1)
        LREG    v1,XGR_V1(k1)
    
        ERET

        .set at
        .set reorder

END(_exc_entry)
那么_exc_entry又是怎么调用的呢?
static void exc_install_ram_vectors(void)
{
    uint32_t *ptr;
    int idx;
    /* Debug: blow away the vector area so we can see what we did */
    ptr = (uint32_t *) PHYS_TO_K0(0);
    for (idx = 0; idx < 0x1000/sizeof(uint32_t); idx++) *ptr++ = 0;
    /*
     * Set up the vectors.  The cache error handler is set up
     * specially.
     */
    exc_setup_hw_vector(MIPS_RAM_VEC_TLBFILL,  CPUCFG_TLBHANDLER,XTYPE_TLBFILL);
    exc_setup_hw_vector(MIPS_RAM_VEC_XTLBFILL, _exc_entry,XTYPE_XTLBFILL);
    exc_setup_hw_vector(MIPS_RAM_VEC_CACHEERR, _exc_entry,XTYPE_CACHEERR);
    exc_setup_hw_vector(MIPS_RAM_VEC_EXCEPTION,_exc_entry,XTYPE_EXCEPTION);
    exc_setup_hw_vector(MIPS_RAM_VEC_INTERRUPT,_exc_entry,XTYPE_INTERRUPT);
    /*
     * Flush the D-cache and invalidate the I-cache so we can start
     * using these vectors.
     */
    cfe_flushcache(CFE_CACHE_FLUSH_D | CFE_CACHE_INVAL_I,0,0);
    /*
     * Write the handle into our low memory space.  If we need to save
     * other stuff down there, this is a good place to do it.
     * This call uses uncached writes - we have not touched the
     * memory in the handlers just yet, so they should not be
     * in our caches.
     */
    _exc_setup_locore((intptr_t) CPUCFG_CERRHANDLER);  //重新设置cache 错误异常处理向量
    /*
     * Finally, clear BEV so we'll use the vectors in RAM.
     */
    _setstatus(_getstatus() & ~M_SR_BEV);
    /*
     * XXX There's a hazard here, but we're not going to worry about
     * XXX it.  It is unlikely we'll use the vectors any time soon.
     */
}
清掉BEV标志,因此除了reset, soft reset,NMI外,所有其他的中断向量都在RAM中0X8000_0000开始处
#define MIPS_ROM_VEC_RESET    0x0000
#define MIPS_ROM_VEC_TLBFILL    0x0200
#define MIPS_ROM_VEC_XTLBFILL    0x0280
#define MIPS_ROM_VEC_CACHEERR    0x0300
#define MIPS_ROM_VEC_EXCEPTION    0x0380
#define MIPS_ROM_VEC_INTERRUPT    0x0400
#define MIPS_ROM_VEC_EJTAG    0x0480

#define MIPS_RAM_VEC_TLBFILL    0x0000                       //EXL=0时TLB异常入口
#define MIPS_RAM_VEC_XTLBFILL    0x0080                      //EXL=1时TLB异常入口
#define MIPS_RAM_VEC_CACHEERR    0x0100                  //cache 错误异常入口
#define MIPS_RAM_VEC_EXCEPTION    0x0180                  //通用异常入口
#define MIPS_RAM_VEC_INTERRUPT    0x0200                  //中断入口
#define MIPS_RAM_VEC_END    0x0300


#define CPUCFG_TLBHANDLER        bcmcore_tlbhandler
TLB异常为何要在分EXL处理呢,主要是为了处理更高效,因为EXL=0时,TLB发生异常的频率很高,所以单独搞个专用的TLB异常向量,可以极大的提升系统性能,因为通用异常处理向量要根据异常码分开处理,效率不高


 _exc_setup_locore((intptr_t) CPUCFG_CERRHANDLER);  //重新设置cache 错误异常处理向量,这个函数
        li    t0,PHYS_TO_K1(CFE_LOCORE_GLOBAL_CERRH)
        SR    a0,0(t0)
把正常的异常处理向量保存在CFE_LOCORE_GLOBAL_CERRH内存中,然后
        li    t0,PHYS_TO_K1(MIPS_RAM_VEC_CACHEERR)

        LOADREL(t1,_exc_cerr_htable)
        LR    t2,R_EXC_CERR_TEMPLATE_END(t1)
        LR    t1,R_EXC_CERR_TEMPLATE(t1)
_exc_cerr_htable中存放的异常处理函数拷贝覆盖到    exc_setup_hw_vector(MIPS_RAM_VEC_CACHEERR, _exc_entry,XTYPE_CACHEERR);已经安装的内存中,也就是0x0100的内存中
_exc_cerr_htable:
        _LONG_    _exc_cerr_template
        _LONG_    _exc_cerr_template_end

LEAF(_exc_cerr_template)
        LR    k0,CFE_LOCORE_GLOBAL_CERRH(zero)
        jal    k0
         nop
取出保存的异常向量地址
/*
* Temporary until all our CPU packages support a cache error handler
*/
#ifndef CPUCFG_CERRHANDLER
#define CPUCFG_CERRHANDLER 0xBFC00000
#else
extern void CPUCFG_CERRHANDLER(void);
#endif
这里实际上是没有定义CPUCFG_CERRHANDLER的,因此在cfe中cache错误将导致直接重启

static void exc_setup_hw_vector(uint32_t vecoffset,
                  void *target,
                  uint32_t k0code)
{
    uint32_t *vec;
    uint32_t new;
    uint32_t lower,upper;
    new = (uint32_t) (intptr_t) target;    /* warning: assumes compatibility addresses! */
    lower = new & 0xffff;
    upper = (new >> 16) & 0xffff;
    if ((lower & 0x8000) != 0) {
    upper++;
    }
    /*
     * Get a KSEG0 version of the vector offset.
     */
    vec = (uint32_t *) PHYS_TO_K0(vecoffset);
    /*
     * Patch in the vector.  Note that we have to flush
     * the L1 Dcache and invalidate the L1 Icache before
     * we can use this.  
     */
    vec[0] = 0x3c1b0000 | upper;   /* lui   k1, HIGH(new)     */
    vec[1] = 0x277b0000 | lower;   /* addiu k1, k1, LOW(new)  */
    vec[2] = 0x03600008;           /* jr    k1                */
    vec[3] = 0x241a0000 | k0code;  /*  li   k0, code          */
}


kernel中的异常处理
* 处理程序什么时候安装? 
traps_init(arch/mips/kernel/traps.c,setup_arch之后start_kernel调用)
/* Copy the generic exception handler code to it's final destination. */
memcpy((void *)(KSEG0 + 0x80), &except_vec1_generic, 0x80);
memcpy((void *)(KSEG0 + 0x100), &except_vec2_generic, 0x80);
memcpy((void *)(KSEG0 + 0x180), &except_vec3_generic, 0x80);
flush_icache_range(KSEG0 + 0x80, KSEG0 + 0x200);
/*
* Setup default vectors
*/
for (i = 0; i <= 31; i++)
set_except_vector(i, handle_reserved);
* 装的什么? 
except_vec3_generic(head.S) (除了TLB refill例外都用这个入口): /* General exception vector R4000 version. */
NESTED(except_vec3_r4000, 0, sp)
.set noat
mfc0 k1, CP_CAUSE
andi k1, k1, 0x7c /* 从cause寄存器取出异常号 */
li k0, 31<<2 beq k1, k0, handle_vced /* 如果是vced,处理之*/
li k0, 14><<2 beq k1, k0, handle_vcei /* 如果是vcei,处理之*/
/* 这两个异常是和cache相关的,cache出了问题,不能再在这个cached的位置处理啦 */
la k0, exception_handlers /* 取出异常处理程序表 */
addu k0, k0, k1 lw k0, (k0) /*处理函数*/
nop jr k0 /*运行异常处理函数*/
nop
那个异常处理程序表是如何初始化的呢?
在traps_init中,大家会看到set_exception_vector(i,handler)这样的代码, 填的就是这张表啦.可是,如果你用souce insigh之类的东西去找那个handler,往往就落空了,??怎么没有handle_ri,handle_tlbl..._?不着急,只不过是一个小trick, 还记得x86中断处理的handler代码吗? 它们是用宏生成的:
entry.S
#define BUILD_HANDLER(exception,handler,clear,verbose)
.align 5;
NESTED(handle_##exception, PT_SIZE, sp);
.set noat;
SAVE_ALL; /* 保存现场,切换栈(如必要)*/
__BUILD_clear_##clear(exception); /*关中断?*/
.set at;
__BUILD_##verbose(exception);
jal do_##handler; /*干活*/
move a0, sp;
ret_from_exception; /*回去*/
nop;
END(handle_##exception) /*生成处理函数*/
BUILD_HANDLER(adel,ade,ade,silent) /* #4 */
BUILD_HANDLER(ades,ade,ade,silent) /* #5 */
BUILD_HANDLER(ibe,ibe,cli,verbose) /* #6 */
BUILD_HANDLER(dbe,dbe,cli,silent) /* #7 */
BUILD_HANDLER(bp,bp,sti,silent) /* #9 */
认真追究下去,这里的一些宏是很重要的,象SAVE_ALL(include/asm/stackframe.h), 异常处理要高效,正确,这里要非常小心.这是因为硬件做的事情实在太少了.