<kernel v5.4>
Interrupts -9- (GIC v3 Driver)
GIC v3 & GIC v4 호환
GIC v3 드라이버 코드는 GIC v4도 같이 사용한다. (GIC v4 its를 위한 코드가 일부 추가됨)
다음은 rock3399 칩에서 사용하는 gic v3 컨트롤러에 대한 디바이스 트리이다.
arch/arm64/boot/dts/rockchip/rk3399.dtsi
gic: interrupt-controller@fee00000 {
compatible = "arm,gic-v3";
#interrupt-cells = <4>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
interrupt-controller;
reg = <0x0 0xfee00000 0 0x10000>, /* GICD */
<0x0 0xfef00000 0 0xc0000>, /* GICR */
<0x0 0xfff00000 0 0x10000>, /* GICC */
<0x0 0xfff10000 0 0x10000>, /* GICH */
<0x0 0xfff20000 0 0x10000>; /* GICV */
interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH 0>;
its: interrupt-controller@fee20000 {
compatible = "arm,gic-v3-its";
msi-controller;
reg = <0x0 0xfee20000 0x0 0x20000>;
};
ppi-partitions {
ppi_cluster0: interrupt-partition-0 {
affinity = <&cpu_l0 &cpu_l1 &cpu_l2 &cpu_l3>;
};
ppi_cluster1: interrupt-partition-1 {
affinity = <&cpu_b0 &cpu_b1>;
};
};
};
- reg
- 기본적으로 GICD(mandatory), GICR(mandatory), GICC(option), GICH(option), GICV(option) 영역 5개를 사용한다.
- interrupts
- its: interrupt-controller@fee20000
- ppi-partitions
- PPI affnity를 사용하여 파티션을 구성한다.
다음은 hisilicon의 hip07 칩에서 사용하는 gic v3 컨트롤러에 대한 디바이스 트리이다.
arch/arm64/boot/dts/hisilicon/hip07.dtsi
gic: interrupt-controller@4d000000 {
compatible = "arm,gic-v3";
#interrupt-cells = <3>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
interrupt-controller;
#redistributor-regions = <4>;
redistributor-stride = <0x0 0x40000>;
reg = <0x0 0x4d000000 0x0 0x10000>, /* GICD */
<0x0 0x4d100000 0x0 0x400000>, /* p0 GICR node 0 */
<0x0 0x6d100000 0x0 0x400000>, /* p0 GICR node 1 */
<0x400 0x4d100000 0x0 0x400000>, /* p1 GICR node 2 */
<0x400 0x6d100000 0x0 0x400000>, /* p1 GICR node 3 */
<0x0 0xfe000000 0x0 0x10000>, /* GICC */
<0x0 0xfe010000 0x0 0x10000>, /* GICH */
<0x0 0xfe020000 0x0 0x10000>; /* GICV */
interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH>;
...
- #redistributor-regions
- GICR 레지스터 영역이 4개의 노드로 구성됨을 알 수 있다.
- redistributor-stride
- GICR 노드 레지스터간에 패딩 간격 페이지 수를 지정한다. 이 값은 64K의 배수여야 한다.
- reg
- 기본적으로 GICD, GICR, GICC, GICH, GICV 영역 5개를 사용하지만, 이 컨트롤러는 GICR 영역이 1 -> 4개로 확장되었다.
GIC v3 드라이버 초기화
gic_of_init()
drivers/irqchip/irq-gic-v3.c
static int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
void __iomem *dist_base;
struct redist_region *rdist_regs;
u64 redist_stride;
u32 nr_redist_regions;
int err, i;
dist_base = of_iomap(node, 0);
if (!dist_base) {
pr_err("%pOF: unable to map gic dist registers\n", node);
return -ENXIO;
}
err = gic_validate_dist_version(dist_base);
if (err) {
pr_err("%pOF: no distributor detected, giving up\n", node);
goto out_unmap_dist;
}
if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions))
nr_redist_regions = 1;
rdist_regs = kcalloc(nr_redist_regions, sizeof(*rdist_regs),
GFP_KERNEL);
if (!rdist_regs) {
err = -ENOMEM;
goto out_unmap_dist;
}
for (i = 0; i < nr_redist_regions; i++) {
struct resource res;
int ret;
ret = of_address_to_resource(node, 1 + i, &res);
rdist_regs[i].redist_base = of_iomap(node, 1 + i);
if (ret || !rdist_regs[i].redist_base) {
pr_err("%pOF: couldn't map region %d\n", node, i);
err = -ENODEV;
goto out_unmap_rdist;
}
rdist_regs[i].phys_base = res.start;
}
if (of_property_read_u64(node, "redistributor-stride", &redist_stride))
redist_stride = 0;
gic_enable_of_quirks(node, gic_quirks, &gic_data);
err = gic_init_bases(dist_base, rdist_regs, nr_redist_regions,
redist_stride, &node->fwnode);
if (err)
goto out_unmap_rdist;
gic_populate_ppi_partitions(node);
if (static_branch_likely(&supports_deactivate_key))
gic_of_setup_kvm_info(node);
return 0;
out_unmap_rdist:
for (i = 0; i < nr_redist_regions; i++)
if (rdist_regs[i].redist_base)
iounmap(rdist_regs[i].redist_base);
kfree(rdist_regs);
out_unmap_dist:
iounmap(dist_base);
return err;
}
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
GIC v3 인터럽트 컨트롤러 초기화 시작 함수이다. (GIC v4 인터럽트 컨트롤러도 이 함수를 같이 사용한다)
- 코드 라인 9~13에서 Distributor 레지스터 영역인 “reg” 속성의 0번째 주소와 사이즈를 읽어와 가상 주소에 매핑한다.
- 코드 라인 15~19에서 Distributor 레지스터에서 gic v3 또는 v4 여부를 체크한다. (성공 시 0)
- 코드 라인 21~22에서 ReDistributor 영역의 수를 알아오기 위해 “#redistributor-regions” 속성 값을 알아온다. 속성이 없는 경우 디폴트는 1이다.
- 코드 라인 24~29에서 ReDistributor 영역의 수만큼 redist_region 구조체를 할당한다.
- 코드 라인 31~43에서 ReDistributor 영역의 수만큼 “reg” 속성의 두 번째 부터 주소와 사이즈를 읽어 가상 주소에 매핑하고, 매핑한 주소는 rdist_regs[].redist_base에 대입한다. 그리고 rdist_regs[].phys_base에는 물리 주소를 기록한다.
- 코드 라인 45~46에서 GICR 노드 레지스터간에 패딩 간격 페이지 수를 지정하기 위해 “redistributor-stride” 속성 값을 알아온다. 속성이 없는 경우 디폴트는 0이다.
- 코드 라인 48에서 gic 컨트롤러들 중 errata 코드가 있는 경우 이를 호출하여 수행한다.
- 현재 Qualcomm MSM8996, Hisilicon의 hip06 및 hip07이 대상이다.
- 코드 라인 50~53에서 gic 컨트롤러를 초기화한다.
- 코드 라인 55에서 모든 PPI들을 위해 irq 파티션을 생성한다.
- 코드 라인 57~58에서 Guest OS를 위한 KVM 정보를 설정한다.
- 코드 라인 59에서 성공 값 0을 반환한다.
gic_validate_dist_version()
drivers/irqchip/irq-gic-v3.c
static int __init gic_validate_dist_version(void __iomem *dist_base)
{
u32 reg = readl_relaxed(dist_base + GICD_PIDR2) & GIC_PIDR2_ARCH_MASK;
if (reg != GIC_PIDR2_ARCH_GICv3 && reg != GIC_PIDR2_ARCH_GICv4)
return -ENODEV;
return 0;
}
GICD_PIDR2 레지스터의 버전을 읽어 gic v3 또는 v4인지 여부를 알아온다. 성공시 0을 반환한다.
GIC 레지스터들 초기화
gic_init_bases()
drivers/irqchip/irq-gic-v3.c
static int __init gic_init_bases(void __iomem *dist_base,
struct redist_region *rdist_regs,
u32 nr_redist_regions,
u64 redist_stride,
struct fwnode_handle *handle)
{
u32 typer;
int err;
if (!is_hyp_mode_available())
static_branch_disable(&supports_deactivate_key);
if (static_branch_likely(&supports_deactivate_key))
pr_info("GIC: Using split EOI/Deactivate mode\n");
gic_data.fwnode = handle;
gic_data.dist_base = dist_base;
gic_data.redist_regions = rdist_regs;
gic_data.nr_redist_regions = nr_redist_regions;
gic_data.redist_stride = redist_stride;
/*
* Find out how many interrupts are supported.
*/
typer = readl_relaxed(gic_data.dist_base + GICD_TYPER);
gic_data.rdists.gicd_typer = typer;
gic_enable_quirks(readl_relaxed(gic_data.dist_base + GICD_IIDR),
gic_quirks, &gic_data);
pr_info("%d SPIs implemented\n", GIC_LINE_NR - 32);
pr_info("%d Extended SPIs implemented\n", GIC_ESPI_NR);
gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,
&gic_data);
irq_domain_update_bus_token(gic_data.domain, DOMAIN_BUS_WIRED);
gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));
gic_data.rdists.has_vlpis = true;
gic_data.rdists.has_direct_lpi = true;
if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist)) {
err = -ENOMEM;
goto out_free;
}
gic_data.has_rss = !!(typer & GICD_TYPER_RSS);
pr_info("Distributor has %sRange Selector support\n",
gic_data.has_rss ? "" : "no ");
if (typer & GICD_TYPER_MBIS) {
err = mbi_init(handle, gic_data.domain);
if (err)
pr_err("Failed to initialize MBIs\n");
}
set_handle_irq(gic_handle_irq);
gic_update_rdist_properties();
gic_smp_init();
gic_dist_init();
gic_cpu_init();
gic_cpu_pm_init();
if (gic_dist_supports_lpis()) {
its_init(handle, &gic_data.rdists, gic_data.domain);
its_cpu_init();
} else {
if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
gicv2m_init(handle, gic_data.domain);
}
gic_enable_nmi_support();
return 0;
out_free:
if (gic_data.domain)
irq_domain_remove(gic_data.domain);
free_percpu(gic_data.rdists.rdist);
return err;
}
- 코드 라인 10~14에서 하이퍼 모드가 운용가능한 상태가 아니면 supports_deactivate_key를 disable 한다.
- 커널이 하이퍼 모드로 동작하는 경우에 따라 EOI 모드를 priority drop과 deactivate를 split하여 운영한다.
- 예) “GIC: Using split EOI/Deactivate mode”
- 참고: 가상화 지원 (하이퍼 모드) | 문c
- 코드 라인 16~20에서 gic_chip_data 구조체 형태인 전역 gic_data에 인자로 전달된 값들로 초기화한다.
- 코드 라인 25~26에서 GICD_TYPER 레지스터 값을 저장한다.
- 코드 라인 28~29에서 GICD_IIDR 레지스터 값을 읽어 관련 디바이스에 대해서만 errtum 코드를 수행한다.
- 코드 라인 31~32에서 SPI와 확장 SPI 수 정보를 출력한다.
- 코드 라인 33~35에서 하이라키를 지원하는 irq 도메인을 생성하고, DOMAIN_BUS_WIRED 도메인 타입으로 지정한다.
- 코드 라인 36~43에서 Redistributor용 per-cpu 자료 구조를 할당 받고 초기화한다.
- 가상 LPI 지원과 Direct LPI Injection을 true로 미리 설정한다.
- 추후 Redistributor 레지스터들을 읽어 하나라도 VLPI 및 DirectLPI 값이 0인 경우 false로 바뀐다.
- 코드 라인 45~47에서 GICD_TYPER.RSS 비트를 읽어 rss 지원 여부를 알아내고 출력한다.
- 코드 라인 49~53에서 GICD_TYPER.MBIS 비트를 읽어 메시지 기반 인터럽트의 지원하는 경우 mbi 관련 초기화 루틴을 수행한다.
- 코드 라인 55에서 이 컨트롤러에서 관리를 위해 사용하는 인터럽트의 핸들러 함수를 지정한다.
- 코드 라인 57에서 Redistributor 속성을 읽어 갱신한다.
- 코드 라인 59에서 SMP를 지원하기 위한 초기화를 수행한다.
- 코드 라인 60에서 Distributtor 레지스터를 사용한 초기화를 수행한다.
- 코드 라인 61에서 CPU interface 레지스터를 사용한 초기화를 수행한다.
- 코드 라인 62에서 CPU 절전(suspen/resume) 관련한 초기화를 수행한다.
- 코드 라인 64~70에서 LPI를 지원하는 경우 ITS츨 초기화한다. 지원하지 않는 경우 GIC 가상화 지원 초기화를 수행한다.
- 코드 라인 72에서 Pesudo-NNI 기능을 지원하는 경우 이에 대한 초기화를 수행한다.
- 코드 라인 74에서 성공 값 0을 반환한다.
GIC Quirks(ERRATA) 초기화 및 적용
gic_enable_of_quirks()
drivers/irqchip/irq-gic-common.c
void gic_enable_of_quirks(const struct device_node *np,
const struct gic_quirk *quirks, void *data)
{
for (; quirks->desc; quirks++) {
if (!of_device_is_compatible(np, quirks->compatible))
continue;
if (quirks->init(data))
pr_info("GIC: enabling workaround for %s\n",
quirks->desc);
}
}
디바이스 트리로 지정된 디바이스만 erratum 적용한다.
gic_enable_quirks()
drivers/irqchip/irq-gic-common.c
void gic_enable_quirks(u32 iidr, const struct gic_quirk *quirks,
void *data)
{
for (; quirks->desc; quirks++) {
if (quirks->compatible)
continue;
if (quirks->iidr != (quirks->mask & iidr))
continue;
if (quirks->init(data))
pr_info("GIC: enabling workaround for %s\n",
quirks->desc);
}
}
@iidr 값이 일치하는 디바이스만 erratum 적용한다.
gic_quirks
static const struct gic_quirk gic_quirks[] = {
{
.desc = "GICv3: Qualcomm MSM8996 broken firmware",
.compatible = "qcom,msm8996-gic-v3",
.init = gic_enable_quirk_msm8996,
},
{
.desc = "GICv3: HIP06 erratum 161010803",
.iidr = 0x0204043b,
.mask = 0xffffffff,
.init = gic_enable_quirk_hip06_07,
},
{
.desc = "GICv3: HIP07 erratum 161010803",
.iidr = 0x00000000,
.mask = 0xffffffff,
.init = gic_enable_quirk_hip06_07,
},
{
}
};
버그 등으로 인해 erratum 코드가 적용되어야 할 디바이스들이다.
SMP core들 시작시 호출될 함수 지정
gic_smp_init()
drivers/irqchip/irq-gic-v3.c
static void gic_smp_init(void)
{
set_smp_cross_call(gic_raise_softirq);
cpuhp_setup_state_nocalls(CPUHP_AP_IRQ_GIC_STARTING,
"irqchip/arm/gicv3:starting",
gic_starting_cpu, NULL);
}
새로운 cpu가 online될 때 gic에 알려야 할 함수를 지정한다.
gic_starting_cpu()
drivers/irqchip/irq-gic-v3.c
static int gic_starting_cpu(unsigned int cpu)
{
gic_cpu_init();
if (gic_dist_supports_lpis())
its_cpu_init();
return 0;
}
새로운 cpu가 online될 때 콜백 호출되는 함수이다. 해당 cpu interface 레지스터들을 초기화하고, LPI를 지원하는 경우 ITS 초기화 함수를 수행한다.
Distributor 초기화
gic_dist_init()
drivers/irqchip/irq-gic-v3.c
static void __init gic_dist_init(void)
{
unsigned int i;
u64 affinity;
void __iomem *base = gic_data.dist_base;
/* Disable the distributor */
writel_relaxed(0, base + GICD_CTLR);
gic_dist_wait_for_rwp();
/*
* Configure SPIs as non-secure Group-1. This will only matter
* if the GIC only has a single security state. This will not
* do the right thing if the kernel is running in secure mode,
* but that's not the intended use case anyway.
*/
for (i = 32; i < GIC_LINE_NR; i += 32)
writel_relaxed(~0, base + GICD_IGROUPR + i / 8);
/* Extended SPI range, not handled by the GICv2/GICv3 common code */
for (i = 0; i < GIC_ESPI_NR; i += 32) {
writel_relaxed(~0U, base + GICD_ICENABLERnE + i / 8);
writel_relaxed(~0U, base + GICD_ICACTIVERnE + i / 8);
}
for (i = 0; i < GIC_ESPI_NR; i += 32)
writel_relaxed(~0U, base + GICD_IGROUPRnE + i / 8);
for (i = 0; i < GIC_ESPI_NR; i += 16)
writel_relaxed(0, base + GICD_ICFGRnE + i / 4);
for (i = 0; i < GIC_ESPI_NR; i += 4)
writel_relaxed(GICD_INT_DEF_PRI_X4, base + GICD_IPRIORITYRnE + i);
/* Now do the common stuff, and wait for the distributor to drain */
gic_dist_config(base, GIC_LINE_NR, gic_dist_wait_for_rwp);
/* Enable distributor with ARE, Group1 */
writel_relaxed(GICD_CTLR_ARE_NS | GICD_CTLR_ENABLE_G1A | GICD_CTLR_ENABLE_G1,
base + GICD_CTLR);
/*
* Set all global interrupts to the boot CPU only. ARE must be
* enabled.
*/
affinity = gic_mpidr_to_affinity(cpu_logical_map(smp_processor_id()));
for (i = 32; i < GIC_LINE_NR; i++)
gic_write_irouter(affinity, base + GICD_IROUTER + i * 8);
for (i = 0; i < GIC_ESPI_NR; i++)
gic_write_irouter(affinity, base + GICD_IROUTERnE + i * 8);
}
GIC Distributor를 초기화한다.
- 코드 라인 8~9에서 Distributor를 disable하기 위해 GICD_CTRL 값에 0을 기록한다.
- 코드 라인 17~18에서 모든 SPI들을 non-secure group 1으로 설정하기위해 GICD_IGROUPRn들을 모두 0xffff_ffff(1 비트당 1 인터럽트)로 설정한다.
- one security states에서 리눅스 커널이 동작하는 경우 그룹 제어가 가능하다.
- 그러나 two secure states인 경우 그룹 설정은 secure에서 해야 하기 때문에 non-secure에서 동작하느 리눅스 커널에서 제어는 의미 없게 된다.
- 코드 라인 21~24에서 모든 확장 SPI들을 disable 및 deactivate 요청하기 위해 GICD_ICENABLERn 과 GICD_ICACTIVERn들을 모두 0xffff_ffff(1 비트당 1 인터럽트)로 설정한다.
- 코드 라인 26~27에서 모든 확장 SPI들을 non-secure group 1으로 설정하기위해 GICD_IGROUPRnE들을 모두 0xffff_ffff(1 비트당 1 인터럽트)로 설정한다.
- 코드 라인 29~30에서 모든 확장 SPI들을 레벨 sensitive 모드로 설정하기 위해 GICD_ICFGRnE들을 모두 0x0(1 비트당 1 인터럽트)으로 설정한다.
- 코드 라인 32~33에서 모든 확장 SPI들에 대해 우선 순위를 0xd0으로 설정하기 위해 GICD_IPRIORITYRnE에 0xa0을 기록한다. (8 비트당 1 인터럽트)
- non-secure에서 설정한 0xa0은 cpu interface로 올라갈 때 내부 변환하면 0xa0 >> 1 | 0x80 = 0xd0에 해당하는 우선 순위이다.
- 코드 라인 36에서 GIC v1 이상에서 동작하는 공통 Distributor 설정 코드를 수행한다.
- 코드 라인 39~40에서 Distributor를 활성화할 때 ARE(Affinity Routing Enable), group 1도 활성화한다.
- 코드 라인 46에서 boot cpu의 affinity 값들을 읽어오기 위해 MPIDR 레지스터를 알아온다.
- 코드 라인 47~51에서 모든 SPI들 및 확장 SPI들을 대상으로 boot cpu로 라우팅 설정하기 위해 읽어온 affinity 값을 GICD_ROUTERn 및 GICD_ROUTERRnE에 기록한다.
gic_dist_wait_for_rwp()
drivers/irqchip/irq-gic-v3.c
/* Wait for completion of a distributor change */
static void gic_dist_wait_for_rwp(void)
{
gic_do_wait_for_rwp(gic_data.dist_base);
}
distributor 레지스터에 기록한 후 이의 수행이 완료될 때까지 대기한다.
gic_do_wait_for_rwp()
drivers/irqchip/irq-gic-v3.c
static void gic_do_wait_for_rwp(void __iomem *base)
{
u32 count = 1000000; /* 1s! */
while (readl_relaxed(base + GICD_CTLR) & GICD_CTLR_RWP) {
count--;
if (!count) {
pr_err_ratelimited("RWP timeout, gone fishing\n");
return;
}
cpu_relax();
udelay(1);
};
}
GICD_CTLR.RWP 레지스터를 읽어 0이될 때까지 반복한다.
GIC v1 이상 공통 Distributor 설정
gic_dist_config()
drivers/irqchip/irq-gic-v3.c
void gic_dist_config(void __iomem *base, int gic_irqs,
void (*sync_access)(void))
{
unsigned int i;
/*
* Set all global interrupts to be level triggered, active low.
*/
for (i = 32; i < gic_irqs; i += 16)
writel_relaxed(GICD_INT_ACTLOW_LVLTRIG,
base + GIC_DIST_CONFIG + i / 4);
/*
* Set priority on all global interrupts.
*/
for (i = 32; i < gic_irqs; i += 4)
writel_relaxed(GICD_INT_DEF_PRI_X4, base + GIC_DIST_PRI + i);
/*
* Deactivate and disable all SPIs. Leave the PPI and SGIs
* alone as they are in the redistributor registers on GICv3.
*/
for (i = 32; i < gic_irqs; i += 32) {
writel_relaxed(GICD_INT_EN_CLR_X32,
base + GIC_DIST_ACTIVE_CLEAR + i / 8);
writel_relaxed(GICD_INT_EN_CLR_X32,
base + GIC_DIST_ENABLE_CLEAR + i / 8);
}
if (sync_access)
sync_access();
}
모든 GIC 버전에서 동작하는 공통 Distributor 설정 코드를 수행한다.
- 코드 라인 9~11에서 모든 SPI들에 대한 low active 레벨 sensitive 설정을 위해 GICD_ICFGRn에 0x0을 기록한다. (2 비트당 1 인터럽트)
- 코드 라인 16~17에서 모든 SPI들에 대해 우선 순위를 0xd0으로 설정하기 위해 GICD_IPRIORITYRn에 0xa0을 기록한다. (8 비트당 1 인터럽트)
- non-secure에서 설정한 0xa0은 cpu interface로 올라갈 때 내부 변환하면 0xa0 >> 1 | 0x80 = 0xd0에 해당하는 우선 순위이다.
- 코드 라인 23~28에서 모든 확장 SPI들을 disable 및 deactivate 요청하기 위해 GICD_ICENABLERn 과 GICD_ICACTIVERn들을 모두 0xffff_ffff(1 비트당 1 인터럽트)로 설정한다.
- 코드 라인 30~31 Distributor 레지스터 적용이 완료될 때까지 대기하도록 인자로 전달받은 @sync_access 함수를 호출한다.
CPU Interface 관련 초기화
gic_cpu_init()
drivers/irqchip/irq-gic-v3.c
static void gic_cpu_init(void)
{
void __iomem *rbase;
int i;
/* Register ourselves with the rest of the world */
if (gic_populate_rdist())
return;
gic_enable_redist(true);
WARN((gic_data.ppi_nr > 16 || GIC_ESPI_NR != 0) &&
!(gic_read_ctlr() & ICC_CTLR_EL1_ExtRange),
"Distributor has extended ranges, but CPU%d doesn't\n",
smp_processor_id());
rbase = gic_data_rdist_sgi_base();
/* Configure SGIs/PPIs as non-secure Group-1 */
for (i = 0; i < gic_data.ppi_nr + 16; i += 32)
writel_relaxed(~0, rbase + GICR_IGROUPR0 + i / 8);
gic_cpu_config(rbase, gic_data.ppi_nr + 16, gic_redist_wait_for_rwp);
/* initialise system registers */
gic_cpu_sys_reg_init();
}
CPU interface를 초기화한다.
- 코드 라인 7~10에서 Redistributor를 활성화한다.
- 코드 라인 17에서 Redistributor에서 SGI 베이스 레지스터 가상 주소를 알아온다.
- 코드 라인 20~21에서 SGI/PPI들을 모두 non-secure group 1으로 설정하기 위해 GICR_IGROUP0Rn에 0xffff_ffff(1 비트당 1 인터럽트)를 기록한다.
- 코드 라인 23에서 모든 버전의 GIC를 위한 공통 CPU 설정 코드를 수행한다.
- 코드 라인 26에서 시스템 레지스터를 초기화한다.
GIC v1 이상 공통 CPU 관련 설정
gic_cpu_config()
drivers/irqchip/irq-gic-common.c
void gic_cpu_config(void __iomem *base, int nr, void (*sync_access)(void))
{
int i;
/*
* Deal with the banked PPI and SGI interrupts - disable all
* private interrupts. Make sure everything is deactivated.
*/
for (i = 0; i < nr; i += 32) {
writel_relaxed(GICD_INT_EN_CLR_X32,
base + GIC_DIST_ACTIVE_CLEAR + i / 8);
writel_relaxed(GICD_INT_EN_CLR_X32,
base + GIC_DIST_ENABLE_CLEAR + i / 8);
}
/*
* Set priority on PPI and SGI interrupts
*/
for (i = 0; i < nr; i += 4)
writel_relaxed(GICD_INT_DEF_PRI_X4,
base + GIC_DIST_PRI + i * 4 / 4);
/* Ensure all SGI interrupts are now enabled */
writel_relaxed(GICD_INT_EN_SET_SGI, base + GIC_DIST_ENABLE_SET);
if (sync_access)
sync_access();
}
모든 GIC 버전에서 동작하는 공통 CPU 관련 설정을 수행한다.
- 코드 라인 9~14에서 모든 SGI/PPI들을 disable 및 deactivate 요청하기 위해 GICD_ICENABLERn 과 GICD_ICACTIVERn들을 모두 0xffff_ffff(1 비트당 1 인터럽트)로 설정한다.
- 코드 라인 19~21에서 모든 SGI/PPI들에 대해 우선 순위를 0xd0으로 설정하기 위해 GICD_IPRIORITYRn에 0xa0을 기록한다. (8 비트당 1 인터럽트)
- non-secure에서 설정한 0xa0은 cpu interface로 올라갈 때 내부 변환하면 0xa0 >> 1 | 0x80 = 0xd0에 해당하는 우선 순위이다.
- 코드 라인 24에서 모든 SGI들을 enable 요청하기 위해 GICD_ISENABLERn들을 모두 0xffff_ffff(1 비트당 1 인터럽트)로 설정한다.
- 코드 라인 26~27에서 Distributor 레지스터 적용이 완료될 때까지 대기하도록 인자로 전달받은 @sync_access 함수를 호출한다.
gic_cpu_sys_reg_init()
drivers/irqchip/irq-gic-v3.c -1/2-
static void gic_cpu_sys_reg_init(void)
{
int i, cpu = smp_processor_id();
u64 mpidr = cpu_logical_map(cpu);
u64 need_rss = MPIDR_RS(mpidr);
bool group0;
u32 pribits;
/*
* Need to check that the SRE bit has actually been set. If
* not, it means that SRE is disabled at EL2. We're going to
* die painfully, and there is nothing we can do about it.
*
* Kindly inform the luser.
*/
if (!gic_enable_sre())
pr_err("GIC: unable to set SRE (disabled at EL2), panic ahead\n");
pribits = gic_get_pribits();
group0 = gic_has_group0();
/* Set priority mask register */
if (!gic_prio_masking_enabled()) {
write_gicreg(DEFAULT_PMR_VALUE, ICC_PMR_EL1);
} else {
/*
* Mismatch configuration with boot CPU, the system is likely
* to die as interrupt masking will not work properly on all
* CPUs
*/
WARN_ON(gic_supports_nmi() && group0 &&
!gic_dist_security_disabled());
}
/*
* Some firmwares hand over to the kernel with the BPR changed from
* its reset value (and with a value large enough to prevent
* any pre-emptive interrupts from working at all). Writing a zero
* to BPR restores is reset value.
*/
gic_write_bpr1(0);
if (static_branch_likely(&supports_deactivate_key)) {
/* EOI drops priority only (mode 1) */
gic_write_ctlr(ICC_CTLR_EL1_EOImode_drop);
} else {
/* EOI deactivates interrupt too (mode 0) */
gic_write_ctlr(ICC_CTLR_EL1_EOImode_drop_dir);
}
GIC 시스템 레지스터들을 초기화한다.
- 코드 라인 3~5에서 현재 cpu에 대한 mpidr 값을 알아와서 aff0 값이 16을 초과한 경우 need_rss에 1을 대입한다.
- aff0이 16 이상이라는 의미는 최하위 레벨에서 16개 이상의 cpu가 사용되므로 RSS(Range Select Support)=1이되어 최하위 affnity 레벨의 cpu 범위가 0~255까지 라는 의미이다.
- 코드 라인 16~17에서 SRE(System Register Enable)이 0인 경우 에러를 출력한다.
- GICv3 하드웨어가 GIC v1~v2 드라이버 코드를 사용하는 경우 SRE=0로 설정하지만, GIC v3 하드웨어에 GIC v3 드라이버 코드를 사용하는 경우 SRE=1로 하여 시스템 레지스터를 사용하는 것이 정확하고, 또 성능에도 유리하다.
- 코드 라인 19에서 pribits 값을 알아온다.
- ICC_CTLR_EL1.PRIbits + 1 값
- 코드 라인 21에서 커널이 group 0를 사용 가능한지 여부를 알아온다.
- 코드 라인 24~34에서 Pesudo-NMI 기능을 위한 priority masking 기능이 활성화되지 않은 경우 디폴트 priority mask 값 0xf0을 ICC_PMR_EL1.PMR 레지스터에 기록한다.
- 코드 라인 42에서 펌웨어에서 Binary Point 값을 설정하였을 수도 있으므로 커널에선 이를 0으로 기록하여 리셋되는 기능이 있다.
- 코드 라인 44~50에서 EL2 부트 여부에 따라 적절한 EOImode를 설정한다.
- EL2 모드에서 리눅스 커널이 부팅한 경우 호스트 OS로 동작하는 경우이다. 이러한 경우 Guest OS를 지원하기 위해 Host OS는 priority drop과 deactivate를 분리한(ICC_CTLR_EL1_EOImode_drop) EOI 모드를 사용한다.
drivers/irqchip/irq-gic-v3.c -2/2-
/* Always whack Group0 before Group1 */
if (group0) {
switch(pribits) {
case 8:
case 7:
write_gicreg(0, ICC_AP0R3_EL1);
write_gicreg(0, ICC_AP0R2_EL1);
/* Fall through */
case 6:
write_gicreg(0, ICC_AP0R1_EL1);
/* Fall through */
case 5:
case 4:
write_gicreg(0, ICC_AP0R0_EL1);
}
isb();
}
switch(pribits) {
case 8:
case 7:
write_gicreg(0, ICC_AP1R3_EL1);
write_gicreg(0, ICC_AP1R2_EL1);
/* Fall through */
case 6:
write_gicreg(0, ICC_AP1R1_EL1);
/* Fall through */
case 5:
case 4:
write_gicreg(0, ICC_AP1R0_EL1);
}
isb();
/* ... and let's hit the road... */
gic_write_grpen1(1);
/* Keep the RSS capability status in per_cpu variable */
per_cpu(has_rss, cpu) = !!(gic_read_ctlr() & ICC_CTLR_EL1_RSS);
/* Check all the CPUs have capable of sending SGIs to other CPUs */
for_each_online_cpu(i) {
bool have_rss = per_cpu(has_rss, i) && per_cpu(has_rss, cpu);
need_rss |= MPIDR_RS(cpu_logical_map(i));
if (need_rss && (!have_rss))
pr_crit("CPU%d (%lx) can't SGI CPU%d (%lx), no RSS\n",
cpu, (unsigned long)mpidr,
i, (unsigned long)cpu_logical_map(i));
}
/**
* GIC spec says, when ICC_CTLR_EL1.RSS==1 and GICD_TYPER.RSS==0,
* writing ICC_ASGI1R_EL1 register with RS != 0 is a CONSTRAINED
* UNPREDICTABLE choice of :
* - The write is ignored.
* - The RS field is treated as 0.
*/
if (need_rss && (!gic_data.has_rss))
pr_crit_once("RSS is required but GICD doesn't support it\n");
}
- 코드 라인 2~18에서 커널에서 group 0을 사용가능한 경우 pribits에 따라4 개의 Activate Priorities Group 0 레지스터값에 0을 기록하여 리셋하여 사용한다.
- ICC_AP0Rn_EL1 레지스터 4개는 EL2에서 physical fiq를 수신하지 않도록 설정된 경우(EL2.HCR_EL2.FMO=0)에만 non-secure EL1 커널에서 access 가능하다.
- 코드 라인 20~34에서 pribits에 따라4 개의 Activate Priorities Group 1 레지스터값에 0을 기록하여 리셋하여 사용한다.
- ICC_AP1Rn_EL1 레지스터 4개는 EL2에서 physical irq를 수신하지 않도록 설정된 경우(EL2.HCR_EL2.IMO=0)에만 non-secure EL1 커널에서 access 가능하다.
- 코드 라인 37에서 non-secure 커널에서 group1 인터럽트를 활성화하기위해 SYS_ICC_IGRPEN1_EL1에 1을 기록한다.
- 코드 라인 40에서 per-cpu has_rss 변수에 RSS(Range Select Support) 값을 알아온다.
- SGI 사용시 0인 경우 0~15 cpu, 1인 경우 0~255 cpu
- 코드 라인 43~51에서 cpu mpidr 값에서 affinity 0에 해당하는 cpu 번호가 RSS 범위를 벗어나는 경우 이를 체크하여 에러 로그를 출력한다.
- 코드 라인 60~61에서 affinity 0 레벨에서 0~255 cpu 범위를 사용하는데 GIC가 지원하지 않는 경우 에러 로그를 출력한다.
gic_enable_sre()
include/linux/irqchip/arm-gic-v3.h
static inline bool gic_enable_sre(void)
{
u32 val;
val = gic_read_sre();
if (val & ICC_SRE_EL1_SRE)
return true;
val |= ICC_SRE_EL1_SRE;
gic_write_sre(val);
val = gic_read_sre();
return !!(val & ICC_SRE_EL1_SRE);
}
GIC 시스템 레지스터를 사용할 수 있도록 활성화한다.
- ICC_SRE_EL1.SRE=1로 활성화한다.
gic_get_pribits()
drivers/irqchip/irq-gic-v3.c
static u32 gic_get_pribits(void)
{
u32 pribits;
pribits = gic_read_ctlr();
pribits &= ICC_CTLR_EL1_PRI_BITS_MASK;
pribits >>= ICC_CTLR_EL1_PRI_BITS_SHIFT;
pribits++;
return pribits;
}
priority 레벨링의 단계에 사용되는 비트 수를 알아온다.
- ICC_CTRL_EL1.PRIbits + 1
- 이 값이 4인 경우 -> pribits=5가 되고 2^5=32가 된다. (이 칩은 총 32 단계의 priority 레벨을 운영한다.)
gic_has_group0()
drivers/irqchip/irq-gic-v3.c
static bool gic_has_group0(void)
{
u32 val;
u32 old_pmr;
old_pmr = gic_read_pmr();
/*
* Let's find out if Group0 is under control of EL3 or not by
* setting the highest possible, non-zero priority in PMR.
*
* If SCR_EL3.FIQ is set, the priority gets shifted down in
* order for the CPU interface to set bit 7, and keep the
* actual priority in the non-secure range. In the process, it
* looses the least significant bit and the actual priority
* becomes 0x80. Reading it back returns 0, indicating that
* we're don't have access to Group0.
*/
gic_write_pmr(BIT(8 - gic_get_pribits()));
val = gic_read_pmr();
gic_write_pmr(old_pmr);
return val != 0;
}
EL1에서 동작하는 리눅스 커널에서 group 0를 사용할 수 있는지 여부를 알아온다.
- group 0가 EL3에서 통제되는 경우 EL1 에서 동작하는 커널에서 최고 높은 우선 순위를 기록하고 읽을 때 그 값이 읽히는 것이 아니라 0이 읽힌다.
- pribits가 5인 경우 32단계로 priority 레벨링이 동작한다. 그 중 0x00을 제외한 가장 높은 우선 순위인 0x08을 기록해본다.
- 0x00, 0x08, 0x10, 0x18, 0x20, … 0xf8
gic_prio_masking_enabled()
arch/arm64/include/asm/arch_gicv3.h
static inline bool gic_prio_masking_enabled(void)
{
return system_uses_irq_prio_masking();
}
Pesudo-NMI 기능을 위해 시스템이 irq masking 기능을 사용할 수 있는 경우 true를 반환한다.
system_uses_irq_prio_masking()
arch/arm64/include/asm/cpufeature.h
static inline bool system_uses_irq_prio_masking(void)
{
return IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) &&
cpus_have_const_cap(ARM64_HAS_IRQ_PRIO_MASKING);
}
CONFIG_ARM64_PSEUDO_NMI 커널 옵션과 IRQ_PRIO_MASKING capability를 가진 경우에 true를 반환한다.
Redistributor 초기화
Redistributor 속성들 갱신
gic_update_rdist_properties()
drivers/irqchip/irq-gic-v3.c
static void gic_update_rdist_properties(void)
{
gic_data.ppi_nr = UINT_MAX;
gic_iterate_rdists(__gic_update_rdist_properties);
if (WARN_ON(gic_data.ppi_nr == UINT_MAX))
gic_data.ppi_nr = 0;
pr_info("%d PPIs implemented\n", gic_data.ppi_nr);
pr_info("%sVLPI support, %sdirect LPI support\n",
!gic_data.rdists.has_vlpis ? "no " : "",
!gic_data.rdists.has_direct_lpi ? "no " : "");
}
Redistributor들에서 몇 가지 속성을 갱신한다. 그런 후 관련 속성을 정보 출력한다.
gic_iterate_rdists()
drivers/irqchip/irq-gic-v3.c
static int gic_iterate_rdists(int (*fn)(struct redist_region *, void __iomem *))
{
int ret = -ENODEV;
int i;
for (i = 0; i < gic_data.nr_redist_regions; i++) {
void __iomem *ptr = gic_data.redist_regions[i].redist_base;
u64 typer;
u32 reg;
reg = readl_relaxed(ptr + GICR_PIDR2) & GIC_PIDR2_ARCH_MASK;
if (reg != GIC_PIDR2_ARCH_GICv3 &&
reg != GIC_PIDR2_ARCH_GICv4) { /* We're in trouble... */
pr_warn("No redistributor present @%p\n", ptr);
break;
}
do {
typer = gic_read_typer(ptr + GICR_TYPER);
ret = fn(gic_data.redist_regions + i, ptr);
if (!ret)
return 0;
if (gic_data.redist_regions[i].single_redist)
break;
if (gic_data.redist_stride) {
ptr += gic_data.redist_stride;
} else {
ptr += SZ_64K * 2; /* Skip RD_base + SGI_base */
if (typer & GICR_TYPER_VLPIS)
ptr += SZ_64K * 2; /* Skip VLPI_base + reserved page */
}
} while (!(typer & GICR_TYPER_LAST));
}
return ret ? -ENODEV : 0;
}
Redistributor들에서 몇 가지 속성을 갱신한다.
- 코드 라인 6~16에서 Redistributor 영역 수 만큼 순회하며 GICR_PIDR2.ARCH 값을 읽어 GIC v3 및 v4가 아닌 경우 경고 메시지를 출력하고 에러를 반환한다.
- 코드 라인 18~22에서 Redistributor 레지스터를 읽어 gic 설정 값을 갱신하기 위해 인자로 전달받은 *fn함수를 호출한다.
- GIC v3에서 전달한 함수: __gic_update_rdist_properties()
- 코드 라인 24~25에서 ACPI를 통해 하나의 Residtributor만 구현되었다고 지정된 경우 처리를 중간하고 함수를 빠져나간다.
- 코드 라인 27~33에서 Resistributor 레지스터들 간에 redist_stride 값 만큼 갭을 둔다. 단 redist_stride가 설정되지 않은 경우 128K/256K(LPIS 지원시) 만큼 갭을 둔다.
- 코드 라인 34에서 GICR_TYPER.LAST 비트가 0이면 아직 끝나지 않았으므로 다음을 처리하기 위해 계속 루프를 돈다.
다음 그림은 4개의 노드에 각각 16개의 cpu를 사용하여 Redistributor를 구성한 모습을 보여준다.
- Redistributor 레지스터 세트는 각각 256K를 사용하였고, 전체 64 세트이다. (4 redistributor regions * 16 cpus)
다음 그림은 1개의 노드에 big/little 클러스터가 같이 구현되어 있는 Redistributor를 구성한 모습과 ppi 파티션이 구성된 모습을 보여준다.
- Redistributor 레지스터 세트는 각각 128K를 사용하였고, 전체 6 세트이다. (1 redistributor regions * 6 cpus)
- 2 개의 파티션에 각각 4개 cpu와 2개 cpu가 구성되어 있다.
__gic_update_rdist_properties()
drivers/irqchip/irq-gic-v3.c
static int __gic_update_rdist_properties(struct redist_region *region,
void __iomem *ptr)
{
u64 typer = gic_read_typer(ptr + GICR_TYPER);
gic_data.rdists.has_vlpis &= !!(typer & GICR_TYPER_VLPIS);
gic_data.rdists.has_direct_lpi &= !!(typer & GICR_TYPER_DirectLPIS);
gic_data.ppi_nr = min(GICR_TYPER_NR_PPIS(typer), gic_data.ppi_nr);
return 1;
}
Redistributor 레지스터를 읽어 속성 갑을 갱신한다.
- 코드 라인 4에서 Redistributor 레지스터 베이스를 가리키는 ptr을 기반으로 GICR_TYPER 레지스터 값을 읽어온다.
- 코드 라인 5에서 GICR_TYPER.VLPIS가 하나라도 0이면 false 설정으로 바꾼다.
- 코드 라인 6에서 GICR_TYPER.DirectLPIS가 하나라도 0이면 false 설정으로 바꾼다.
- 코드 라인 7에서 GIC를 통해 읽은 PPI 수와 GICR을 통해 읽은 PPI 수 중 작은 값으로 ppi_nr에 대입한다.
Redistributor 활성화
gic_populate_rdist()
drivers/irqchip/irq-gic-v3.c
static int gic_populate_rdist(void)
{
if (gic_iterate_rdists(__gic_populate_rdist) == 0)
return 0;
/* We couldn't even deal with ourselves... */
WARN(true, "CPU%d: mpidr %lx has no re-distributor!\n",
smp_processor_id(),
(unsigned long)cpu_logical_map(smp_processor_id()));
return -ENODEV;
}
Redistributor들에서 cpu의 affinity와 동일한 경우 rd_base와 phys_base를 갱신한다.
__gic_populate_rdist()
drivers/irqchip/irq-gic-v3.c
static int __gic_populate_rdist(struct redist_region *region, void __iomem *ptr)
{
unsigned long mpidr = cpu_logical_map(smp_processor_id());
u64 typer;
u32 aff;
/*
* Convert affinity to a 32bit value that can be matched to
* GICR_TYPER bits [63:32].
*/
aff = (MPIDR_AFFINITY_LEVEL(mpidr, 3) << 24 |
MPIDR_AFFINITY_LEVEL(mpidr, 2) << 16 |
MPIDR_AFFINITY_LEVEL(mpidr, 1) << 8 |
MPIDR_AFFINITY_LEVEL(mpidr, 0));
typer = gic_read_typer(ptr + GICR_TYPER);
if ((typer >> 32) == aff) {
u64 offset = ptr - region->redist_base;
gic_data_rdist_rd_base() = ptr;
gic_data_rdist()->phys_base = region->phys_base + offset;
pr_info("CPU%d: found redistributor %lx region %d:%pa\n",
smp_processor_id(), mpidr,
(int)(region - gic_data.redist_regions),
&gic_data_rdist()->phys_base);
return 0;
}
/* Try next one */
return 1;
}
MPIDR을 통해 알아온 4단계 affinity 값과 GICR_TYPER을 통해 읽어온 4 단계 affinity 값이 같은 경우에 한해 rd_base와 phys_base를 갱신하고 로그 정보를 출력한다. 성공 시 0을 반환한다.
- GICR_TYPER 레지스터는 64비트 값을 가지고 있고 bits[61:32]에 4단계 affinity 값이 담겨있다.
gic_enable_redist()
drivers/irqchip/irq-gic-v3.c
static void gic_enable_redist(bool enable)
{
void __iomem *rbase;
u32 count = 1000000; /* 1s! */
u32 val;
if (gic_data.flags & FLAGS_WORKAROUND_GICR_WAKER_MSM8996)
return;
rbase = gic_data_rdist_rd_base();
val = readl_relaxed(rbase + GICR_WAKER);
if (enable)
/* Wake up this CPU redistributor */
val &= ~GICR_WAKER_ProcessorSleep;
else
val |= GICR_WAKER_ProcessorSleep;
writel_relaxed(val, rbase + GICR_WAKER);
if (!enable) { /* Check that GICR_WAKER is writeable */
val = readl_relaxed(rbase + GICR_WAKER);
if (!(val & GICR_WAKER_ProcessorSleep))
return; /* No PM support in this redistributor */
}
while (--count) {
val = readl_relaxed(rbase + GICR_WAKER);
if (enable ^ (bool)(val & GICR_WAKER_ChildrenAsleep))
break;
cpu_relax();
udelay(1);
};
if (!count)
pr_err_ratelimited("redistributor failed to %s...\n",
enable ? "wakeup" : "sleep");
}
Redistributor를 깨우거나 슬립시킨다. @enable=1일 때 깨우고, @enable=0일 때 슬립한다.
- 코드 라인 7~8에서 FLAGS_WORKAROUND_GICR_WAKER_MSM8996 워크어라운드 플래그가 설정된 경우 함수를 빠져나간다.
- 코드 라인 10~18에서 GICR_WAKER.ProcessorSleep 필드를 제거(@enable=1)하거나 추가(@enable=0)한다.
- 코드 라인 20~24에서 @enable=0일 때 GICR_WAKER.ProcessorSleep 을 다시 읽어 0인 경우 PM을 지원하지 않으므로 함수를 빠져나간다.
- 코드 라인 26~32에서 최대 1초간 busy wait하며 GICR_WAKER.ChildrenAsleep을 읽은 값과 enable이 서로 다른 경우 루프를 벗어난다.
- 코드 라인 33~35에서 1초 동안 설정되지 않은 경우 “Redistributor failed to (wakeup|sleep)”을 출력한다.
KVM 지원
gic_of_setup_kvm_info()
irq-gic-v3.c
static void __init gic_of_setup_kvm_info(struct device_node *node)
{
int ret;
struct resource r;
u32 gicv_idx;
gic_v3_kvm_info.type = GIC_V3;
gic_v3_kvm_info.maint_irq = irq_of_parse_and_map(node, 0);
if (!gic_v3_kvm_info.maint_irq)
return;
if (of_property_read_u32(node, "#redistributor-regions",
&gicv_idx))
gicv_idx = 1;
gicv_idx += 3; /* Also skip GICD, GICC, GICH */
ret = of_address_to_resource(node, gicv_idx, &r);
if (!ret)
gic_v3_kvm_info.vcpu = r;
gic_v3_kvm_info.has_v4 = gic_data.rdists.has_vlpis;
gic_set_kvm_info(&gic_v3_kvm_info);
}
KVM을 통해 Guest OS를 동작시킬 때 사용할 gic 정보를 저장한다.
gic_set_kvm_info()
drivers/irqchip/irq-gic-common.c
void gic_set_kvm_info(const struct gic_kvm_info *info)
{
BUG_ON(gic_kvm_info != NULL);
gic_kvm_info = info;
}
virt/kvm/arm/vgic/vgic-init.c – kvm_vgic_hyp_init() 함수에서 gic-v3가 제공한 정보를 가져다 virtual gic를 초기화할 떄 사용한다.
GIC v3 Operations
GIC v3용 irq_chip Operations는 가상화 지원 관련하여 두 가지가 사용된다.
- gic_eoimode1_chip
- gic_chip
호스트 OS용 gic_eoimode1_chip
drivers/irqchip/irq-gic-v3.c
static struct irq_chip gic_eoimode1_chip = {
.name = "GICv3",
.irq_mask = gic_eoimode1_mask_irq,
.irq_unmask = gic_unmask_irq,
.irq_eoi = gic_eoimode1_eoi_irq,
.irq_set_type = gic_set_type,
.irq_set_affinity = gic_set_affinity,
.irq_get_irqchip_state = gic_irq_get_irqchip_state,
.irq_set_irqchip_state = gic_irq_set_irqchip_state,
.irq_set_vcpu_affinity = gic_irq_set_vcpu_affinity,
.irq_nmi_setup = gic_irq_nmi_setup,
.irq_nmi_teardown = gic_irq_nmi_teardown,
.flags = IRQCHIP_SET_TYPE_MASKED |
IRQCHIP_SKIP_SET_WAKE |
IRQCHIP_MASK_ON_SUSPEND,
};
Guest OS용 gic_chip
drivers/irqchip/irq-gic-v3.c
static struct irq_chip gic_chip = {
.name = "GICv3",
.irq_mask = gic_mask_irq,
.irq_unmask = gic_unmask_irq,
.irq_eoi = gic_eoi_irq,
.irq_set_type = gic_set_type,
.irq_set_affinity = gic_set_affinity,
.irq_get_irqchip_state = gic_irq_get_irqchip_state,
.irq_set_irqchip_state = gic_irq_set_irqchip_state,
.irq_nmi_setup = gic_irq_nmi_setup,
.irq_nmi_teardown = gic_irq_nmi_teardown,
.flags = IRQCHIP_SET_TYPE_MASKED |
IRQCHIP_SKIP_SET_WAKE |
IRQCHIP_MASK_ON_SUSPEND,
};
인터럽트 Mask & Unmask
gic_eoimode1_mask_irq()
drivers/irqchip/irq-gic-v3.c
static void gic_eoimode1_mask_irq(struct irq_data *d)
{
gic_mask_irq(d);
/*
* When masking a forwarded interrupt, make sure it is
* deactivated as well.
*
* This ensures that an interrupt that is getting
* disabled/masked will not get "stuck", because there is
* noone to deactivate it (guest is being terminated).
*/
if (irqd_is_forwarded_to_vcpu(d))
gic_poke_irq(d, GICD_ICACTIVER);
}
해당 인터럽트를 mask 한다.
- 코드 라인 3에서 GICD_ICENABLERn의 인터럽트 번호에 해당하는 비트에 1을 기록하여 physical 인터럽트를 mask 한다.
- 코드 라인 12~13에서 Guest OS에 vcpu를 통해 인터럽트를 전달 가능한 경우 GICD_ICACTIVERn의 인터럽트 번호에 해당하는 비트에 1을 기록하여 deactivate 요청을 한다.
gic_mask_irq()
drivers/irqchip/irq-gic-v3.c
static void gic_mask_irq(struct irq_data *d)
{
gic_poke_irq(d, GICD_ICENABLER);
}
해당 인터럽트를 mask 한다.
- 코드 라인 3에서 GICD_ICENABLERn의 인터럽트 번호에 해당하는 비트에 1을 기록하여 physical 인터럽트를 mask 한다.
gic_unmask_irq()
drivers/irqchip/irq-gic-v3.c
static void gic_unmask_irq(struct irq_data *d)
{
gic_poke_irq(d, GICD_ISENABLER);
}
해당 인터럽트를 unmask 한다.
- 코드 라인 3에서 GICD_ISENABLERn의 인터럽트 번호에 해당하는 비트에 1을 기록하여 physical 인터럽트를 unmask 한다.
레지스터 Peek & Poke
gic_peek_irq()
drivers/irqchip/irq-gic-v3.c
static int gic_peek_irq(struct irq_data *d, u32 offset)
{
void __iomem *base;
u32 index, mask;
offset = convert_offset_index(d, offset, &index);
mask = 1 << (index % 32);
if (gic_irq_in_rdist(d))
base = gic_data_rdist_sgi_base();
else
base = gic_data.dist_base;
return !!(readl_relaxed(base + offset + (index / 32) * 4) & mask);
}
@offset 위치 레지스터의 해당 인터럽트에 대응하는 비트 값을 읽어온다. (1=설정, 0=클리어)
- 코드 라인 6에서 해당 인터럽트의 @offset에 해당하는 offset을 알아오고, @index에는 세 가지(PPI/SPI, EPPI 및 ESPI) 타입의 0부터 시작하는 값을 알아온다.
- 코드 라인 9~12에서 해당 인터럽트가 Redistributor를 사용해야 하는 per-cpu 타입(PPI/EPPI) 여부에 따라 베이스 주소를 sgi_base로, 그 외의 경우 dist_base로 한다.
- 코드 라인 14에서 레지스터 베이스에 offset을 더한 주소에서 산출된 mask 비트 값을 읽어온다.
gic_poke_irq()
drivers/irqchip/irq-gic-v3.c
static void gic_poke_irq(struct irq_data *d, u32 offset)
{
void (*rwp_wait)(void);
void __iomem *base;
u32 index, mask;
offset = convert_offset_index(d, offset, &index);
mask = 1 << (index % 32);
if (gic_irq_in_rdist(d)) {
base = gic_data_rdist_sgi_base();
rwp_wait = gic_redist_wait_for_rwp;
} else {
base = gic_data.dist_base;
rwp_wait = gic_dist_wait_for_rwp;
}
writel_relaxed(mask, base + offset + (index / 32) * 4);
rwp_wait();
}
@offset 위치 레지스터의 해당 인터럽트에 대응하는 비트에 1을 기록한다.
- clear-request 또는 set-request 타입 레지스터에 사용한다
convert_offset_index()
drivers/irqchip/irq-gic-v3.c
/*
* Routines to disable, enable, EOI and route interrupts
*/
static u32 convert_offset_index(struct irq_data *d, u32 offset, u32 *index)
{
switch (get_intid_range(d)) {
case PPI_RANGE:
case SPI_RANGE:
*index = d->hwirq;
return offset;
case EPPI_RANGE:
/*
* Contrary to the ESPI range, the EPPI range is contiguous
* to the PPI range in the registers, so let's adjust the
* displacement accordingly. Consistency is overrated.
*/
*index = d->hwirq - EPPI_BASE_INTID + 32;
return offset;
case ESPI_RANGE:
*index = d->hwirq - ESPI_BASE_INTID;
switch (offset) {
case GICD_ISENABLER:
return GICD_ISENABLERnE;
case GICD_ICENABLER:
return GICD_ICENABLERnE;
case GICD_ISPENDR:
return GICD_ISPENDRnE;
case GICD_ICPENDR:
return GICD_ICPENDRnE;
case GICD_ISACTIVER:
return GICD_ISACTIVERnE;
case GICD_ICACTIVER:
return GICD_ICACTIVERnE;
case GICD_IPRIORITYR:
return GICD_IPRIORITYRnE;
case GICD_ICFGR:
return GICD_ICFGRnE;
case GICD_IROUTER:
return GICD_IROUTERnE;
default:
break;
}
break;
default:
break;
}
WARN_ON(1);
*index = d->hwirq;
return offset;
}
해당 인터럽트 타입과 @offset 주소로 레지스터에 대한 offset을 알아온다. 그리고 출력 인자 *index에는 hwirq 번호를 대입한다.
- 코드 라인 3~7에서 해당 인터럽트가 PPI 및 SPI 범위인 경우 @offset을 그대로 반환하고, @index에는 d->hwirq역시 그대로 반환한다.
- 코드 라인 8~15에서 해당 인터럽트가 확장 PPI 범위인 경우 @offset을 그대로 반환하고, @index에는 d->hwirq – EPPI_BASE_INTID(1024 + 32) + 32 값을 반환한다.
- 결국 @index = d->hwirq – 1024 (확장 SPI에 대한 @index는 0 ~ 1023)
- 코드 라인 16~36에서 해당 인터럽트가 확장 SPI 범위인 경우 @offset에 따라 레지스터 offset을 반환하고, @index에는 d->hwirq – ESPI_BASE_INTID 값을 반환한다.
- 결국 @index = d->hwirq – 4096 (확장 SPI의 INTID 는 4096 부터 시작, @index는 0부터)
- 코드 라인 37~47에서 그 외의 범위인 경우 @offset을 그대로 반환하고, @index에는 d->hwirq역시 그대로 반환한다.
인터럽트 타입 설정
gic_set_type()
drivers/irqchip/irq-gic-v3.c
static int gic_set_type(struct irq_data *d, unsigned int type)
{
enum gic_intid_range range;
unsigned int irq = gic_irq(d);
void (*rwp_wait)(void);
void __iomem *base;
u32 offset, index;
int ret;
/* Interrupt configuration for SGIs can't be changed */
if (irq < 16)
return -EINVAL;
range = get_intid_range(d);
/* SPIs have restrictions on the supported types */
if ((range == SPI_RANGE || range == ESPI_RANGE) &&
type != IRQ_TYPE_LEVEL_HIGH && type != IRQ_TYPE_EDGE_RISING)
return -EINVAL;
if (gic_irq_in_rdist(d)) {
base = gic_data_rdist_sgi_base();
rwp_wait = gic_redist_wait_for_rwp;
} else {
base = gic_data.dist_base;
rwp_wait = gic_dist_wait_for_rwp;
}
offset = convert_offset_index(d, GICD_ICFGR, &index);
ret = gic_configure_irq(index, type, base + offset, rwp_wait);
if (ret && (range == PPI_RANGE || range == EPPI_RANGE)) {
/* Misconfigured PPIs are usually not fatal */
pr_warn("GIC: PPI INTID%d is secure or misconfigured\n", irq);
ret = 0;
}
return ret;
}
해당 인터럽트에 대한 트리거 타입을 지정한다.
- 코드 라인 11~12에서 SGI는 에러를 반환한다.
- 코드 라인 14~19에서 SPI 또는 확장 SPI 영역인 경우 level high 또는 edge rising 트리거 타입만 지정할 수 있다.
- 코드 라인 21~27에서 해당 인터럽트가 Redistributor를 사용해야 하는 per-cpu 타입(PPI/EPPI) 여부에 따라 sgi_base를 그 외의 경우 dist_base를 레지스터 베이스로 지정한다.
- 코드 라인 29에서 PPI/SPI인 경우 GICD_ICFGRn 또는 EPPI/ESPI인 경우 GICD_ICFGRnE 레지스터에 대한 offset 주소를 알아오고 index에는 해당 인터럽트에 대한 0부터 시작하는 인덱스 값을 알아온다.(n)
- 코드 라인 31~36에서 위에서 알아온 베이스 주소 + offset 주소의 레지스터에 index에 해당하는 인터럽트 위치에 type을 기록하고, rwp_wait 함수를 통해 레지스터에 기록이 완료될 때까지 대기한다.
get_intid_range()
drivers/irqchip/irq-gic-v3.c
static enum gic_intid_range get_intid_range(struct irq_data *d)
{
return __get_intid_range(d->hwirq);
}
해당 인터럽트에 대한 레인지 타입을 알아온다.
__get_intid_range()
drivers/irqchip/irq-gic-v3.c
static enum gic_intid_range __get_intid_range(irq_hw_number_t hwirq)
{
switch (hwirq) {
case 16 ... 31:
return PPI_RANGE;
case 32 ... 1019:
return SPI_RANGE;
case EPPI_BASE_INTID ... (EPPI_BASE_INTID + 63):
return EPPI_RANGE;
case ESPI_BASE_INTID ... (ESPI_BASE_INTID + 1023):
return ESPI_RANGE;
case 8192 ... GENMASK(23, 0):
return LPI_RANGE;
default:
return __INVALID_RANGE__;
}
}
@hwirq 값에 따른 인터럽트에 대한 레인지 타입을 반환한다.
- PPI_RANGE: hwirq 16 ~ 31
- SPI_RANGE: hwirq: 32 ~ 1019
- EPPI_RANGE: 1024 ~ 1024 + 63
- ESPI_RANGE: 4096 ~ 4096 + 1023
- LPI_RANGE: 8192 ~ 0x80_0000 (2^23)
gic_intid_range
drivers/irqchip/irq-gic-v3.c
enum gic_intid_range {
PPI_RANGE,
SPI_RANGE,
EPPI_RANGE,
ESPI_RANGE,
LPI_RANGE,
__INVALID_RANGE__
};
gic_configure_irq()
drivers/irqchip/irq-gic-common.c
int gic_configure_irq(unsigned int irq, unsigned int type,
void __iomem *base, void (*sync_access)(void))
{
u32 confmask = 0x2 << ((irq % 16) * 2);
u32 confoff = (irq / 16) * 4;
u32 val, oldval;
int ret = 0;
unsigned long flags;
/*
* Read current configuration register, and insert the config
* for "irq", depending on "type".
*/
raw_spin_lock_irqsave(&irq_controller_lock, flags);
val = oldval = readl_relaxed(base + confoff);
if (type & IRQ_TYPE_LEVEL_MASK)
val &= ~confmask;
else if (type & IRQ_TYPE_EDGE_BOTH)
val |= confmask;
/* If the current configuration is the same, then we are done */
if (val == oldval) {
raw_spin_unlock_irqrestore(&irq_controller_lock, flags);
return 0;
}
/*
* Write back the new configuration, and possibly re-enable
* the interrupt. If we fail to write a new configuration for
* an SPI then WARN and return an error. If we fail to write the
* configuration for a PPI this is most likely because the GIC
* does not allow us to set the configuration or we are in a
* non-secure mode, and hence it may not be catastrophic.
*/
writel_relaxed(val, base + confoff);
if (readl_relaxed(base + confoff) != val)
ret = -EINVAL;
raw_spin_unlock_irqrestore(&irq_controller_lock, flags);
if (sync_access)
sync_access();
return ret;
}
@irq에 해당하는 2 비트의 @type을 일반/확장 인터럽트 여부에 따라 GICD_TYPERn 또는 GICD_TYPERnE에 기록한다. 기록한 후에는 기록이 완료될 때까지 잠시 대기한다.
Pseudo-NMI 지원
Pseudo-NMI 초기화
gic_enable_nmi_support()
drivers/irqchip/irq-gic-v3.c
static void gic_enable_nmi_support(void)
{
int i;
if (!gic_prio_masking_enabled())
return;
if (gic_has_group0() && !gic_dist_security_disabled()) {
pr_warn("SCR_EL3.FIQ is cleared, cannot enable use of pseudo-NMIs\n");
return;
}
ppi_nmi_refs = kcalloc(gic_data.ppi_nr, sizeof(*ppi_nmi_refs), GFP_KERNEL);
if (!ppi_nmi_refs)
return;
for (i = 0; i < gic_data.ppi_nr; i++)
refcount_set(&ppi_nmi_refs[i], 0);
static_branch_enable(&supports_pseudo_nmis);
if (static_branch_likely(&supports_deactivate_key))
gic_eoimode1_chip.flags |= IRQCHIP_SUPPORTS_NMI;
else
gic_chip.flags |= IRQCHIP_SUPPORTS_NMI;
}
Pesudo-NMI를 지원하는 경우 초기화 한다.
- 코드 라인 5~6에서 Pesudo-NMI 기능을 위해 시스템이 irq masking 기능이 필요한데 사용할 수 없으면 그냥 함수를 빠져나간다.
- 코드 라인 8~11에서 커널에서 gorup 0를 지원하면서 secure를 동작 중인 경우 경고 메시지를 출력하고 함수를 빠져나간다.
- EL3에서 secure가 동작 중이면 SCR_EL3.FIQ가 설정되어 FIQ를 커널에서 처리해야 한다.
- 코드 라인 13~15에서 ppi_nmi_refs 레퍼런스 카운터를 ppi 수 만큼 할당한다.
- 코드 라인 17~18에서 ppi_nmi_refs[]를 모두 0으로 설정한다.
- 코드 라인 20에서 Pesudo-NMI 기능이 동작한다는 static key를 활성화한다.
- 코드 라인 22~25에서 EL2 호스트 OS 운영에 따라 irq_chip operations의 플래그에 NMI 지원을 추가한다.
Pseudo-NMI 인터럽트 설정
gic_irq_nmi_setup()
drivers/irqchip/irq-gic-v3.c
static int gic_irq_nmi_setup(struct irq_data *d)
{
struct irq_desc *desc = irq_to_desc(d->irq);
if (!gic_supports_nmi())
return -EINVAL;
if (gic_peek_irq(d, GICD_ISENABLER)) {
pr_err("Cannot set NMI property of enabled IRQ %u\n", d->irq);
return -EINVAL;
}
/*
* A secondary irq_chip should be in charge of LPI request,
* it should not be possible to get there
*/
if (WARN_ON(gic_irq(d) >= 8192))
return -EINVAL;
/* desc lock should already be held */
if (gic_irq_in_rdist(d)) {
u32 idx = gic_get_ppi_index(d);
/* Setting up PPI as NMI, only switch handler for first NMI */
if (!refcount_inc_not_zero(&ppi_nmi_refs[idx])) {
refcount_set(&ppi_nmi_refs[idx], 1);
desc->handle_irq = handle_percpu_devid_fasteoi_nmi;
}
} else {
desc->handle_irq = handle_fasteoi_nmi;
}
gic_irq_set_prio(d, GICD_INT_NMI_PRI);
return 0;
}
해당 인터럽트를 Pesudo-NMI로 설정한다.
- 코드 라인 5~6에서 Pesudo-NMI를 지원하지 않는 시스템인 경우 -EINVAL 에러를 반환한다.
- 코드 라인 8~11에서 해당 인터럽트가 이미 enable 된 경우 에러 메시지를 출력하고 -EINVAL 에러를 반환한다.
- 코드 라인 17~18에서 LPI인 경우 지원하지 않으므로 -EINVAL을 반환한다.
- 코드 라인 21~31에서 해당 인터럽트가 Redistributor를 사용해야 하는 per-cpu 타입(PPI/EPPI) 여부에 따라 다음과 같이 설정한다.
- PPI/EPPI 타입인 경우 해당 인터럽트 번호별로 첫 번째 설정인 경우에만 핸들러를 handle_percpu_devid_fasteoi_nmi() 함수로 설정한다.
- 그 외 타입인 경우 핸들러를 handle_fasteoi_nmi() 함수로 지정한다.
- 참고: Interrupts -2- (irq chip) | 문c
- 코드 라인 33에서 인터럽트 우선 순위 값을 0x20 (내부 변환 후 0x90)으로 설정한다.
- non-secure EL1에서 동작하는 커널에서 우선 순위 값을 설정하면 group 1 인터럽트들만 설정 가능하며 다음과 같은 기준으로 변경되어 설정된다.
- prio(0x20) >> 1 | 0x80 = 변환 prio(0x90)
gic_supports_nmi()
drivers/irqchip/irq-gic-v3.c
static inline bool gic_supports_nmi(void)
{
return IS_ENABLED(CONFIG_ARM64_PSEUDO_NMI) &&
static_branch_likely(&supports_pseudo_nmis);
}
Pesudo-NMI를 지원하는지 여부를 반환한다.
gic_irq_in_rdist()
drivers/irqchip/irq-gic-v3.c
static inline int gic_irq_in_rdist(struct irq_data *d)
{
enum gic_intid_range range = get_intid_range(d);
return range == PPI_RANGE || range == EPPI_RANGE;
}
해당 인터럽트가 Redistributor를 사용해야 하는지 여부를 알아온다. (PPI 또는 EPPI인 경우)
인터럽트 우선 순위 설정
gic_irq_set_prio()
drivers/irqchip/irq-gic-v3.c
static void gic_irq_set_prio(struct irq_data *d, u8 prio)
{
void __iomem *base = gic_dist_base(d);
u32 offset, index;
offset = convert_offset_index(d, GICD_IPRIORITYR, &index);
writeb_relaxed(prio, base + offset + index);
}
해당 인터럽트의 우선 순위를 설정한다.
- non-secure EL1에서 동작하는 커널에서 우선 순위 값을 설정하면 group 1 인터럽트들만 설정 가능하며 다음과 같은 기준으로 변경되어 설정된다.
- prio >> 1 | 0x80 = 변환 prio (cpu interface로 전달되는 최종 priority 값이다)
Pseudo-NMI 인터럽트 해제
kernel/irq/chip.c
static void gic_irq_nmi_teardown(struct irq_data *d)
{
struct irq_desc *desc = irq_to_desc(d->irq);
if (WARN_ON(!gic_supports_nmi()))
return;
if (gic_peek_irq(d, GICD_ISENABLER)) {
pr_err("Cannot set NMI property of enabled IRQ %u\n", d->irq);
return;
}
/*
* A secondary irq_chip should be in charge of LPI request,
* it should not be possible to get there
*/
if (WARN_ON(gic_irq(d) >= 8192))
return;
/* desc lock should already be held */
if (gic_irq_in_rdist(d)) {
u32 idx = gic_get_ppi_index(d);
/* Tearing down NMI, only switch handler for last NMI */
if (refcount_dec_and_test(&ppi_nmi_refs[idx]))
desc->handle_irq = handle_percpu_devid_irq;
} else {
desc->handle_irq = handle_fasteoi_irq;
}
gic_irq_set_prio(d, GICD_INT_DEF_PRI);
}
해당 인터럽트를 Pesudo-NMI에서 일반 인터럽트로 변경하여 설정한다.
- 코드 라인 5~6에서 Pesudo-NMI를 지원하지 않는 시스템인 경우 -EINVAL 에러를 반환한다.
- 코드 라인 8~11에서 해당 인터럽트가 이미 enable 된 경우 에러 메시지를 출력하고 -EINVAL 에러를 반환한다.
- 코드 라인 17~18에서 LPI인 경우 지원하지 않으므로 -EINVAL을 반환한다.
- 코드 라인 21~29에서 해당 인터럽트가 Redistributor를 사용해야 하는 per-cpu 타입(PPI/EPPI) 여부에 따라 다음과 같이 설정한다.
- PPI/EPPI 타입인 경우 해당 인터럽트 번호별로 첫 번째 설정인 경우에만 핸들러를 handle_percpu_devid_irq() 함수로 설정한다.
- 그 외 타입인 경우 핸들러를 handle_fasteoi_irq() 함수로 지정한다.
- 코드 라인 31에서 인터럽트 우선 순위 값을 0xa0 (내부 변환 후 0xd0)으로 설정한다.
- non-secure EL1에서 동작하는 커널에서 우선 순위 값을 설정하면 group 1 인터럽트들만 설정 가능하며 다음과 같은 기준으로 변경되어 설정된다.
- prio(0xa0) >> 1 | 0x80 = 변환 prio(0xd0)
구조체
gic_chip_data 구조체
drivers/irqchip/irq-gic-v3.c
struct gic_chip_data {
struct fwnode_handle *fwnode;
void __iomem *dist_base;
struct redist_region *redist_regions;
struct rdists rdists;
struct irq_domain *domain;
u64 redist_stride;
u32 nr_redist_regions;
u64 flags;
bool has_rss;
unsigned int ppi_nr;
struct partition_desc **ppi_descs;
};
GIC v3 컨트롤러의 내부 칩 데이터를 구성한다.
- *fwnode
- *dist_base
- *redist_regions
- Redistributor 영역 수 만큼 redist_region 구조체를 할당하여 사용한다.
- *domain
- GIC가 사용하는 irq domain을 가리킨다.
- redist_stride
- Distributor들간의 주소 간격이 지정된다.
- nr_redist_regions
- flags
- has_rss
- affinity 0 레벨에서 0~255 범위의 cpu 번호를 지원해야 하는지 여부 (Range Select Support)
- **ppi_descs
- PPI 파티션을 사용하는 경우 파티션 수 만큼 partition_desc 구조체를 할당하여 사용한다.
redist_region 구조체
drivers/irqchip/irq-gic-v3.c
struct redist_region {
void __iomem *redist_base;
phys_addr_t phys_base;
bool single_redist;
};
하나의 Redistributor 정보를 관리한다.
- *redist_base
- Redistributor 레지스터 가상 주소 베이스
- phys_base
- Redistributor 레지스터 물리 주소 베이스
- single_redist
- Redistributor 영역 내에 single Redistributor인지 여부
rdists 구조체
include/linux/irqchip/arm-gic-v3.h
struct rdists {
struct {
void __iomem *rd_base;
struct page *pend_page;
phys_addr_t phys_base;
bool lpi_enabled;
} __percpu *rdist;
phys_addr_t prop_table_pa;
void *prop_table_va;
u64 flags;
u32 gicd_typer;
bool has_vlpis;
bool has_direct_lpi;
};
하나의 Redistributor region 정보를 관리한다.
- *rdist
- *rd_base
- Redistributor 레지스터 가상 주소 베이스
- *pend_page
- phys_base
- Redistributor 레지스터 물리 주소 베이스
- lpi_enabled
- prop_table_pa
- *prop_table_va
- flags
- gicd_typer
- has_vlpis
- has_direct_lpi
참고