IMX6ULL裸机学习(11)— 中断和GIC中断控制器

IMX6ULL裸机学习(11)— 中断和GIC中断控制器

一、什么是GIC

GIC即通用中断控制器( Generic Interrupt Controller),ARM 体系结构定义了通用中断控制器(GIC),该控制器包括一组用于管理单核或多核系统中的中断的硬件资源。 GIC 提供了内存映射寄存器,可用于管理中断源和行为,以及用于将中断路由到各个 CPU 核。它使软件能够屏蔽,启用和禁用来自各个中断源的中断,以对各个中断源进行优先级排序和生成软件触发中断。

如下图所示,对于各种中断都是通过GIC到达CPU的。另外,所有的中断都使用同一个异常向量,即无论发生什么中断,CPU都是跳转到异常向量表的第七项IRQ异常处,对于不同的中断,都需要我们在软件中区分。
在这里插入图片描述

二、中断的分类

如下图所示,为GIC控制器的逻辑结构。我们知道,中断源是有很多的,所以为了区分这些不同的中断源GIC会给他们分配一个唯一 ID,这些 ID 就是中断 ID。每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。这 1020 个 ID 分为三类:

  1. SGI:软件触发中断(Software Generated Interrupt)中断号0-15
    这是由软件通过写入专用仲裁单元的寄存器即软件触发中断寄存器(GICD_SGIR)显式生成的。它最常用于CPU核间通信。SGI既可以发给所有的核,也可以发送给系统中选定的一组核心。中断号0-15保留用于SGI的中断号。用于通信的确切中断号由软件决定。

  2. PPI:私有外设中断(Private Peripheral Interrupt)中断号16-31
    我们知道,GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理,因此这些中断就叫做私有中断。PPI的中断号为16-31,它们标识CPU核私有的中断源,并且独立于另一个内核上的相同中断源,比如,每个核的计时器。

  3. SPI:共享外设中断(Shared Peripheral Interrupt)中断号32-1020
    共享中断,顾名思义,所有 Core 共享的中断,这个是最常见的,比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
    在这里插入图片描述

三、IMX6ULL的GPIO中断

对于IMX6ULL来说,共享中断总共使用了128个中断 ID,即ID号32~159,这128个中断 在《I.MX6ULL 参考手册》的【3.2 Cortex A7 interrupts】小节的【Table 3-1】中列出来了。我们可以从中查找到我们所需要的GPIO1_IO18的中断ID为99(=67+32),并且可以看到GPIO1_IO16~IO31 这 16 个 IO 共用 ID 99。
在这里插入图片描述

四、IRQ中断编程

首先,修改start.s文件,添加IRQ异常的处理和允许中断触发

.text                               /* .text段保存代码,是只读和可执行的,后面的指令都属于.text段。 */
.global  _start                     /* .global表示_start是一个全局符号,会在链接器链接时用到 */

_start:                             /* 标签_start,汇编程序的默认入口是_start */
	b	reset						/* 复位异常,复位后从这里开始执行 */		
	ldr	pc,=_undefined_instruction  /* 未定义的指令异常,当cpu执行到不认识的指令时,会跳转到这里 */
	ldr	pc,=_software_interrupt     /* 软中断异常,当cpu执行svc指令时,会跳转到这里  */
	.word 0 /* ldr	pc,=_prefetch_abort         /* 预取中止异常,处理器预取指令的地址不存在,或不允许当前访问时 */
	.word 0 /* ldr	pc,=_data_abort             /* 数据中止异常,处理器数据访问指令的地址不存在,或不允许当前访问时 */
	.word 0 /* ldr	pc,=_not_used               /* 未使用,预留的地址 */
	ldr	pc,=_irq                    /* 中断请求有效,且CPSR中的I位(即中断屏蔽位)为0时,产生IRQ异常 */
	.word 0 /* ldr	pc,=_fiq                    /* 快速中断请求有效,且CPSR中的F位(即快中断屏蔽位)为0时,产生FIQ异常 */
reset:
    /* 1、设置栈 */
    ldr sp, =(0x80000000+0x100000)  /* 设置栈顶地址 */

    /* 2、设置异常向量表基地址 : VBAR */
	ldr r0, =_start
	mcr p15, 0, r0, c12, c0, 0

    /* 3、清除bss段 */
    ldr r1, =__bss_start            /* 将bss段开始地址存入r1寄存器 */
    ldr r2, =__bss_end              /* 将bss段结束地址存入r2寄存器 */
    b clean_bss
clean:
    mov r3, #0                      /* 将0存入r3寄存器 */
    str r3, [r1], #4                /* 将r3中的值存到r1中的值所指向的地址中, 同时r1中的值加4 */
clean_bss:
    cmp r1, r2                      /* 比较r1和r2内的值是否相等 */
    bne clean                       /* 如果不相等则跳转到clean标签处,如果相等则往下执行 */

    /* 4、调用系统初始化函数,在异常处理函数中需要用到uart功能,需要先初始化uart */
    bl SystemInit

    /* 5、获取当前CPSR的值存入r1中,r1会作为C函数的参数进行传递 */
    mrs r0, cpsr
    bl print_cpsr

    /* 6、写入一个未定义指令 */  
    .word 0xffffffff 

    /* 7、触发一个SVC异常 */
    SVC #1

    /* 8、允许中断触发,清除I位,使能中断*/
    CPSIE I  //

    /* 9、跳转到led函数 */
    bl main

    /* 10、原地循环 */
    b .

_undefined_instruction:
	/* 1、设置Undefined模式下的栈 */
	ldr sp, =(0x80000000+0x50000)
	
	/* 2、保存现场 */
	stmdb sp!, {
    
    r0-r12,lr}

	/* 3、调用处理函数 */
	bl do_undefined_c

	/* 4、恢复现场 */
	ldmia sp!, {
    
    r0-r12,pc}^

_software_interrupt:
    /* 1、设置SVC模式下的栈 */
	ldr sp, =(0x80000000+0x40000)
	
	/* 2、保存现场 */
	stmdb sp!, {
    
    r0-r12,lr}

	/* 3、调用处理函数 */
	bl do_svc_c

	/* 4、恢复现场 */
	ldmia sp!, {
    
    r0-r12,pc}^

_irq:
    /* 1、设置IRQ模式下的栈 */
	ldr sp, =(0x80000000+0x30000)
	
	/* 2、保存现场 */
    subs lr, lr, #4
	stmdb sp!, {
    
    r0-r12,lr}

	/* 3、调用处理函数 */
	bl do_irq_c

	/* 4、恢复现场 */
	ldmia sp!, {
    
    r0-r12,pc}^

然后新建gic.h和gic.c函数,实现gic控制器的初始化和irq处理函数do_irq_c()
首先,我们可以在《I.MX6ULL 参考手册》的【System memory map】表中查到GIC控制器的基地址为0X00A00000,也可以通过 CP15 查询,指令如下,将 GIC 的基地址读到 r0 寄存器:

mrc p15, 4, r0, c15, c0, 0

在这里插入图片描述
接下来我们要将所有的中断禁用,以免再初始化的过程中出现意外,先通过GICD_TYPER寄存器获取中断个数,在【ARM® Generic Interrupt Controller】手册中可以查到该寄存器
在这里插入图片描述
在这里插入图片描述
然后对 GICD_ICENABLER寄存器写1来禁止中断,注意 GICD_ICENABLER有一组寄存器,每个寄存器控制着32个中断号
在这里插入图片描述
然后GICD_IGROUPR寄存器来设置中断的分发,将中断全部分发给group0,(关于Group 0 和Group 1是与安全相关的,暂时不去深究),同样的,每个寄存器控制着32个中断号。
在这里插入图片描述
另外,对于多核CPU,需要设置将中断发送给哪一个CPU,通过GICD_ITARGETSR寄存器来实现,因为IMX6ULL为单核CPU,所以我们将所有的共享中断都发送给CPU interface 0,不同于上面两个寄存器,每个GICD_ITARGETSR寄存器只控制4个中断号,且GICD_ITARGETSR0~ 7 是只读的,对应的就是中断号0~31(软件触发中断和私有外设中断)。
在这里插入图片描述
然后GICD_ICFGR用来设置中断触发类型,每个GICD_ICFGR寄存器控制16个中断号
在这里插入图片描述
关于中断类型的区别如下
在这里插入图片描述
寄存器GICC_PMR是一个中断优先级过滤器,对于IMX6ULL来说,其有32个优先级,【7:3】为优先级设置有效位,所以我们将GICC_PMR的【7:3】设置为1用来通过这32个优先级,将【2:0】设为0。
在这里插入图片描述
接下来是抢占优先级和子优先级的设置,我们将【7:3】这5位都设置位抢占优先级,即没有子优先级, GICC_BPR在设置为2,如下所示
在这里插入图片描述
编写gic_init()如下所示


/* 描述:GIC控制器初始化
 * 参数:无
 * 返回值:无 */	
void gic_init(void)
{
    
    
	u32 i, irq_num;

    /* 1、获取GIC控制器的基地址 */
	GIC_Type *gic = get_gic_base();

	/* 2、获取imxull的中断个数,注意: 中断个数 = irq_num * 32*/
	irq_num = (gic->D_TYPER & 0x1F) + 1;

	/* 3、禁止所有的PPI、SIG、SPI中断,每个ICENABLER寄存器对应32个中断号 */
	for (i = 0; i < irq_num; i++)
        gic->D_ICENABLER[i] = 0xFFFFFFFFUL;

	/* 4、把所有的中断都发给group0,每个IGROUPR寄存器对应32个中断号 */
	for (i = 0; i < irq_num; i++)
	  gic->D_IGROUPR[i] = 0x0UL;

	/* 5、所有的SPI中断都发给cpu interface 0,D_ITARGETSR定义为8位数据类型,只对应一个中断号 */
	for (i = 32; i < (irq_num * 32); i++)
	    gic->D_ITARGETSR[i] = 0x01UL;

	/* 6、设置所有的SPI中断触发类型,0-level, 1-edge  */
	for (i = 2; i < irq_num << 1; i++)
	    gic->D_ICFGR[i] = 0x01010101UL;

	/* 7、优先级过滤设置,允许imx6ull所有的32个优先级 */
	gic->C_PMR = (0xFFUL << (8 - 5)) & 0xFFUL;
	
	/* 8、设置优先级组,[7:3]五位都为抢占优先级,没有子优先级 */
	gic->C_BPR = 7 - 5;
	
	/* 9、使能group0给CPU interface分发中断 */
	gic->D_CTLR = 1UL;
	
	/* 10、使能CPU interface给processor分发中断 */
	gic->C_CTLR = 1UL;
}

/* 描述:使能中断号为nr的中断
 * 参数:中断ID
 * 返回值:无 */	
void gic_enable_irq(uint32_t nr)
{
    
    
	GIC_Type *gic = get_gic_base();

	/* The GICD_ISENABLERs provide a Set-enable bit for each interrupt supported by the GIC.
	 * Writing 1 to a Set-enable bit enables forwarding of the corresponding interrupt from the
	 * Distributor to the CPU interfaces. Reading a bit identifies whether the interrupt is enabled.
	 */
	gic->D_ISENABLER[nr >> 5] = (uint32_t)(1UL << (nr & 0x1FUL));

}

然后我们再实现如下几个函数

/* 描述:使能中断号为nr的中断
 * 参数:中断ID
 * 返回值:无 */	
void gic_enable_irq(uint32_t nr)
{
    
    
	GIC_Type *gic = get_gic_base();

	/* The GICD_ISENABLERs provide a Set-enable bit for each interrupt supported by the GIC.
	 * Writing 1 to a Set-enable bit enables forwarding of the corresponding interrupt from the
	 * Distributor to the CPU interfaces. Reading a bit identifies whether the interrupt is enabled.
	 */
	gic->D_ISENABLER[nr >> 5] = (uint32_t)(1UL << (nr & 0x1FUL));

}

/* 描述:禁止中断号为nr的中断
 * 参数:中断ID
 * 返回值:无 */
void gic_disable_irq(uint32_t nr)
{
    
    
	GIC_Type *gic = get_gic_base();

	/* The GICD_ICENABLERs provide a Clear-enable bit for each interrupt supported by the
	 * GIC. Writing 1 to a Clear-enable bit disables forwarding of the corresponding interrupt from
     * the Distributor to the CPU interfaces. Reading a bit identifies whether the interrupt is enabled. 
	 */
	gic->D_ICENABLER[nr >> 5] = (uint32_t)(1UL << (nr & 0x1FUL));
}

/* 描述:获得当前中断的 interrtup ID。
 * 参数:无
 * 返回值:中断ID */
uint32_t get_gic_irq(void)
{
    
    
	uint32_t nr;

	GIC_Type *gic = get_gic_base();
	/* The processor reads GICC_IAR to obtain the interrupt ID of the
	 * signaled interrupt. This read acts as an acknowledge for the interrupt
	 */
	nr = gic->C_IAR;

	return nr;
}

/* 描述:清除中断,表示该中断已经处理完毕
 * 参数:中断ID
 * 返回值:无 */
void clear_gic_irq(int nr)
{
    
    

	GIC_Type *gic = get_gic_base();

	/* write GICC_EOIR inform the CPU interface that it has completed 
	 * the processing of the specified interrupt 
	 */
	gic->C_EOIR = nr;
}

/* 描述:IRQ中断处理函数
 * 参数:无
 * 返回值:无 */
void do_irq_c(void)
{
    
    
	int irq;
	
	/* 1. 分辨中断,获取当前中断号 */
	irq = get_gic_irq();

	/* 2. 调用处理函数 */
	if (irq == (67 + 32))
	{
    
    
        irq_hander_gpio1_16_32();
	}

	/* 3. 清除中断 */
	clear_gic_irq(irq);
}

五、GPIO编程

新建key.hkey.c文件,编写GPIO初始化以及中断处理函数

#include "gic.h"
#include "key.h"
#include "uart.h"

#define IRQ_GPIO1_16_32 (67 + 32)

void key_init(void)
{
    
    
    volatile unsigned int *pRegKey;
    GPIO_Type *gpio1 = (GPIO_Type *)0x0209c000;
	 
	/* 1、把GPIO1_18设置为GPIO功能 */
	pRegKey = (volatile unsigned int *)(0x020e0000 + 0x8c);
	*pRegKey &= ~(0xf);
	*pRegKey |= (0x5);

	/* 2、把GPIO1_18设置为输入引脚 */
	gpio1->GDIR &= ~(1<<18);

	/* 3、设置中断触发方式:双边沿触发 */
	gpio1->EDGE_SEL |= (1<<18);

	/* 4、为避免发生错误的中断, 先清除IO18中断标志位和GPIO1中断 */
	gpio1->ISR |= (1<<18);
	clear_gic_irq(IRQ_GPIO1_16_32);

	/* 5、使能IO14中断,使能GPIO4中断 */
	gpio1->IMR |= (1<<18);	
	gic_enable_irq(IRQ_GPIO1_16_32);
}

void irq_hander_gpio1_16_32(void)
{
    
    
	GPIO_Type *gpio1 = (GPIO_Type *)0x0209c000;
	
	if ((gpio1->ISR & (1<<18)) != 0)
	{
    
    
		if (gpio1->DR & (1<<18))
		{
    
    
			putstring("KEY released!\r\n");
		}
		else
		{
    
    
            putstring("KEY pressed!\r\n");			
		}
		gpio1->ISR |= (1<<18);
	}
}

六、编译

修改makefile,添加gic.c和key.c到源文件目录中,

C_SOURCES = src/main.c \
src/uart.c \
src/led.c \
src/gic.c \
src/key.c

在main函数中加入gic和key的初始化

#include "uart.h"
#include "led.h"
#include "gic.h"
#include "key.h"

void SystemInit(void)
{
    
    
    uart_init();
}

int main(void)
{
    
    
    led_init();
    gic_init();
    key_init();

    putstring("imx6ull\r\n");

    while(1)
    {
    
    
        putstring("led on\r\n");
        led_on();
        delay(1000000);

        putstring("led off\r\n");
        led_off();
        delay(1000000);
    }
}

执行make编译,
在这里插入图片描述

六、烧录运行

接下来烧录到开发板,按下按键,可以看到中断已经触发
在这里插入图片描述

七、附录

上一篇:IMX6ULL裸机学习(10)— 未定义异常和SVC异常实例
下一篇:
代码存放:https://gitee.com/william_william/imx6ull_noos/tree/master/05_gpio_int

猜你喜欢

转载自blog.csdn.net/qq_38113006/article/details/112748489