Chinaunix首页 | 论坛 | 博客
  • 博客访问: 221084
  • 博文数量: 66
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2013-03-07 16:50
文章分类

全部博文(66)

文章存档

2016年(5)

2014年(27)

2013年(34)

我的朋友

分类: LINUX

2013-09-10 09:28:00

PowerPC 异常类型与异常向量

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


经典 PowerPC
异常类型 异常向量偏移 (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 比特,该比特用来选择中断向量的地址前缀。


Book E
异常类型 异常向量偏移寄存器 说明
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) 


EXCEPTION 这句展开后如下:
				
 .align 5; //32 bytes aligned 
 ExternalInput: 
  NORMAL_EXCEPTION_PROLOG; 
  addi 	 r3,r1,STACK_FRAME_OVERHEAD; 
  xfer(0x0500, do_IRQ); 

这些宏继续展开就是中断处理的代码。后面”中断处理程序”一章有更详细介绍。


在 __early_start 函数中,使用以上代码段的地址设置 IVOR4 和 IVPR:
				
 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 实现。


arch_local_irq_disable 的代码展开如下:
				
 #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 中断的硬件处理流程

  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 所保存的地址处继续执行。

PowerPC Linux 中断处理程序

异常向量 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 的内部逻辑和编程接口才能回答。

MPIC

MPIC 概述

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


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

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;

  • 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 内部寄存器和流程

另外还有几个 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。他们的实现请参考:


读 IACK
				
 do_IRQ 
 ppc_md.get_irq() 
 ==mpic.c::mpic_get_irq() 


写 EOI
				
 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


参考资料

学习

讨论

阅读(2166) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~