当前位置:   article > 正文

imx6ull中断分析

imx6ull中断

GIC中断控制器简介

imx6ull中断控制器使用GIC(通用中断控制器),功能框图如下图所示:
在这里插入图片描述
GIC 主要分为分发器(Distributor)和 CPU 接口(CPU interface/Virtual CPU interface)。
分发器(Distributor):分发器用于管理 CPU 所有中断源,确定每个中断的优先级,管理中断的屏蔽和中断抢占。最终将优先级最高的中断转发到一个或者多个 CPU 接口。 (也就是每次分发一个中断)。

分发器的作用:
• 全局的开启或关闭 CPU 的中断。
• 控制任意一个中断请求的开启和关闭。(中断使能)//最主要就是这个
• 设置每个中断请求的中断优先级。
• 指定中断发生时将中断请求发送到那些 CPU(i.MX 6U 是单核)。
• 设置每个”外部中断”的触发方式(边缘触发或者电平触发)。

分发器相关寄存器

  • 中断数量:GICD_TYPER
  • 中断清除: GICD_ ICENABLERn
  • 中断使能:GICD_ISACTIVERn
  • 中断优先级设置:GICD_IPRIORITYR

CPU接口:CPU 接口可以开启或关闭发往 CPU 的中断请求, CPU 中断开启后只有优先级高于“中断优先级掩码”的中断请求才能被发送到 CPU。

CPU接口的作用:
• 开启或关闭向处理器发送中断请求.。
• 确认中断(acknowledging an interrupt)。
• 指示中断处理的完成。
• 为处理器设置中断优先级掩码。
• 定义处理器的抢占策略
• 确定挂起的中断请求中优先级最高的中断请求。
最主要就是获取当前中断的中断号:保存在 中断确认寄存器 GICC_IAR

CPU接口相关寄存器

  • 中断优先级数量:GICC_PMR
  • 抢占优先级和子优先级设置: GICC_BPR
  • 保存中断ID:GICC_IAR
  • 通知cpu中断完成:GICC_EOIR

协处理器简介

CP15 寄存器是一组寄存器编号为 C0~c15。每一个寄存器根据寄存器操作码(opc1 和 opc2)和 CRm 又可分为多个寄存器
*即可通过对协处理器设置不同的参数,最终可以使协处理器对应到不同的寄存器;类似指针?
协处理器读写说明

//设置并读协处理器
MRC {cond} p15, <opc1>, <Rn>, <CRn>, <CRm>, <opc2>
//设置并写协处理器
MCR {cond} p15, <opc1>, <Rn>, <CRn>, <CRm>, <opc2> 
  • 1
  • 2
  • 3
  • 4

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寄存器中获取当前中断的中断号*/
  • 1
  • 2
  • 3
  • 4

cps指令

在特权模式下(除了用户模式,剩余的模式都是特权模式?有争议),可以通过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 模式*/
  • 1
  • 2

官方启动代码分析

/*****中断向量表**********/
__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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125

整个中断的流程为:
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

中断表的定义如下:

/*中断服务函数表*/
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];
  • 1
  • 2

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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

使用时,先在中断号对应的中断表 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
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

所以在用户编程时,配置好中断之后,需要手动注册中断服务函数,再开启中断(防止未注册中断服务函数就进入中断中)
一个简单的中断配置如下所示:

/*按键初始化函数*/
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);    
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

总结

imx6ull的中断处理机制为:
1、发生中断时,进入IRQ中断,//第一级查表
2、通过协处理器找到当前中断的中断号,将中断号加载到R0中
3、PUSH R0到栈中,R0中值将传递给调用函数的第一个参数
4、调用SystemIrqHandler()函数,进行二级查表
5、根据查表执行实际的当前中断的中断处理函数
6、中断处理完成,通知CPU,返回中断断点处

在裸机编程中需要进行的工作:
1、配置好中断;
2、注册中断服务函数到 irqTable
3、开启GIC相应中断。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/385569
推荐阅读
  

闽ICP备14008679号