天天看點

ARM64基礎14:ARM64的中斷處理之GIC400實作(以樹莓派4采用的BCM2711晶片為例)1.GIC的誕生背景2.GIC的版本号3.GIC支援的中斷類型4 GIC的中斷狀态5 GIC硬體原理6.GICv2中斷控制器

接上篇,ARM64基礎13:ARM64的異常處理之中斷處理(以樹莓派4采用的BCM2711晶片為例)

1.GIC的誕生背景

傳統中斷控制器,比如樹莓派4b的legacy interrupt controller,具備

中斷enable寄存器;

中斷狀态寄存器;

随着新業務的出現,比如

1.中斷源變得越來越多;

2.不同類型的中斷出現,比如多核間中斷,中斷優先級,軟中斷等;

3.支援虛拟化;

傳統中斷控制器已經無法滿足需求,由此GIC控制器應運而生;

2.GIC的版本号

GIC演進的版本号如下表

GICv1 GICv2 GICv3

支援8核

支援多達1020個中斷源

8bit優先級

支援軟體觸發中斷

TrustZone支援

IP:

GIC-390

應用場景:

Cortex-A9 MPCore

支援虛拟化

支援secure software

IP:

GIC-400

應用場景:

Cortex-A7 MPCore

樹莓派4b

支援大于8核

支援基于消息的中斷

支援更多的中斷ID

IP:

GIC-500

GIC-600

應用場景:

新款手機

目前最新版本是GIC500/600,比如最新的高端手機大多支援,而在傳統嵌入式系統中,大多GIC400就夠用,比如這裡以樹莓派;

3.GIC支援的中斷類型

SGI: 軟體産生的中斷(Software Generated Interrupt),用于給其他CPU核發送中斷信号;

PPI: 私有外設中斷(Private Perpheral Interrupt),該中斷是某個指定的CPU獨有的;

SPI: 共享外設中斷(Shared Peripheral Interrupt),所有CPU都可以通路這個中斷;

LPI: 本地特殊外設中斷(Locality-specific Peripheral Interrupt),GICv3新增的中斷類型,基于消息傳遞的中斷類型

中斷類型 中斷号範圍
軟體觸發中斷(SGI) 0~15
私有外設中斷(PPI) 16~31
共享外設中斷(SPI) 32~1019

4 GIC的中斷狀态

中斷的處理流程是:分發器把收集來的中斷先緩存,依次把優先級最高的中斷請求送往CPU接口,CPU讀取一個中斷,其實就是讀取接口的一個寄存器,隻不過這個寄存器存放的是中斷号,此時中斷的狀态由pending轉為active,CPU處理完了以後,将中斷号寫入GIC的接口,告訴GIC處理完了,可以将這個中斷清理,

ARM64基礎14:ARM64的中斷處理之GIC400實作(以樹莓派4采用的BCM2711晶片為例)1.GIC的誕生背景2.GIC的版本号3.GIC支援的中斷類型4 GIC的中斷狀态5 GIC硬體原理6.GICv2中斷控制器

5 GIC硬體原理

GIC 是連接配接外設中斷和CPU的橋梁,也是多個CPU之間中斷互聯的通道,它負責檢測、管理、分發中斷;

ARM64基礎14:ARM64的中斷處理之GIC400實作(以樹莓派4采用的BCM2711晶片為例)1.GIC的誕生背景2.GIC的版本号3.GIC支援的中斷類型4 GIC的中斷狀态5 GIC硬體原理6.GICv2中斷控制器

5.1 GIC結構

ARM64基礎14:ARM64的中斷處理之GIC400實作(以樹莓派4采用的BCM2711晶片為例)1.GIC的誕生背景2.GIC的版本号3.GIC支援的中斷類型4 GIC的中斷狀态5 GIC硬體原理6.GICv2中斷控制器

上圖摘自GIC400官方手冊,GIC可以做到

1.使能或禁止中斷;

2.把中斷分組到FIQ或者IRQ;

3.多核系統中,可以将中斷配置設定到不同CPU上;

4.設定GIC給到CPU的觸發方式(不等于外設的觸發方式);

5.支援虛拟化擴充;

5.2 GIC功能

ARM64基礎14:ARM64的中斷處理之GIC400實作(以樹莓派4采用的BCM2711晶片為例)1.GIC的誕生背景2.GIC的版本号3.GIC支援的中斷類型4 GIC的中斷狀态5 GIC硬體原理6.GICv2中斷控制器

由上圖知,GIC按功能,劃分為兩部分:仲裁分發和CPU接口;

5.2.1.分發器distributor

主要作用是,檢測各個中斷源的狀态,控制各個中斷源的行為,分發各個中斷源産生的中斷事件到一個或多個CPU接口。主要功能包括:

(1) 使能或禁止中斷,分發器對中斷的控制分兩級别,一個是全局中斷控制(GIC_DIST_CTRL),一個是針對具體中斷源控制(GIC_DIST_ENABLE_CLEAR),

(2)控制優先級;

(3)将仲裁後的最高優先級中斷事件,分發給一個或多個CPU接口;

(4)中斷屬性設定,比如觸發方式等;

5.2.2 CPU接口

(1)禁止或使能CPU接口向相應的CPU送出中斷事件;對于ARM核,中斷信号線是nIRQ或nFIQ,若禁止GIC中斷,即使分發器分發了一個事件到CPU接口,也不會送出nIRQ或nFIQ信号給CPU;

(2)ackowledging中斷;ARM核一旦應答了中斷,分發器就會把該中斷狀态從pending改為active, 若沒有後續pending中斷,CPU接口會deassert nIRQ或nFIQ信号線;若有後續中斷,CPU接口将中斷狀态改為pending and active, 此時依然保持nIRQ或nFIQ信号的asserted狀态;

(3)中斷處理完畢;ARM核處理完中斷,回向CPU接口的寄存器寫EOI指令,分發器将目前中斷狀态改為deactive,同時也可以将其他pending的中斷向CPU接口送出;

(4)設定優先級掩碼;可以屏蔽較低優先級中斷,使其不同時給ARM核;

(5)設定中斷搶占政策;

(6)始終標明最高優先級中斷事件,送出給ARM核;

一個完整的中斷處理過程時序圖(摘自GIC400手冊B1):

假定:

a.都是電平觸發;

b.都是共享外設中斷;

c.M/N信号都配置為同一個CPU的為FIQEn中斷;

d.N信号優先級高于M信号;

ARM64基礎14:ARM64的中斷處理之GIC400實作(以樹莓派4采用的BCM2711晶片為例)1.GIC的誕生背景2.GIC的版本号3.GIC支援的中斷類型4 GIC的中斷狀态5 GIC硬體原理6.GICv2中斷控制器

6.GICv2中斷控制器

GIC-400包括兩組寄存器

D系列:The Distributor registers(GICD_),包含中斷設定和配置;

C系列:The CPU Interface registers(GICC_),包含CPU相關的特殊寄存器;

6.1通路GIC-400寄存器:

樹莓派4b中GIC-400基地

ARM64基礎14:ARM64的中斷處理之GIC400實作(以樹莓派4采用的BCM2711晶片為例)1.GIC的誕生背景2.GIC的版本号3.GIC支援的中斷類型4 GIC的中斷狀态5 GIC硬體原理6.GICv2中斷控制器

GIC-400中位址偏移:

ARM64基礎14:ARM64的中斷處理之GIC400實作(以樹莓派4采用的BCM2711晶片為例)1.GIC的誕生背景2.GIC的版本号3.GIC支援的中斷類型4 GIC的中斷狀态5 GIC硬體原理6.GICv2中斷控制器

6.2 GIC-400初始化流程

(1)設定distributor和CPU interface寄存器組的基位址;

(2)讀取GICD_TYPER寄存器,計算目前GIC最大支援多少個中斷源;

(3)初始化distributor:

a.disable distributor;

b.設定中斷分組;

b.設定SPI中斷的路由;

c.設定SPI中斷的觸發類型;

d.disactive和disable所有中斷源;

e.enable distributor;

(4)初始化CPU Interface:

a.設定GIC_CPU_PRIMASK,設定中斷優先級mask level;

b. enable CPU interface;

相關寄存器說明

1.設定分組

ARM64基礎14:ARM64的中斷處理之GIC400實作(以樹莓派4采用的BCM2711晶片為例)1.GIC的誕生背景2.GIC的版本号3.GIC支援的中斷類型4 GIC的中斷狀态5 GIC硬體原理6.GICv2中斷控制器

group0:安全中斷,由nFIQ驅動

group1:非安全中斷,由nIRQ驅動

6.3 注冊中斷

(1) 初始化外設;

(2)查找該外設的中斷在GIC-400的中斷号,例如PNS timer中斷号為30;

(3)設定GIC_DIST_ENABLE_SET寄存器來enable這個中斷号;

(4)打開裝置相關的中斷,例如樹莓派的generic timer,需要打開ARM_LOCAL寄存器組中的TIMER_CNTRL0寄存器中相應enable位;

(5)打開CPU的PSTATE中I位;

查樹莓派手冊知,PNS timer中斷号為30

ARM64基礎14:ARM64的中斷處理之GIC400實作(以樹莓派4采用的BCM2711晶片為例)1.GIC的誕生背景2.GIC的版本号3.GIC支援的中斷類型4 GIC的中斷狀态5 GIC硬體原理6.GICv2中斷控制器

6.4 中斷響應

1.中斷觸發;

2. 跳轉異常向量表;

3. 跳轉到GIC中斷函數裡,gic_handle_irq();

4. 讀取GICC_IAR寄存器,擷取中斷号;

5. 根據中斷号來進行相應中斷處理,例如,若讀取中斷号為30,說明是PNS的generic timer,跳轉到generic timer處理函數;

讀取中斷号;

ARM64基礎14:ARM64的中斷處理之GIC400實作(以樹莓派4采用的BCM2711晶片為例)1.GIC的誕生背景2.GIC的版本号3.GIC支援的中斷類型4 GIC的中斷狀态5 GIC硬體原理6.GICv2中斷控制器

中斷處理完成,寫回EOI:

ARM64基礎14:ARM64的中斷處理之GIC400實作(以樹莓派4采用的BCM2711晶片為例)1.GIC的誕生背景2.GIC的版本号3.GIC支援的中斷類型4 GIC的中斷狀态5 GIC硬體原理6.GICv2中斷控制器

timer部分核心代碼:

#include <asm/timer.h>
#include <asm/irq.h>
#include <io.h>
#include <asm/arm_local_reg.h>
#include <timer.h>

#define HZ 250
#define NSEC_PER_SEC    8000000000L

static unsigned int val = NSEC_PER_SEC / HZ;

static int generic_timer_init(void)
{
	asm volatile(
		"mov x0, #1\n"
		"msr cntp_ctl_el0, x0"
		:
		:
		: "memory");

	return 0;
}

static int generic_timer_reset(unsigned int val)
{
	asm volatile(
		"msr cntp_tval_el0, %x[timer_val]"
		:
		: [timer_val] "r" (val)
		: "memory");

	return 0;
}

static void enable_timer_interrupt(void)
{
	writel(CNT_PNS_IRQ, TIMER_CNTRL0);
}

void timer_init(void)
{
	generic_timer_init();
	generic_timer_reset(val);

	gicv2_unmask_irq(GENERIC_TIMER_IRQ);

	//enable_timer_interrupt();
}

void handle_timer_irq(void)
{
	generic_timer_reset(val);
	printk("Core0 Timer interrupt received\r\n");
}
           

irq部分核心代碼:

#include <arm-gic.h>
#include "io.h"
#include <asm/irq.h>

struct gic_chip_data {
	unsigned long raw_dist_base;
	unsigned long raw_cpu_base;
	struct irq_domain *domain;
	struct irq_chip *chip;
	unsigned int gic_irqs;
};

#define gic_dist_base(d) ((d)->raw_dist_base)
#define gic_cpu_base(d) ((d)->raw_cpu_base)

#define ARM_GIC_MAX_NR 1
static struct gic_chip_data gic_data[ARM_GIC_MAX_NR];

/* IRQs start ID */
#define HW_IRQ_START 16

static unsigned long gic_get_dist_base(void)
{
	struct gic_chip_data *gic = &gic_data[0];

	return gic_dist_base(gic);
}

static unsigned long gic_get_cpu_base(void)
{
	struct gic_chip_data *gic = &gic_data[0];

	return gic_cpu_base(gic);
}

static void gic_set_irq(int irq, unsigned int offset)
{
	unsigned int mask = 1 << (irq % 32);

	writel(mask, gic_get_dist_base() + offset + (irq / 32) * 4);
}

void gicv2_mask_irq(int irq)
{
	gic_set_irq(irq, GIC_DIST_ENABLE_CLEAR);
}

void gicv2_unmask_irq(int irq)
{
	gic_set_irq(irq, GIC_DIST_ENABLE_SET);
}

void gicv2_eoi_irq(int irq)
{
	writel(irq, gic_get_cpu_base() + GIC_CPU_EOI);
}

static unsigned int gic_get_cpumask(struct gic_chip_data *gic)
{
	unsigned long base = gic_dist_base(gic);
	unsigned int mask, i;

	for (i = mask = 0; i < 32; i += 4) {
		mask = readl(base + GIC_DIST_TARGET + i);
		printk("mask:0x%x\n",mask);
		mask |= mask >> 16;
		mask |= mask >> 8;
		printk("----irq[%d],mask:0x%x\n",i,mask);

		if (mask)
			break;
	}

	return mask;
}

static void gic_dist_init(struct gic_chip_data *gic)
{
	unsigned long base = gic_dist_base(gic);
	unsigned int cpumask;
	unsigned int gic_irqs = gic->gic_irqs;
	int i;

	/* 關閉中斷*/
	writel(GICD_DISABLE, base + GIC_DIST_CTRL);

	unsigned int cpu_group=0;

		
	for (i = 0; i < 32; i += 4) {
		cpu_group = readl(base + GIC_DIST_IGROUP+i);
		printk("reg[%d],cpu_group:0x%x\n",i/4,cpu_group); //default for group0
	}

	/* 設定中斷路由:GIC_DIST_TARGET
	 *
	 * 前32個中斷怎麼路由是GIC晶片固定的,是以先讀GIC_DIST_TARGET前面的值
	 * 然後全部填充到 SPI的中斷号 */
	cpumask = gic_get_cpumask(gic);
	cpumask |= cpumask << 8;
	cpumask |= cpumask << 16;
	printk("----cpumask:0x%x\n",cpumask);

	for (i = 32; i < gic_irqs; i += 4)
		;//writel(cpumask, base + GIC_DIST_TARGET + i * 4 / 4);

	/* Set all global interrupts to be level triggered, active low */
	for (i = 32; i < gic_irqs; i += 16)
		//writel(GICD_INT_ACTLOW_LVLTRIG, base + GIC_DIST_CONFIG + i / 4);
		writel(0x1, base + GIC_DIST_CONFIG + i / 4);

	/* Deactivate and disable all 中斷(SGI, PPI, SPI).
	 *
	 * 當注冊中斷的時候才 enable某個一個SPI中斷,例如調用gic_unmask_irq()*/
	for (i = 0; i < gic_irqs; i += 32) {
		writel(GICD_INT_EN_CLR_X32, base +
				GIC_DIST_ACTIVE_CLEAR + i / 8);
		writel(GICD_INT_EN_CLR_X32, base +
				GIC_DIST_ENABLE_CLEAR + i / 8);
	}

	/*打開SGI中斷(0~15),可能SMP會用到*/
	writel(GICD_INT_EN_SET_SGI, base + GIC_DIST_ENABLE_SET);

	/* 打開中斷:Enable group0 and group1 interrupt forwarding.*/
	writel(GICD_ENABLE, base + GIC_DIST_CTRL);
}

void gic_cpu_init(struct gic_chip_data *gic)
{
	int i;
	unsigned long base = gic_cpu_base(gic);
	unsigned long dist_base = gic_dist_base(gic);

	/*
	 * Set priority on PPI and SGI interrupts
	 */
	for (i = 0; i < 32; i += 4)
		//writel(0xa0a0a0a0,dist_base + GIC_DIST_PRI + i * 4 / 4);
		writel(0x30,dist_base + GIC_DIST_PRI + i * 4 / 4);

	//writel(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK);
	writel(0x80, base + GIC_CPU_PRIMASK);

	writel(GICC_ENABLE, base + GIC_CPU_CTRL);
}

void gic_handle_irq(void)
{
	struct gic_chip_data *gic = &gic_data[0];
	unsigned long base = gic_cpu_base(gic);
	unsigned int irqstat, irqnr;

	do {
		irqstat = readl(base + GIC_CPU_INTACK);
		irqnr = irqstat & GICC_IAR_INT_ID_MASK; //get lower 9bits

		if (irqnr == GENERIC_TIMER_IRQ)
			handle_timer_irq();

		gicv2_eoi_irq(irqnr); //write eoi

	} while (0);

}

int gic_init(int chip, unsigned long dist_base, unsigned long cpu_base)
{
	struct gic_chip_data *gic;
	int gic_irqs;
	int virq_base;

	gic = &gic_data[chip];

	gic->raw_cpu_base = cpu_base;
	gic->raw_dist_base = dist_base;

	/* readout how many interrupts are supported*/
	gic_irqs = readl(gic_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
	gic_irqs = (gic_irqs + 1) * 32;
	if (gic_irqs > 1020)
		gic_irqs = 1020;
	gic->gic_irqs = gic_irqs;

	printk("%s: cpu_base:0x%x, dist_base:0x%x, gic_irqs:%d\n",
			__func__, cpu_base, dist_base, gic->gic_irqs);

	gic_dist_init(gic);
	gic_cpu_init(gic);

	return 0;
}


           

繼續閱讀