mips下的异常、中断

版权声明:本文为博主原创文章,未经博主允许不得转载。博客搬迁至https://winddoing.github.io https://blog.csdn.net/u011037149/article/details/50831428

什么是异常

在mips中,中断、陷阱、系统调用和任何可以中断程序正常执行流的情况都称异常

1. 外部事件  ——中断

2. 内存翻译异常

3. 其他不太常见的内核修改的程序条件

4. 程序或硬件探测到的错误

5. 数据完整性错误

6. 系统调用和陷入

精确异常

在运行流程中没有任何多余效应的异常。即当异常发生时,在受害指令之前的指令被完全执行,而受害指令及后面的指令还没开始执行(注:说受害指令及后面的指令还没做任何事情是不对的,实际上受害指令是处于其指令周期的第三阶段刚完成,即ALU阶段刚完成)。精确异常有有助于保证软件设计上不受硬件实现的影响。

CP0中的EPC寄存器用于指向异常发生时指令跳转前的执行位置,一般是受害指令地址。当异常时,是返回这个地址继续执行。但如果受害指令在分支延迟槽中,则会硬件自动处理使EPC往回指一条指令,即分支指令。在重新执行分支指令时,分支延迟槽中的指令会被再执行一次。

精确异常的实现对流水线的流畅性是有一定的影响的,如果异常太多,系统执行效率就会受到影响。

异常

常规异常一般为软件的异常,而中断一般为硬件异常,中断可以是芯片内部,也可以是芯片外部触发产生。

异常发生时,跳转前最后被执行的指令是其MEM阶段刚好被执行完的那条指令。受害指令是其ALU阶段刚好执行完的那条指令。

Ø 用户特权地址的TLB重填

TLB硬件上只存在存储一定数目的地址转换条目,在运行一个虚拟内存的OS的系统中,如果如果程序得以充分的运行,应用程序就会很容易碰到一个虚拟地址在TLB中没有的情况,一个TLB不匹配的事件就发生了。异常产生后硬件帮助异常处理程序在大约13个时钟周期内完成TLB重填。

Ø 64位地址空间的TLB重填

为了充分利用64位CPU的地址空间,地址转换用了一套略为不同的寄存器布局和不同的TLB重填例程,叫做XTLB重填。

Ø 非缓存的异常入口点

出于对异常处理性能的考虑,访问中断入口地址时都要经过缓存,但是在系统启动期间,上电或重启时缓存未经初始化不能使用。因此,在早期启动期间访问不需要经过缓存,CP0寄存器中有个模式位SR(BEV)将异常处理入口定位于非缓存的、启动安全的kseg1内存区域。

Ø 奇偶/ECC校验错误

Mips32cpu可以检查到数据错误,这是不管SR(BEV)的状态,缓存错误的异常入口都在非缓存区域kseg1内存区域。

Ø 重启

MIPS系统把重启看作一个不可回归的异常来处理。
          冷启动:CPU硬件完全被重新配置,软件重新加载;
           热启动:软件完全重新初始化;

Ø 中断

异常向量

所有的异常入口点都位于MIPS内存映射中不需要地址转换的区域——非缓存的kseg1段和缓存的kseg0段。当SR(BEV)置位时,非缓存的异常入口时固定的,但是当SR(BEV)清零时,EBase寄存器可以通过编程一起移动所有的异常入口到其他地址。

高优先级异常有:冷启动、热重启、非屏蔽中断,

TLB 重填(32 位模式),

xTLB 重填(64 位模式),cache 错误,其他异常。

高优先级异常入口地址有以下五个:

优先级异常入口

异常类型 

正常运行(BEV 为 0)

 启动(BEV 为 1)

冷启动、热重启、非屏蔽中断

0x BFC0 0000

0x BFC0 0000

TLB 重填

0x 8000 0000

0x BFC0 0200

xTLB 重填 

0x 8000 0080

0x BFC0 0280

cache 错误

0x A000 0100

0x BFC0 0300

 其他

0x 8000 0180 

0x BFC0 0380

状态寄存器(SR)

BEV=1 :非缓存异常处理入口固定定位于非缓存的、启动安全的kseg1内存区域

BEV=0 :异常处理入口不固定,通过EBase寄存器可以编程移动,系统正常运行时为0

MIPS 下 TLB、Cache 都要 OS 参与管理,在其启动时 OS 尚未接管系统,这个时候不采用 TLB、Cache 机制是很重要的。

冷启动、 热重启、 非屏蔽中断的入口地址始终位于 0x BFC0 0000

 

其他异常类入口(一般称为通用异常入口)   CPU 内部异常或者外部中断发生时, CPU 硬件设置 CAUSE 寄存器的 ExcCode( CAUSE6: 2) 位后, 就跳转到该异常入口。 ExcCode 位段用来描述通用异常类型,  5 位, 故而可 以描述 2^5 = 32 个异常类型。

1. 高优先级异常入口初始化

通用异常入口初始化, 位于:[ arch/mips/kernel/traps. c]

void __init trap_init( )
cache 错误入口初始化, 位于:[ arch/mips/mm/c-r4k. c]

void __init r4k_cache_init( )

2. 通用异常处理表初始化

CAUSE 寄存器的 ExcCode 索引一张通 用异常处理表 exception_handlers 它定义于:

arch/mips/kernel/traps. c]
void __init trap_init( void)

 

异常初始化

1.   EPC被置为被中断的PC

2. 如果中断模式是兼容模式的话(普便情况),则vector offset = 0x180.

3.   如果Status[EXL] == 1, offset = 0x180,并且EPC不变

4. 如果是TLB refill, offset = 0x0000000,如果是其它异常,则是0x180。这里强调一下是,mips R2后中断有几种模式,不同的模式它的入口还不同。

5.  如果Status[BEV] == 1, BASE会到0xBFC00200,否则就BASE会是0x80000000,当然在MIPS32 R2后,我们设置了EBASE寄存器,这可以让用户选择中断后跳转的指令,这与R1就不同了,它固定在0x80000000处。为了向后兼容,EBASE默认情况下,它就是0x80000000处。

6.  设置cause[extcode]为异常号,中断为0

7. Status[EXL] = 0,这个值会产生很大的影响,它会无视Status[IE, KSU]位,而强制处于内核态和关中断状态。这样做的原因是为了我们有足够的时间去保存现场。

8.  最近PC会跑去base+offset的地方。

异常处理

异常产生

                      EPC指向异常位置

1. 设置EPC指向回归的位置;

2. 设置SR(EXL)强迫CPU进入kernel态,并禁止所有中断响应。

3. 设置Cause寄存器,以使软件可以得到异常的类型信息;还有其它一些寄存器在某些异常时 会被设置;

4.  CPU开始从异常入口取指令,然后以后的所有事情都交由软件处理了。

          k0和k1寄存器用于保存异常处理函数的地址。
          异常处理函数执行完成后,会回到异常分配函数那去,在异常分配函数里,有一个eret指令,用于回归原来被中断的程序继续执行;eret指令会原子性地把中断响应打开(置SR(EXL)),并把状态级由kernel转到user级,并返回原地址继续执行

中断

MIPS CPU有8个独立的中断位(在Cause寄存器中),其中,6个为外部中断,2个为内部中断(可由软件访问)。一般来说,片上的时钟计数/定时器,会连接到一个硬件位上去。

Ø 全局中断使能位SR(IE)必须置1,否则没有中断响应。

Ø 设置SR(EXL)(异常级别)和SR(ERL)(错误级别)位(任何异常之后会立即设置这二者之一)将阻止中断。

Ø 状态寄存器里有8个单独的中断屏蔽位SR(IM),每个对应Cause寄存器的一个中断位。要使能某个中断,其对应的屏蔽位SR(IM)必须置为1。

         中断处理程序也是用通用异常入口。

中断的响应:

handle_int   ->plat_irq_dispatch->do_IRQ(irq)->generic_handle_irq -> __do_IRQ()

1. 假如Gpio中断,系统产生中断, 通过在 CPU 的中断引脚上,引起异常。

2. CPU 自动设置 CAUSE  ExcCode 位为 0 IP5 x 并跳转到通用异常入口0x 80000180

3. 位于通用异常入口处的简单异常处理程序, 根据 ExcCode 的值索引异常处理表(exception_handlers)  获取到 0 号异常的处理程序是 handle_int 并跳转过去

4.  handle_int 根据 CAUSE  IP 位的值跳转到相关的中断plat_irq_dispatch;通过简单的计算得到中断号, 进而调  do_IRQ 进入相应的中断处理程序。

 

启动

CPU重启和异常几乎相同,但重启不会返回到被中断的程序。利用一般异常的机制,EPC就指向重启被侦测到时正在执行的那条指令,大多数寄存器的值会被保留。然而,重启打断了正常的操作、正在进行装载的一个寄存器,或者正在进行存储或重填的缓存单元,此时重启也许会被忽略。

利用重启时存储起来的状态可以做一些有用的死机后的调试。

启动顺序:

1. 跳转到主ROM代码。

2. 将状态寄存器设置成为某种已知的有意义的状态,从现在开始,可以对非缓存内存区域进行存取操作。

3. 在初始化并且对RAM空间的完整性进行快速自检前,启动代码只能使用寄存器。

4. 对控制台端口和诊断寄存器配置,以输出初始化过称中的问题

5. 分配堆栈,设置足够多的寄存器,通用标准的C代码

6. 初始化缓存


猜你喜欢

转载自blog.csdn.net/u011037149/article/details/50831428