赞
踩
imx6ull中断控制器使用GIC(通用中断控制器),功能框图如下图所示:
GIC 主要分为分发器(Distributor)和 CPU 接口(CPU interface/Virtual CPU interface)。
分发器(Distributor):分发器用于管理 CPU 所有中断源,确定每个中断的优先级,管理中断的屏蔽和中断抢占。最终将优先级最高的中断转发到一个或者多个 CPU 接口。 (也就是每次分发一个中断)。
分发器的作用:
• 全局的开启或关闭 CPU 的中断。
• 控制任意一个中断请求的开启和关闭。(中断使能)//最主要就是这个
• 设置每个中断请求的中断优先级。
• 指定中断发生时将中断请求发送到那些 CPU(i.MX 6U 是单核)。
• 设置每个”外部中断”的触发方式(边缘触发或者电平触发)。
分发器相关寄存器
CPU接口:CPU 接口可以开启或关闭发往 CPU 的中断请求, CPU 中断开启后只有优先级高于“中断优先级掩码”的中断请求才能被发送到 CPU。
CPU接口的作用:
• 开启或关闭向处理器发送中断请求.。
• 确认中断(acknowledging an interrupt)。
• 指示中断处理的完成。
• 为处理器设置中断优先级掩码。
• 定义处理器的抢占策略
• 确定挂起的中断请求中优先级最高的中断请求。
最主要就是获取当前中断的中断号:保存在 中断确认寄存器 GICC_IAR中
CPU接口相关寄存器
CP15 寄存器是一组寄存器编号为 C0~c15。每一个寄存器根据寄存器操作码(opc1 和 opc2)和 CRm 又可分为多个寄存器
*即可通过对协处理器设置不同的参数,最终可以使协处理器对应到不同的寄存器;类似指针?
协处理器读写说明
//设置并读协处理器
MRC {cond} p15, <opc1>, <Rn>, <CRn>, <CRm>, <opc2>
//设置并写协处理器
MCR {cond} p15, <opc1>, <Rn>, <CRn>, <CRm>, <opc2>
CP15 寄存器读、写指令说明如下:
• cond:指令执行的条件码,忽略则表示无条件执行命令。
• Opcode_1:寄存器操作码 1 ,对应 Op1 选项。
• Rd:通用寄存器,当为 mrc 时,用于保存从 CP15 寄存器读取得到的数据。当为 mcr 时,用于保存将要
写入 CP15 寄存器的数据。
• CRn:要读、写的 CP15 寄存器(c0~c15),对应的 CRn 选项。
• CRm:寄存器从编号,对应 CRm 选项。
• Opcode_2:寄存器操作码 2,对应的 Op2 选项。
如:读取系统控制寄存器
如上图所示,系统控制寄存器,协处理器的参数分别为:CRn=c1,opc1=0,CRm=c0,opc2=0,故读写 SCTLR 寄存器的值到R0的命令为:
mrc p15, 0, r0, c1, c0, 0 /* Read CP15 System Control register /
mcr p15, 0, r0,c1,c0,0 / Write value back to CP15 System Controlregister */
回到上述问题,GIC中断控制寄存器的基地址可以通过协处理器来获取,GIC 基地址保存在 CBAR 寄存器中。
/****获取当前中断的中断号***/
MRC P15, 4, r1, C15, C0, 0 /* Get GIC base address :即从CBAR寄存器中获取GIC基地址*/
ADD r1, r1, #0x2000 /* r1: GICC base address :R1=R1+0X2000*/
LDR r0, [r1, #0xC] /* r0: IAR 从R1+0XC这个地址中获取到的内容加载到R0寄存器,即从GICC_IAR寄存器中获取当前中断的中断号*/
在特权模式下(除了用户模式,剩余的模式都是特权模式?有争议),可以通过CPS指令直接修改CPSR寄存器(程序状态寄存器)的M[4:0],让处理器进入不同的模式
CPSR 的寄存器结构相同,如下图所示:
其中M[4:0] 就是处理器的模式控制为,不同的编码对应着不同的处理器模式,如下图所示:
CPS #0x12 /*把CPSR寄存器的M[4:0]修改为0x12,让处理器进入IRQ 模式*/
CPS #0x13 /*把CPSR寄存器的M[4:0]修改为0x13,让处理器进入SVC 模式*/
/*****中断向量表**********/ __vector_table ARM LDR PC, Reset_Word ; Reset LDR PC, Undefined_Word ; Undefined instructions LDR PC, SVC_Word ; Supervisor Call LDR PC, PrefAbort_Word ; Prefetch abort LDR PC, DataAbort_Word ; Data abort DCD 0 ; RESERVED LDR PC, IRQ_Word ; IRQ interrupt LDR PC, FIQ_Word ; FIQ interrupt /*******DATA段*******/ DATA /*使用” DCD”伪指令将” Reset_Word”(复位中断)跳转地址设置为 ” __iar_program_start”即复位中断发生后将会跳转到” __iar_program_start”位置执行*/ Reset_Word DCD __iar_program_start Undefined_Word DCD Undefined_Handler SVC_Word DCD SVC_Handler PrefAbort_Word DCD PrefAbort_Handler DataAbort_Word DCD DataAbort_Handler IRQ_Word DCD IRQ_Handler FIQ_Word DCD FIQ_Handler __iar_program_start CPSID I ; Mask interrupts,关闭所有中断 ; Reset SCTLR Settings,设置系统控制寄存器 MRC P15, 0, R0, C1, C0, 0 ; Read CP15 System Control register BIC R0, R0, #(0x1 << 12) ; Clear I bit 12 to disable I Cache BIC R0, R0, #(0x1 << 2) ; Clear C bit 2 to disable D Cache BIC R0, R0, #0x2 ; Clear A bit 1 to disable strict alignment BIC R0, R0, #(0x1 << 11) ; Clear Z bit 11 to disable branch prediction BIC R0, R0, #0x1 ; Clear M bit 0 to disable MMU ; Write value back to CP15 System Control register MCR P15, 0, R0, C1, C0, 0 ; Set up stack for IRQ, System/User and Supervisor Modes ; Enter IRQ mode,进入IRQ模式,设置IRQ模式下的栈地址 CPS #0x12 LDR SP, =SFE(ISTACK) ; Set up IRQ handler stack ; Enter System mode,进入系统模式,设置系统模式下的栈地址 CPS #0x1F LDR SP, =SFE(CSTACK) ; Set up System/User Mode stack ; Enter Supervisor mode,进入SVC模式,设置SVC模式下的栈地址,SVC模式即特权模式 CPS #0x13 LDR SP, =SFE(CSTACK) ; Set up Supervisor Mode stack LDR R0, =SystemInit ;系统初始化 BLX R0 CPSIE I ; Unmask interrupts开中断 ; Application runs in Supervisor mode,进入_cmain,之后进入main() LDR R0, =__cmain BX R0 PUBWEAK Undefined_Handler PUBWEAK SVC_Handler PUBWEAK PrefAbort_Handler PUBWEAK DataAbort_Handler PUBWEAK IRQ_Handler PUBWEAK FIQ_Handler SECTION .text:CODE:REORDER:NOROOT(2) EXTERN SystemIrqHandler ARM Undefined_Handler B . ; Undefined instruction at address LR-Off \ (Off=4 in ARM mode and Off=2 in THUMB mode) SVC_Handler B . ; Supervisor call from Address LR PrefAbort_Handler B . ; Prefetch instruction abort at address LR-4 DataAbort_Handler B . ; Load data abort at instruction address LR-8 /*****IRQ中断******/ IRQ_Handler /****CPSR(程序状态寄存器)中为IRQ模式,此时使用IRQ模式的栈空间* *****SPSR(程序状态保存寄存器)中保存进入IRQ中断的前一个模式************/ PUSH {LR} ; Save return address+4保存进入中断的LR寄存器 PUSH {R0-R3, R12} ; Push caller save registers MRS R0, SPSR ; Save SPRS to allow interrupt reentry 程序状态保存寄存器。SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态 PUSH {R0} /*从GICC_IAR寄存器中获取当前中断的中断号并加载到R0寄存器中*/ MRC P15, 4, R1, C15, C0, 0 ; Get GIC base address ADD R1, R1, #0x2000 ; R1: GICC base address LDR R0, [R1, #0xC] ; R0: IAR PUSH {R0, R1} //PUSH R0以及R1,将当前中断的中断号通过R0寄存器传参 CPS #0x13 ; Change to Supervisor mode to allow interrupt reentry,进入SVC模式,即中断处理的二级查表的栈空间使用SVC模式下的栈空间 PUSH {LR} ; Save Supervisor LR //PUSH LR寄存器,便于进入SystemIrqHandler()之后返回 LDR R2, =SystemIrqHandler BLX R2 ; Call SystemIrqHandler with param IAR//调转到SystemIrqHandler()函数进行二级查表 POP {LR} CPS #0x12 ; Back to IRQ mode返回IRQ模式 POP {R0, R1} STR R0, [R1, #0x10] ; Now IRQ handler finished: write to EOIR中断执行完成,通知CPU POP {R0} //这个R0中为SPSR程序状态保存寄存器 MSR SPSR_CXSF, R0 //恢复spsr POP {R0-R3, R12} POP {LR} SUBS PC, LR, #4 //跳出IRQ中断,返回进入中断处 FIQ_Handler B . ; Unexpected FIQ END
整个中断的流程为:
1、发生中断时,进入IRQ中断,//第一级查表
2、通过协处理器找到当前中断的中断号,将中断号加载到R0中
3、PUSH R0到栈中,R0中值将传递给调用函数的第一个参数
4、调用SystemIrqHandler()函数,进行二级查表
5、根据查表执行实际的当前中断的中断处理函数
6、中断处理完成,通知CPU,返回中断断点处
void SystemIrqHandler(uint32_t giccIar)
{
uint32_t intNum = giccIar & 0x3FFUL;
/* j检查中断号是否合法 */
if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS))
{
return;
}
/*如果对应的中断函数不为空,则调用该中断函数*/
if(NULL != irqTable[intNum].irqHandler)
/*根据传递进入的中断号,执行表中对应指向的函数**/
irqTable[intNum].irqHandler(giccIar, irqTable[intNum].userParam);
return;
}
中断表的定义如下:
/*中断服务函数表*/
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];
sys_irq_handle_t 中断函数结构体定义如下
/*定义函数指针*/
typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param);
/* 中断函数结构体*/
typedef struct _sys_irq_handle
{
system_irq_handler_t irqHandler; /* 中断服务函数指针 */
void *userParam; /* 中断服务函数参数 */
} sys_irq_handle_t;
使用时,先在中断号对应的中断表 irqTable 中注册中断服务函数,然后
当中断发生时,首先进入IRQ中断;
之后在IRQ中断中调用SystemIrqHandler();
再在SystemIrqHandler()函数中通过当前中断号,查询 irqTable (中断表)中早已注册好的中断服务函数。
注册中断服务函数如下:
/*注册中断服务函数*/
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam)
{
irqTable[irq].irqHandler = handler; //中断服务函数
irqTable[irq].userParam = userParam; //要传入中断服务函数的参数,一般为NULL
}
所以在用户编程时,配置好中断之后,需要手动注册中断服务函数,再开启中断(防止未注册中断服务函数就进入中断中)
一个简单的中断配置如下所示:
/*按键初始化函数*/ void button_init(void) { /**********************************第一部分***********************************/ /*时钟初始化*/ CCM->CCGR1 = 0xffffffff; /*设置 key引脚的复用功能以及PAD属性*/ IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01,0); IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0x10B0); /*设置GPIO5_01为输入模式*/ GPIO5->GDIR &= ~(1<<1); /**********************************第二部分**************************************/ /*使能GPIO5_1中断*/ GPIO5->IMR |= (1<<1); /*禁止GPIO5_1中断双边沿触发*/ GPIO5->EDGE_SEL = 0; /*设置GPIO5_1上升沿触发*/ GPIO5->ICR1 |= (2<<2); /*添加中断服务函数到 "中断向量表"*/ system_register_irqhandler(GPIO5_Combined_0_15_IRQn, (system_irq_handler_t)GPIO5_1_IRQHandler, NULL); /*开启GIC中断相应中断*/ GIC_EnableIRQ(GPIO5_Combined_0_15_IRQn); } void GPIO5_1_IRQHandler(void) { /*按键引脚中断服务函数*/ if((GPIO5->DR)&(1<<1)){ if(button_status > 0){ button_status = 0; } else{ button_status = 1; } } /*清除Gpio5_1中断标志位*/ GPIO5->ISR |= (1 << 1); }
imx6ull的中断处理机制为:
1、发生中断时,进入IRQ中断,//第一级查表
2、通过协处理器找到当前中断的中断号,将中断号加载到R0中
3、PUSH R0到栈中,R0中值将传递给调用函数的第一个参数
4、调用SystemIrqHandler()函数,进行二级查表
5、根据查表执行实际的当前中断的中断处理函数
6、中断处理完成,通知CPU,返回中断断点处
在裸机编程中需要进行的工作:
1、配置好中断;
2、注册中断服务函数到 irqTable
3、开启GIC相应中断。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。