分类: LINUX
2013-02-01 11:49:00
PowerPC 异常类型与异常向量
将处理器内部产生的异常和外部中断信号统一称为异常 (Exception)。异常发生时,PowerPC 处理器根据异常的类型,分别跳转到不同的异常向量地址,执行异常处理程序。异常类型到异常向量的映射,经典 PowerPC 和 Book E 有所不同。
异常类型 | 异常向量偏移 (0X) | 说明 |
---|---|---|
System Reset | 00100 |
|
Machine Check | 00200 |
|
DSI | 00300 | Data Storage Exception |
ISI | 00400 | Instruction Storage Exception |
External Interrupt | 00500 | 所有外部中断信号 |
Alignment | 00600 |
|
… |
|
本文只关注外部中断,完整内容请看处理器手册 |
根据异常类型得到偏移 offset, 异常向量的物理地址为 :
MSR[IP]=0 时,Vector = offset ;
MSR[IP]=1 时,Vector = offset | 0xFFF00000;
其中 MSR[IP] 代表 Machine State Register 的 Interrupt Prefix 比特,该比特用来选择中断向量的地址前缀。
异常类型 | 异常向量偏移寄存器 | 说明 |
---|---|---|
Critical Input | IVOR0 | 来自 #cint 引脚的外部中断信号 |
Machine Check | IVOR1 |
|
DSI | IVOR2 | Data Storage Exception |
ISI | IVOR3 | Instruction Storage Exception |
External Input | IVOR4 | 来自 #int 引脚的外部中断信号 |
Alignment | IVOR5 |
|
… |
|
本文只关注外部中断,完整内容请看处理器手册 |
从异常类型对应的 IVOR(Interrupt Vector Offset Register) 得到偏移 ( 只取低 16 比特 , 最低 4 比特清零 ),加上 IVPR(Interrupt Prefix Register) 的高 16 比特,构成中断向量的地址:
Vector = (IVORn & 0xFFF0) | (IVPR & 0xFFFF0000);
值得注意的是,跟经典 PowerPC 不同,Book E 的中断向量是 Effective Address, 对应 Linux 内核的虚拟地址。
Linux 对 Book E 中断向量寄存器的设置
以 Freescale E500 处理器为例,在 arch/powerpc/kernel/head_xxx.S(xxx 为处理器子类型 ) 中,定义了标签 interrupt_base,紧接着就是
interrupt_base: ... EXCEPTION(0x0500, ExternalInput, do_IRQ, EXC_XFER_LITE) |
.align 5; //32 bytes aligned ExternalInput: NORMAL_EXCEPTION_PROLOG; addi r3,r1,STACK_FRAME_OVERHEAD; xfer(0x0500, do_IRQ); |
这些宏继续展开就是中断处理的代码。后面”中断处理程序”一章有更详细介绍。
li r26, ExternalInput@l; /*IVOR only uses the low 16-bits*/ mtspr SPRN_IVOR4, r26; ...... lis r4, interrupt_base@h /*IVPR only uses the high 16-bits*/ mtspr SPRN_IVPR, r4 |
以上代码把 IVOR4 和 IVPR 分别设置为 ExternalInput 地址的低 16 位和 interrupt_base 的高 16 位。由于 head_xxx.S 编译出来的目标文件链接在内核镜像的最前面,而内核加载地址对齐在比较大的内存块边界上,所以可以保证 interrupt_base 的高 16 位,跟 ExternalInput 的高 16 位,是相同的。
PowerPC 外部中断的屏蔽
MSR(Machine Status Register) 的 EE 位 (MSR 的第 16 比特,记为 MSR[EE]) 为 0 时将屏蔽来自 #int 引脚的外部中断,为 1 时使能。
mfmsr 和 mtmsr 指令用来读写 MSR 的内容。格式为:"mfmsr rD", "mtmsr rS"。另外 Book E 还提供专门的指令来写 MSR[EE] 位:wrtee 和 wrteei,格式为"wrtee rS","wrteei E"。其中 E 是立即数 0 或 1。
Linux kernel 中屏蔽和使能外部中断的接口有:
local_irq_disable()/local_irq_enable() local_irq_save()/local_irq_restore() |
另外,spin_lock_irq, spin_lock_irqsave 也包含以上屏蔽操作。
在 Linux Powerpc 中,以上屏蔽外部中断的操作,都是由内联函数 arch_local_irq_disable 或者 arch_local_irq_save 实现。
#ifdef CONFIG_BOOKE asm volatile("wrteei 0" : : : "memory"); #else register unsigned long rval; asm volatile("mfmsr %0\n" : "=r" (rval)); rval &= ~MSR_EE; /*MSR_EE = 1<<16*/ asm volatile("mtmsr %0" : : "r" (rval) : "memory"); #endif |
PowerPC 中断的硬件处理流程
注:PowerPC 有两种执行模式,分别为用户模式 (User Mode) 和特权模式 (Supervisor Mode), MSR[PR]=0 表示特权模式。Linux 内核运行在特权模式,而普通程序运行在用户模式。
PowerPC Linux 中断处理程序
异常向量 ExternalInput 处的处理程序主要分为以下几个步骤:
建立用户中断处理程序的栈帧,并把一些寄存器的值保存在栈帧中,它们在栈帧中布局由 struct pt_regs 定义。
在 irq_enter() 函数中更新 preempt_count。 读 MPIC 的 IACK 寄存器,通知 MPIC 开始处理该中断,同时获取 MPIC 中断号 , 映射为软件中断号,找到并运行注册在该软件中断号上的用户中断处理程序,最后写 MPIC 的 EOI 寄存器,通知 MPIC 中断处理结束。
在 irq_exit() 中更新 preempt_count, 调用 invoke_softirq() 处理 pending 的软中断。
某些条件满足时,进行进程调度和信号处理,最后调用 rfi 指令返回。
中断控制器在什么情况下把外设的中断信号发送给处理器?读 IACK 寄存器得到的是什么?硬件中断号怎么来的?为什么要写 EOI ?中断嵌套?这些问题的答案并不存在于代码中,需要了解 MPIC 的内部逻辑和编程接口才能回答。
MPIC
MPIC 概述
OpenPIC(OPEN Programmable Interrupt Controller) 是 AMD 和 Cyrix 联合开发的中断控制器规范。目前 PowerPC 系统多使用 MPIC (Multi-Processor Interrupt Controller)。MPIC 兼容 OpenPIC 并有所增强,主要包括增加了 #cint 和 #mcp 中断信号输出,更灵活的多处理器中断路由算法等。
MPIC 的主要功能就是接受外设的中断信号或者 MPIC 本身产生的中断,按照一定的逻辑和次序,向各个处理器的管脚提交中断信号。
MPIC 本身也会产生中断,包括 IPI(Inter Processor Interrupt), MSI(Message Signaled Interrupt), MPIC Timer Interrupt 等。这些中断最后也是由 MPIC 送到处理器的 #int 引脚。对于处理器来说,它们也都是外部中断。关于这些中断及其使用,将另文介绍。
中断源编号
无论是从 MPIC 外部引脚进来的中断,还是 MPIC 自身产生的中断,都有一个中断源编号,范围是 0~127。查阅芯片手册可以获得具体中断的中断源编号。
MPIC 编程接口
中断源配置寄存器
每个中断源对应一套配置寄存器。它们的地址由中断源编号 n 决定。VPRn = VPR0 + 32 * n; DRn = VPRn + 16;
注意:对于 IPI 和 MPIC Timer 中断,DR 有多个比特被置 1,表示多播。一个中断被复制到多个处理器; 而对于来自外设的中断,DR 有多个比特被置 1,表示分布式分发,由 MPIC 进行路由选择,只将该中断发送给其中一个处理器。Freescale 的 QorIQ 系列平台的 MPIC, 都不支持分布式分发模式。
Per CPU 寄存器
每个处理器对应一套 Per CPU 寄存器。以下计算地址的公式中,n 代表处理器编号。
地址为:CTPRn = MPIC_CPU_BASE+ 4096*n + 0x80
包含 4 比特优先级字段。OS 可以设置当前任务的优先级,只有中断源的优先级大于 CTPR,MPIC 才会向处理器提交中断信号。对于实时操作系统来说,这是一个非常友好的设计。不过 Linux 目前没有利用这一功能,所有 CTPR 都一直设为 0。
注意:中断源优先级等于 CTPR 时,不会被提交到处理器。CTPR 最小值是 0,所以把中断的优先级设为 0,相当于屏蔽该中断。
地址为:IACKn = MPIC_CPU_BASE+ 4096*n + 0xa0
只读。处理器 n 响应一个外部中断,读 IACKn 就可以得到该中断的 MPIC 中断号 (Vector/Priority 寄存器的 Vector 字段 )。
地址为 EOIn = MPIC_CPU_BASE+ 4096*n + 0xb0
写 0 表示结束一个中断的处理。
全局寄存器
如 GCR 寄存器可以重启 MPIC, PIR 寄存器可以重启指定的处理器。
Linux 内核 MPIC 寄存器的定义
powerpc/include/asm/mpic.h 文件有以上寄存器地址偏移和各字段的比特掩码定义
MPIC 内部寄存器和流程
另外还有几个 MPIC 内部的 Per CPU 寄存器,一般处理器无法访问它们,但是它们在 MPIC 内部处理流程中扮演了非常重要的角色。
共 136 位,分别代表 136 个中断源。IPR 记录 MPIC 接收到中断信号,但还没有开始处理的中断。
IRR 只保存一个中断源编号。这个中断一定是 IPR 中优先级最高的,并且比处理器正在处理的中断 ( 如果有的话 ) 的优先级更高。
15 位。ISR 记录处理器开始处理 ( 读 IACK),但还没有处理完的中断 ( 写 EOI) 的优先级。MPIC 不允许优先级相同的中断互相嵌套,所以最多能嵌套的中断数就是有效优先级的数目 15。
MPIC 接收到一个中断信号以后,如果中断源寄存器的屏蔽位为 0,则根据 DR 的内容,将 IPR 对应比特置 1。例如,若 DR 的 P2 被置 1,表示该中断被定向到处理器 2,因此将 IPR2 的对应比特置 1。
IPR 中优先级最高的中断,如果其优先级大于 ISR 中的最高优先级,则将它的中断源编号,保存到 IRR,同时它的 MPIC 中断号被拷贝到 IACK 中。
如果 IRR 中断的优先级大于 CTPR,则向处理器 #int 引脚输出中断信号。( 注意此时有可能有其他中断正在处理中 )
如果处理器的 MSR[EE] 位为 1,则执行 ExternalInput 中断向量,读 IACK 得到 MPIC 中断号。对 IACK 的读操作,同时会翻转中断信号的电平极性;将 ISR 中的对应比特置 1,表示该级别中断正在被处理中;IPR 中对应的比特也会被清零。
中断处理程序执行完时,向 EOI 写 0。MPIC 将 ISR 中优先级最高的比特清零。
总结一下:
只有屏蔽位为零的中断,才能进入到 IPR。
只有更高优先级的中断都处理完之后,IPR 中的中断才能进入到 IRR。
只有 IRR 中断的优先级大于 CTPR 时,才能向处理器输出中断信号。
只有处理器的 MSR[EE] 位为 1,处理器才响应中断信号,执行外部中断向量。
一个中断存在于 ISR 中的时间范围是软件读 IACK 和写 EOI 之间。
ISR 的行为类似一个堆栈,并且压入堆栈的总是更高的优先级,弹出的总是当前最高优先级。实际上,在系统内存中的确有一个这样的堆栈跟 ISR 相对应,那就是中断处理程序嵌套而形成的堆栈。
要支持中断嵌套,需要在中断处理程序中把 MSR[EE] 设为 1。( 因为在进入中断向量之前,MSR[EE] 被处理器自动设置为 0)
对 PowerPC Linux 来说,在中断处理过程中,软件跟 MPIC 之间的交互就是读 IACK 和写 EOI。他们的实现请参考:
do_IRQ ppc_md.get_irq() ==mpic.c::mpic_get_irq() |
do_IRQ handle_one_irq() generic_handle_irq() desc->handle_irq() = handle_xxxx_irq() desc->irq_data.chip->irq_eoi() ==mpic.c::mpic_eoi() |
中断号的映射
有三种关于中断的序号:
由物理连接决定。它也是中断源配置寄存器的地址索引。
VPR 寄存器 Vector 字段的值,是由软件配置到 VPR 的,理论取值范围为 0~65535。Linux 配置 VPR 时,总是让 Vector 等于中断源编号。
在 Linux 中,一个外部中断用 irq_desc 结构来表示,用同名的数组 irq_desc[NR_IRQS] 来表示所有外部中断。这个数组的索引就是软件中断号。
软件中断号跟 MPIC 中断号往往并不相等。函数 irq_create_mapping 用来建立 MPIC 中断号到软件中断号的映射。软件中断号到 MPIC 中断号的映射保存在数组 irq_map 中。
virq_to_hw 宏用来从软件中断号得到 MPIC 中断号。
反向映射 ( 从 MPIC 中断号得到软件中断号 ) 有四种方式,分别是:
两者取值相同
两者并不相等,但是没有维护反向映射表。只能遍历 irq_map 来进行反向映射。
用数组 revmap 保存反向映射表。查询函数为 irq_linear_revmap。
用 radix tree 来保存反向映射表。查询函数为 irq_radix_revmap_lookup
参考资料
学习
讨论