gic_v3.c 5.5 KB
Newer Older
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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
// SPDX-License-Identifier: GPL-2.0
/*
 * ARM Generic Interrupt Controller (GIC) v3 support
 */

#include <linux/sizes.h>

#include "kvm_util.h"
#include "processor.h"
#include "delay.h"

#include "gic_v3.h"
#include "gic_private.h"

struct gicv3_data {
	void *dist_base;
	void *redist_base[GICV3_MAX_CPUS];
	unsigned int nr_cpus;
	unsigned int nr_spis;
};

#define sgi_base_from_redist(redist_base) (redist_base + SZ_64K)

enum gicv3_intid_range {
	SGI_RANGE,
	PPI_RANGE,
	SPI_RANGE,
	INVALID_RANGE,
};

static struct gicv3_data gicv3_data;

static void gicv3_gicd_wait_for_rwp(void)
{
	unsigned int count = 100000; /* 1s */

	while (readl(gicv3_data.dist_base + GICD_CTLR) & GICD_CTLR_RWP) {
		GUEST_ASSERT(count--);
		udelay(10);
	}
}

static void gicv3_gicr_wait_for_rwp(void *redist_base)
{
	unsigned int count = 100000; /* 1s */

	while (readl(redist_base + GICR_CTLR) & GICR_CTLR_RWP) {
		GUEST_ASSERT(count--);
		udelay(10);
	}
}

static enum gicv3_intid_range get_intid_range(unsigned int intid)
{
	switch (intid) {
	case 0 ... 15:
		return SGI_RANGE;
	case 16 ... 31:
		return PPI_RANGE;
	case 32 ... 1019:
		return SPI_RANGE;
	}

	/* We should not be reaching here */
	GUEST_ASSERT(0);

	return INVALID_RANGE;
}

static uint64_t gicv3_read_iar(void)
{
	uint64_t irqstat = read_sysreg_s(SYS_ICC_IAR1_EL1);

	dsb(sy);
	return irqstat;
}

static void gicv3_write_eoir(uint32_t irq)
{
	write_sysreg_s(irq, SYS_ICC_EOIR1_EL1);
	isb();
}

static void
gicv3_config_irq(unsigned int intid, unsigned int offset)
{
	uint32_t cpu = guest_get_vcpuid();
	uint32_t mask = 1 << (intid % 32);
	enum gicv3_intid_range intid_range = get_intid_range(intid);
	void *reg;

	/* We care about 'cpu' only for SGIs or PPIs */
	if (intid_range == SGI_RANGE || intid_range == PPI_RANGE) {
		GUEST_ASSERT(cpu < gicv3_data.nr_cpus);

		reg = sgi_base_from_redist(gicv3_data.redist_base[cpu]) +
			offset;
		writel(mask, reg);
		gicv3_gicr_wait_for_rwp(gicv3_data.redist_base[cpu]);
	} else if (intid_range == SPI_RANGE) {
		reg = gicv3_data.dist_base + offset + (intid / 32) * 4;
		writel(mask, reg);
		gicv3_gicd_wait_for_rwp();
	} else {
		GUEST_ASSERT(0);
	}
}

static void gicv3_irq_enable(unsigned int intid)
{
	gicv3_config_irq(intid, GICD_ISENABLER);
}

static void gicv3_irq_disable(unsigned int intid)
{
	gicv3_config_irq(intid, GICD_ICENABLER);
}

static void gicv3_enable_redist(void *redist_base)
{
	uint32_t val = readl(redist_base + GICR_WAKER);
	unsigned int count = 100000; /* 1s */

	val &= ~GICR_WAKER_ProcessorSleep;
	writel(val, redist_base + GICR_WAKER);

	/* Wait until the processor is 'active' */
	while (readl(redist_base + GICR_WAKER) & GICR_WAKER_ChildrenAsleep) {
		GUEST_ASSERT(count--);
		udelay(10);
	}
}

static inline void *gicr_base_cpu(void *redist_base, uint32_t cpu)
{
	/* Align all the redistributors sequentially */
	return redist_base + cpu * SZ_64K * 2;
}

static void gicv3_cpu_init(unsigned int cpu, void *redist_base)
{
	void *sgi_base;
	unsigned int i;
	void *redist_base_cpu;

	GUEST_ASSERT(cpu < gicv3_data.nr_cpus);

	redist_base_cpu = gicr_base_cpu(redist_base, cpu);
	sgi_base = sgi_base_from_redist(redist_base_cpu);

	gicv3_enable_redist(redist_base_cpu);

	/*
	 * Mark all the SGI and PPI interrupts as non-secure Group-1.
	 * Also, deactivate and disable them.
	 */
	writel(~0, sgi_base + GICR_IGROUPR0);
	writel(~0, sgi_base + GICR_ICACTIVER0);
	writel(~0, sgi_base + GICR_ICENABLER0);

	/* Set a default priority for all the SGIs and PPIs */
	for (i = 0; i < 32; i += 4)
		writel(GICD_INT_DEF_PRI_X4,
				sgi_base + GICR_IPRIORITYR0 + i);

	gicv3_gicr_wait_for_rwp(redist_base_cpu);

	/* Enable the GIC system register (ICC_*) access */
	write_sysreg_s(read_sysreg_s(SYS_ICC_SRE_EL1) | ICC_SRE_EL1_SRE,
			SYS_ICC_SRE_EL1);

	/* Set a default priority threshold */
	write_sysreg_s(ICC_PMR_DEF_PRIO, SYS_ICC_PMR_EL1);

	/* Enable non-secure Group-1 interrupts */
	write_sysreg_s(ICC_IGRPEN1_EL1_ENABLE, SYS_ICC_GRPEN1_EL1);

	gicv3_data.redist_base[cpu] = redist_base_cpu;
}

static void gicv3_dist_init(void)
{
	void *dist_base = gicv3_data.dist_base;
	unsigned int i;

	/* Disable the distributor until we set things up */
	writel(0, dist_base + GICD_CTLR);
	gicv3_gicd_wait_for_rwp();

	/*
	 * Mark all the SPI interrupts as non-secure Group-1.
	 * Also, deactivate and disable them.
	 */
	for (i = 32; i < gicv3_data.nr_spis; i += 32) {
		writel(~0, dist_base + GICD_IGROUPR + i / 8);
		writel(~0, dist_base + GICD_ICACTIVER + i / 8);
		writel(~0, dist_base + GICD_ICENABLER + i / 8);
	}

	/* Set a default priority for all the SPIs */
	for (i = 32; i < gicv3_data.nr_spis; i += 4)
		writel(GICD_INT_DEF_PRI_X4,
				dist_base + GICD_IPRIORITYR + i);

	/* Wait for the settings to sync-in */
	gicv3_gicd_wait_for_rwp();

	/* Finally, enable the distributor globally with ARE */
	writel(GICD_CTLR_ARE_NS | GICD_CTLR_ENABLE_G1A |
			GICD_CTLR_ENABLE_G1, dist_base + GICD_CTLR);
	gicv3_gicd_wait_for_rwp();
}

static void gicv3_init(unsigned int nr_cpus, void *dist_base)
{
	GUEST_ASSERT(nr_cpus <= GICV3_MAX_CPUS);

	gicv3_data.nr_cpus = nr_cpus;
	gicv3_data.dist_base = dist_base;
	gicv3_data.nr_spis = GICD_TYPER_SPIS(
				readl(gicv3_data.dist_base + GICD_TYPER));
	if (gicv3_data.nr_spis > 1020)
		gicv3_data.nr_spis = 1020;

	/*
	 * Initialize only the distributor for now.
	 * The redistributor and CPU interfaces are initialized
	 * later for every PE.
	 */
	gicv3_dist_init();
}

const struct gic_common_ops gicv3_ops = {
	.gic_init = gicv3_init,
	.gic_cpu_init = gicv3_cpu_init,
	.gic_irq_enable = gicv3_irq_enable,
	.gic_irq_disable = gicv3_irq_disable,
	.gic_read_iar = gicv3_read_iar,
	.gic_write_eoir = gicv3_write_eoir,
};