转载一篇关于POWERPC的中断的文章

来源:互联网 发布:优优部屋 淘宝 编辑:程序博客网 时间:2024/05/29 11:38

将处理器内部产生的异常和外部中断信号统一称为异常 (Exception)。异常发生时,PowerPC 处理器根据异常的类型,分别跳转到不同的异常向量地址,执行异常处理程序。异常类型到异常向量的映射,经典 PowerPC 和 Book E 有所不同。



异常类型异常向量偏移 (0X)说明System Reset00100 Machine Check00200 DSI00300Data Storage ExceptionISI00400Instruction Storage ExceptionExternal Interrupt00500所有外部中断信号Alignment00600 … 本文只关注外部中断,完整内容请看处理器手册

根据异常类型得到偏移 offset, 异常向量的物理地址为 :

MSR[IP]=0 时,Vector = offset ;

MSR[IP]=1 时,Vector = offset | 0xFFF00000;

其中 MSR[IP] 代表 Machine State Register 的 Interrupt Prefix 比特,该比特用来选择中断向量的地址前缀。



异常类型异常向量偏移寄存器说明Critical InputIVOR0来自 #cint 引脚的外部中断信号Machine CheckIVOR1 DSIIVOR2Data Storage ExceptionISIIVOR3Instruction Storage ExceptionExternal InputIVOR4来自 #int 引脚的外部中断信号AlignmentIVOR5 … 本文只关注外部中断,完整内容请看处理器手册

从异常类型对应的 IVOR(Interrupt Vector Offset Register) 得到偏移 ( 只取低 16 比特 , 最低 4 比特清零 ),加上 IVPR(Interrupt Prefix Register) 的高 16 比特,构成中断向量的地址:

Vector = (IVORn & 0xFFF0) | (IVPR & 0xFFFF0000);

值得注意的是,跟经典 PowerPC 不同,Book E 的中断向量是 Effective Address, 对应 Linux 内核的虚拟地址。

 

以 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 位,是相同的。


 

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 


 

  1. 把正在执行的指令序列下一条指令地址保存到 SRR0(Save/Restore Regiser 0)。
  2. 把当前 MSR 的内容保存到 SRR1。
  3. 把 MSR 某些比特置为 0,如 PR, EE。

注:PowerPC 有两种执行模式,分别为用户模式 (User Mode) 和特权模式 (Supervisor Mode), MSR[PR]=0 表示特权模式。Linux 内核运行在特权模式,而普通程序运行在用户模式。

  1. 在新的 MSR 状态下,从中断向量偏移处开始指令读取和执行。
  2. 外部中断处理结束时,必须通过 rfi 指令返回。rfi 的执行,会把 SRR1 的内容恢复到 MSR, 并从 SRR0 所保存的地址处继续执行。

 

异常向量 ExternalInput 处的处理程序主要分为以下几个步骤:

  1. NORMAL_EXCEPTION_PROLOG 宏

    建立用户中断处理程序的栈帧,并把一些寄存器的值保存在栈帧中,它们在栈帧中布局由 struct pt_regs 定义。

  2. 执行 do_IRQ

    在 irq_enter() 函数中更新 preempt_count。 读 MPIC 的 IACK 寄存器,通知 MPIC 开始处理该中断,同时获取 MPIC 中断号 , 映射为软件中断号,找到并运行注册在该软件中断号上的用户中断处理程序,最后写 MPIC 的 EOI 寄存器,通知 MPIC 中断处理结束。

    在 irq_exit() 中更新 preempt_count, 调用 invoke_softirq() 处理 pending 的软中断。

  3. ret_from_except

    某些条件满足时,进行进程调度和信号处理,最后调用 rfi 指令返回。

    中断控制器在什么情况下把外设的中断信号发送给处理器?读 IACK 寄存器得到的是什么?硬件中断号怎么来的?为什么要写 EOI ?中断嵌套?这些问题的答案并不存在于代码中,需要了解 MPIC 的内部逻辑和编程接口才能回答。


 

 

OpenPIC(OPEN Programmable Interrupt Controller) 是 AMD 和 Cyrix 联合开发的中断控制器规范。目前 PowerPC 系统多使用 MPIC (Multi-Processor Interrupt Controller)。MPIC 兼容 OpenPIC 并有所增强,主要包括增加了 #cint 和 #mcp 中断信号输出,更灵活的多处理器中断路由算法等。



PowerPC 处理器与 MPIC 的连接,中断信号的路由

MPIC 的主要功能就是接受外设的中断信号或者 MPIC 本身产生的中断,按照一定的逻辑和次序,向各个处理器的管脚提交中断信号。

MPIC 本身也会产生中断,包括 IPI(Inter Processor Interrupt), MSI(Message Signaled Interrupt), MPIC Timer Interrupt 等。这些中断最后也是由 MPIC 送到处理器的 #int 引脚。对于处理器来说,它们也都是外部中断。关于这些中断及其使用,将另文介绍。

 

无论是从 MPIC 外部引脚进来的中断,还是 MPIC 自身产生的中断,都有一个中断源编号,范围是 0~127。查阅芯片手册可以获得具体中断的中断源编号。

 

中断源配置寄存器

每个中断源对应一套配置寄存器。它们的地址由中断源编号 n 决定。VPRn = VPR0 + 32 * n; DRn = VPRn + 16;

  • VPR(Vector Priority Register), 中断向量优先级寄存器。包括 16 比特 Vector ( 即所谓硬件中断号,本文称为 MPIC 中断号 ),4 比特优先级和 1 比特屏蔽位,还有两个比特用来设置中断信号的极性与模式 ( 组合为上升缘,下降缘,高电平,低电平四种触发方式 )。中断源编号到 MPIC 中断号的映射,是通过对 VPR 编程来控制的。
  • DR(Destination Register) 中断目的寄存器。P0~Pm 位用来指定该中断被定向到哪个处理器。

注意:对于 IPI 和 MPIC Timer 中断,DR 有多个比特被置 1,表示多播。一个中断被复制到多个处理器; 而对于来自外设的中断,DR 有多个比特被置 1,表示分布式分发,由 MPIC 进行路由选择,只将该中断发送给其中一个处理器。Freescale 的 QorIQ 系列平台的 MPIC, 都不支持分布式分发模式。

Per CPU 寄存器

每个处理器对应一套 Per CPU 寄存器。以下计算地址的公式中,n 代表处理器编号。

  • CTPR(Current Task Priority Register)

地址为:CTPRn = MPIC_CPU_BASE+ 4096*n + 0x80

包含 4 比特优先级字段。OS 可以设置当前任务的优先级,只有中断源的优先级大于 CTPR,MPIC 才会向处理器提交中断信号。对于实时操作系统来说,这是一个非常友好的设计。不过 Linux 目前没有利用这一功能,所有 CTPR 都一直设为 0。

注意:中断源优先级等于 CTPR 时,不会被提交到处理器。CTPR 最小值是 0,所以把中断的优先级设为 0,相当于屏蔽该中断。

  • IACK(Interrupt Ack Register)

地址为:IACKn = MPIC_CPU_BASE+ 4096*n + 0xa0

只读。处理器 n 响应一个外部中断,读 IACKn 就可以得到该中断的 MPIC 中断号 (Vector/Priority 寄存器的 Vector 字段 )。

  • EOI(End Of Interrupt)

地址为 EOIn = MPIC_CPU_BASE+ 4096*n + 0xb0

写 0 表示结束一个中断的处理。

全局寄存器

如 GCR 寄存器可以重启 MPIC, PIR 寄存器可以重启指定的处理器。

Linux 内核 MPIC 寄存器的定义

powerpc/include/asm/mpic.h 文件有以上寄存器地址偏移和各字段的比特掩码定义

 

另外还有几个 MPIC 内部的 Per CPU 寄存器,一般处理器无法访问它们,但是它们在 MPIC 内部处理流程中扮演了非常重要的角色。

  • IPR(Interrupt Pending Register)

共 136 位,分别代表 136 个中断源。IPR 记录 MPIC 接收到中断信号,但还没有开始处理的中断。

  • IRR(Interrupt Request Register)

IRR 只保存一个中断源编号。这个中断一定是 IPR 中优先级最高的,并且比处理器正在处理的中断 ( 如果有的话 ) 的优先级更高。

  • ISR(In-Service Register)

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() 


 

有三种关于中断的序号:

  • 中断源编号

由物理连接决定。它也是中断源配置寄存器的地址索引。

  • MPIC 中断号

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_HOST_MAP_LEGACY

两者取值相同

  • IRQ_HOST_MAP_NOMAP

两者并不相等,但是没有维护反向映射表。只能遍历 irq_map 来进行反向映射。

  • IRQ_HOST_MAP_LINEAR

用数组 revmap 保存反向映射表。查询函数为 irq_linear_revmap。

  • IRQ_HOST_MAP_TREE

用 radix tree 来保存反向映射表。查询函数为 irq_radix_revmap_lookup

原创粉丝点击