]> rtime.felk.cvut.cz Git - jailhouse.git/commitdiff
arm: add support for GICv2
authorJean-Philippe Brucker <jean-philippe.brucker@arm.com>
Tue, 15 Jul 2014 14:17:12 +0000 (15:17 +0100)
committerJan Kiszka <jan.kiszka@siemens.com>
Fri, 19 Dec 2014 10:04:08 +0000 (11:04 +0100)
This patch implements the following GICv2 features:
- Remap GICC to GICV in the cells to provide a virtual interface
- Guest SGI filtering and hyp SGI handling
- IRQ injection

Signed-off-by: Jean-Philippe Brucker <jean-philippe.brucker@arm.com>
[Jan: switch to mmio accessor]
Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
hypervisor/arch/arm/Makefile
hypervisor/arch/arm/gic-common.c
hypervisor/arch/arm/gic-v2.c [new file with mode: 0644]
hypervisor/arch/arm/include/asm/gic_v2.h [new file with mode: 0644]
hypervisor/arch/arm/include/asm/platform.h
hypervisor/arch/arm/irqchip.c

index 7ae9c49326764b20bd07804b8be022817ad4086b..6b6ae4a1ea81a030679ebfcb0e2ca63d25613adf 100644 (file)
@@ -22,5 +22,6 @@ obj-y += paging.o mmu_hyp.o mmu_cell.o caches.o
 obj-y += psci.o psci_low.o smp.o
 obj-y += irqchip.o gic-common.o
 obj-$(CONFIG_ARM_GIC_V3) += gic-v3.o
+obj-$(CONFIG_ARM_GIC) += gic-v2.o
 obj-$(CONFIG_SERIAL_AMBA_PL011) += dbg-write-pl011.o
 obj-$(CONFIG_ARCH_VEXPRESS) += smp-vexpress.o
index bbf0ff4c9478ae5c338dc11caf9923f8308d6484..24ddef3b9e2d19585da8c74bca57dab15964ba65 100644 (file)
@@ -143,6 +143,25 @@ static int handle_irq_route(struct per_cpu *cpu_data,
        }
 }
 
+static int handle_sgir_access(struct per_cpu *cpu_data,
+                             struct mmio_access *access)
+{
+       struct sgi sgi;
+       unsigned long val = access->val;
+
+       if (!access->is_write)
+               return TRAP_HANDLED;
+
+       sgi.targets = (val >> 16) & 0xff;
+       sgi.routing_mode = (val >> 24) & 0x3;
+       sgi.aff1 = 0;
+       sgi.aff2 = 0;
+       sgi.aff3 = 0;
+       sgi.id = val & 0xf;
+
+       return gic_handle_sgir_write(cpu_data, &sgi, false);
+}
+
 int gic_handle_sgir_write(struct per_cpu *cpu_data, struct sgi *sgi,
                          bool virt_input)
 {
@@ -212,6 +231,10 @@ int gic_handle_dist_access(struct per_cpu *cpu_data,
                                (reg & 0x3ff) / 4, 8, false);
                break;
 
+       case GICD_SGIR:
+               ret = handle_sgir_access(cpu_data, access);
+               break;
+
        case GICD_CTLR:
        case GICD_TYPER:
        case GICD_IIDR:
diff --git a/hypervisor/arch/arm/gic-v2.c b/hypervisor/arch/arm/gic-v2.c
new file mode 100644 (file)
index 0000000..86f77f9
--- /dev/null
@@ -0,0 +1,286 @@
+/*
+ * Jailhouse, a Linux-based partitioning hypervisor
+ *
+ * Copyright (c) ARM Limited, 2014
+ *
+ * Authors:
+ *  Jean-Philippe Brucker <jean-philippe.brucker@arm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#include <jailhouse/control.h>
+#include <jailhouse/mmio.h>
+#include <asm/gic_common.h>
+#include <asm/irqchip.h>
+#include <asm/platform.h>
+#include <asm/setup.h>
+
+static unsigned int gic_num_lr;
+
+extern void *gicd_base;
+extern unsigned int gicd_size;
+void *gicc_base;
+unsigned int gicc_size;
+void *gicv_base;
+void *gich_base;
+unsigned int gich_size;
+
+static int gic_init(void)
+{
+       int err;
+
+       /* FIXME: parse device tree */
+       gicc_base = GICC_BASE;
+       gicc_size = GICC_SIZE;
+       gich_base = GICH_BASE;
+       gich_size = GICH_SIZE;
+       gicv_base = GICV_BASE;
+
+       err = arch_map_device(gicc_base, gicc_base, gicc_size);
+       if (err)
+               return err;
+
+       err = arch_map_device(gich_base, gich_base, gich_size);
+
+       return err;
+}
+
+static int gic_cpu_reset(struct per_cpu *cpu_data, bool is_shutdown)
+{
+       unsigned int i;
+       bool root_shutdown = is_shutdown && (cpu_data->cell == &root_cell);
+       u32 active;
+       u32 gich_vmcr = 0;
+       u32 gicc_ctlr, gicc_pmr;
+
+       /* Clear list registers */
+       for (i = 0; i < gic_num_lr; i++)
+               gic_write_lr(i, 0);
+
+       /* Deactivate all PPIs */
+       active = mmio_read32(gicd_base + GICD_ISACTIVER);
+       for (i = 16; i < 32; i++) {
+               if (test_bit(i, (unsigned long *)&active))
+                       mmio_write32(gicc_base + GICC_DIR, i);
+       }
+
+       /* Disable PPIs if necessary */
+       if (!root_shutdown)
+               mmio_write32(gicd_base + GICD_ICENABLER, 0xffff0000);
+       /* Ensure IPIs are enabled */
+       mmio_write32(gicd_base + GICD_ISENABLER, 0x0000ffff);
+
+       mmio_write32(gich_base + GICH_APR, 0);
+
+       if (is_shutdown)
+               mmio_write32(gich_base + GICH_HCR, 0);
+
+       if (root_shutdown) {
+               gich_vmcr = mmio_read32(gich_base + GICH_VMCR);
+               gicc_ctlr = 0;
+               gicc_pmr = (gich_vmcr >> GICH_VMCR_PMR_SHIFT) << GICV_PMR_SHIFT;
+
+               if (gich_vmcr & GICH_VMCR_EN0)
+                       gicc_ctlr |= GICC_CTLR_GRPEN1;
+               if (gich_vmcr & GICH_VMCR_EOImode)
+                       gicc_ctlr |= GICC_CTLR_EOImode;
+
+               mmio_write32(gicc_base + GICC_CTLR, gicc_ctlr);
+               mmio_write32(gicc_base + GICC_PMR, gicc_pmr);
+
+               gich_vmcr = 0;
+       }
+       mmio_write32(gich_base + GICH_VMCR, gich_vmcr);
+
+       return 0;
+}
+
+static int gic_cpu_init(struct per_cpu *cpu_data)
+{
+       u32 vtr, vmcr;
+       u32 cell_gicc_ctlr, cell_gicc_pmr;
+
+       /* Ensure all IPIs are enabled */
+       mmio_write32(gicd_base + GICD_ISENABLER, 0x0000ffff);
+
+       cell_gicc_ctlr = mmio_read32(gicc_base + GICC_CTLR);
+       cell_gicc_pmr = mmio_read32(gicc_base + GICC_PMR);
+
+       mmio_write32(gicc_base + GICC_CTLR,
+                    GICC_CTLR_GRPEN1 | GICC_CTLR_EOImode);
+       mmio_write32(gicc_base + GICC_PMR, GICC_PMR_DEFAULT);
+
+       vtr = mmio_read32(gich_base + GICH_VTR);
+       gic_num_lr = (vtr & 0x3f) + 1;
+
+       /* VMCR only contains 5 bits of priority */
+       vmcr = (cell_gicc_pmr >> GICV_PMR_SHIFT) << GICH_VMCR_PMR_SHIFT;
+       /*
+        * All virtual interrupts are group 0 in this driver since the GICV
+        * layout seen by the guest corresponds to GICC without security
+        * extensions:
+        * - A read from GICV_IAR doesn't acknowledge group 1 interrupts
+        *   (GICV_AIAR does it, but the guest never attempts to accesses it)
+        * - A write to GICV_CTLR.GRP0EN corresponds to the GICC_CTLR.GRP1EN bit
+        *   Since the guest's driver thinks that it is accessing a GIC with
+        *   security extensions, a write to GPR1EN will enable group 0
+        *   interrups.
+        * - Group 0 interrupts are presented as virtual IRQs (FIQEn = 0)
+        */
+       if (cell_gicc_ctlr & GICC_CTLR_GRPEN1)
+               vmcr |= GICH_VMCR_EN0;
+       if (cell_gicc_ctlr & GICC_CTLR_EOImode)
+               vmcr |= GICH_VMCR_EOImode;
+
+       mmio_write32(gich_base + GICH_VMCR, vmcr);
+       mmio_write32(gich_base + GICH_HCR, GICH_HCR_EN);
+
+       return 0;
+}
+
+static void gic_eoi_irq(u32 irq_id, bool deactivate)
+{
+       /*
+        * The GIC doesn't seem to care about the CPUID value written to EOIR,
+        * which is rather convenient...
+        */
+       mmio_write32(gicc_base + GICC_EOIR, irq_id);
+       if (deactivate)
+               mmio_write32(gicc_base + GICC_DIR, irq_id);
+}
+
+static void gic_route_spis(struct cell *config_cell, struct cell *dest_cell)
+{
+}
+
+static void gic_cell_init(struct cell *cell)
+{
+       struct jailhouse_memory gicv_region;
+
+       /*
+        * target_cpu_map has not been populated by all available CPUs when the
+        * setup code initialises the root cell. It is assumed that the kernel
+        * already has configured all its SPIs anyway, and that it will redirect
+        * them when unplugging a CPU.
+        */
+       if (cell != &root_cell)
+               gic_route_spis(cell, cell);
+
+       gicv_region.phys_start = (unsigned long)gicv_base;
+       /*
+        * WARN: some SoCs (EXYNOS4) use a modified GIC which doesn't have any
+        * banked CPU interface, so we should map per-CPU physical addresses
+        * here.
+        * As for now, none of them seem to have virtualization extensions.
+        */
+       gicv_region.virt_start = (unsigned long)gicc_base;
+       gicv_region.size = gicc_size;
+       gicv_region.flags = JAILHOUSE_MEM_DMA | JAILHOUSE_MEM_READ
+                         | JAILHOUSE_MEM_WRITE;
+
+       /*
+        * Let the guest access the virtual CPU interface instead of the
+        * physical one
+        */
+       arch_map_memory_region(cell, &gicv_region);
+}
+
+static void gic_cell_exit(struct cell *cell)
+{
+       /* Reset interrupt routing of the cell's spis*/
+       gic_route_spis(cell, &root_cell);
+}
+
+static int gic_send_sgi(struct sgi *sgi)
+{
+       u32 val;
+
+       if (!is_sgi(sgi->id))
+               return -EINVAL;
+
+       val = (sgi->routing_mode & 0x3) << 24
+               | (sgi->targets & 0xff) << 16
+               | (sgi->id & 0xf);
+
+       mmio_write32(gicd_base + GICD_SGIR, val);
+
+       return 0;
+}
+
+static int gic_inject_irq(struct per_cpu *cpu_data, struct pending_irq *irq)
+{
+       int i;
+       int first_free = -1;
+       u32 lr;
+       u64 elsr;
+
+       elsr = mmio_read32(gich_base + GICH_ELSR0);
+       elsr |= (u64)mmio_read32(gich_base + GICH_ELSR1) << 32;
+       for (i = 0; i < gic_num_lr; i++) {
+               if (test_bit(i, (unsigned long *)&elsr)) {
+                       /* Entry is available */
+                       if (first_free == -1)
+                               first_free = i;
+                       continue;
+               }
+
+               /* Check that there is no overlapping */
+               lr = gic_read_lr(i);
+               if ((lr & GICH_LR_VIRT_ID_MASK) == irq->virt_id)
+                       return -EINVAL;
+       }
+
+       if (first_free == -1) {
+               /* Enable maintenance IRQ */
+               u32 hcr;
+               hcr = mmio_read32(gich_base + GICH_HCR);
+               hcr |= GICH_HCR_UIE;
+               mmio_write32(gich_base + GICH_HCR, hcr);
+
+               return -EBUSY;
+       }
+
+       /* Inject group 0 interrupt (seen as IRQ by the guest) */
+       lr = irq->virt_id;
+       lr |= GICH_LR_PENDING_BIT;
+
+       if (irq->hw) {
+               lr |= GICH_LR_HW_BIT;
+               lr |= irq->type.irq << GICH_LR_PHYS_ID_SHIFT;
+       } else {
+               lr |= irq->type.sgi.cpuid << GICH_LR_CPUID_SHIFT;
+               if (irq->type.sgi.maintenance)
+                       lr |= GICH_LR_SGI_EOI_BIT;
+       }
+
+       gic_write_lr(first_free, lr);
+
+       return 0;
+}
+
+static int gic_mmio_access(struct per_cpu *cpu_data,
+                          struct mmio_access *access)
+{
+       void *address = (void *)access->addr;
+
+       if (address >= gicd_base && address < gicd_base + gicd_size)
+               return gic_handle_dist_access(cpu_data, access);
+
+       return TRAP_UNHANDLED;
+}
+
+struct irqchip_ops gic_irqchip = {
+       .init = gic_init,
+       .cpu_init = gic_cpu_init,
+       .cpu_reset = gic_cpu_reset,
+       .cell_init = gic_cell_init,
+       .cell_exit = gic_cell_exit,
+
+       .send_sgi = gic_send_sgi,
+       .handle_irq = gic_handle_irq,
+       .inject_irq = gic_inject_irq,
+       .eoi_irq = gic_eoi_irq,
+       .mmio_access = gic_mmio_access,
+};
diff --git a/hypervisor/arch/arm/include/asm/gic_v2.h b/hypervisor/arch/arm/include/asm/gic_v2.h
new file mode 100644 (file)
index 0000000..04a6eb9
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Jailhouse, a Linux-based partitioning hypervisor
+ *
+ * Copyright (c) ARM Limited, 2014
+ *
+ * Authors:
+ *  Jean-Philippe Brucker <jean-philippe.brucker@arm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#ifndef _JAILHOUSE_ASM_GIC_V2_H
+#define _JAILHOUSE_ASM_GIC_V2_H
+
+#define GICD_CIDR0             0xff0
+#define GICD_CIDR1             0xff4
+#define GICD_CIDR2             0xff8
+#define GICD_CIDR3             0xffc
+
+#define GICD_PIDR0             0xfe0
+#define GICD_PIDR1             0xfe4
+#define GICD_PIDR2             0xfe8
+#define GICD_PIDR3             0xfec
+#define GICD_PIDR4             0xfd0
+#define GICD_PIDR5             0xfd4
+#define GICD_PIDR6             0xfd8
+#define GICD_PIDR7             0xfdc
+
+#define GICC_CTLR              0x0000
+#define GICC_PMR               0x0004
+#define GICC_BPR               0x0008
+#define GICC_IAR               0x000c
+#define GICC_EOIR              0x0010
+#define GICC_RPR               0x0014
+#define GICC_HPPIR             0x0018
+#define GICC_ABPR              0x001c
+#define GICC_AIAR              0x0020
+#define GICC_AEOIR             0x0024
+#define GICC_AHPPIR            0x0028
+#define GICC_APR0              0x00d0
+#define GICC_APR1              0x00d4
+#define GICC_APR2              0x00d8
+#define GICC_APR3              0x00dc
+#define GICC_NSAPR0            0x00e0
+#define GICC_NSAPR1            0x00e4
+#define GICC_NSAPR2            0x00e8
+#define GICC_NSAPR3            0x00ec
+#define GICC_IIDR              0x00fc
+#define GICC_DIR               0x1000
+
+#define GICC_CTLR_GRPEN1       (1 << 0)
+#define GICC_CTLR_EOImode      (1 << 9)
+
+#define GICC_PMR_DEFAULT       0xf0
+
+#define GICH_HCR               0x000
+#define GICH_VTR               0x004
+#define GICH_VMCR              0x008
+#define GICH_MISR              0x010
+#define GICH_EISR0             0x020
+#define GICH_EISR1             0x024
+#define GICH_ELSR0             0x030
+#define GICH_ELSR1             0x034
+#define GICH_APR               0x0f0
+#define GICH_LR_BASE           0x100
+
+#define GICV_PMR_SHIFT         3
+#define GICH_VMCR_PMR_SHIFT    27
+#define GICH_VMCR_EN0          (1 << 0)
+#define GICH_VMCR_EN1          (1 << 1)
+#define GICH_VMCR_ACKCtl       (1 << 2)
+#define GICH_VMCR_EOImode      (1 << 9)
+
+#define GICH_HCR_EN            (1 << 0)
+#define GICH_HCR_UIE           (1 << 1)
+#define GICH_HCR_LRENPIE       (1 << 2)
+#define GICH_HCR_NPIE          (1 << 3)
+#define GICH_HCR_VGRP0EIE      (1 << 4)
+#define GICH_HCR_VGRP0DIE      (1 << 5)
+#define GICH_HCR_VGRP1EIE      (1 << 6)
+#define GICH_HCR_VGRP1DIE      (1 << 7)
+#define GICH_HCR_EOICOUNT_SHIFT        27
+
+#define GICH_LR_HW_BIT         (1 << 31)
+#define GICH_LR_GRP1_BIT       (1 << 30)
+#define GICH_LR_ACTIVE_BIT     (1 << 29)
+#define GICH_LR_PENDING_BIT    (1 << 28)
+#define GICH_LR_PRIORITY_SHIFT 23
+#define GICH_LR_SGI_EOI_BIT    (1 << 19)
+#define GICH_LR_CPUID_SHIFT    10
+#define GICH_LR_PHYS_ID_SHIFT  10
+#define GICH_LR_VIRT_ID_MASK   0x3ff
+
+#ifndef __ASSEMBLY__
+
+#include <jailhouse/mmio.h>
+
+static inline u32 gic_read_lr(unsigned int i)
+{
+       extern void *gich_base;
+
+       return mmio_read32(gich_base + GICH_LR_BASE + i * 4);
+}
+
+static inline void gic_write_lr(unsigned int i, u32 value)
+{
+       extern void *gich_base;
+
+       mmio_write32(gich_base + GICH_LR_BASE + i * 4, value);
+}
+
+static inline u32 gic_read_iar(void)
+{
+       extern void *gicc_base;
+
+       return mmio_read32(gicc_base + GICC_IAR) & 0x3ff;
+}
+
+#endif /* !__ASSEMBLY__ */
+#endif /* _JAILHOUSE_ASM_GIC_V2_H */
index e858be109da6bb44e35101174dcb4c4af60b0080..d355f8f03274a15f26c805659cf92603ad4aaefa 100644 (file)
 #  define GICR_SIZE    0x100000
 
 #  include <asm/gic_v3.h>
+# else /* GICv2 */
+#  define GICD_BASE    ((void *)0x2c001000)
+#  define GICD_SIZE    0x1000
+#  define GICC_BASE    ((void *)0x2c002000)
+/*
+ * WARN: most device trees are broken and report only one page for the GICC.
+ * It will brake the handle_irq code, since the GICC_DIR register is located at
+ * offset 0x1000...
+ */
+#  define GICC_SIZE    0x2000
+#  define GICH_BASE    ((void *)0x2c004000)
+#  define GICH_SIZE    0x2000
+#  define GICV_BASE    ((void *)0x2c006000)
+#  define GICV_SIZE    0x2000
+
+#  include <asm/gic_v2.h>
 # endif /* GIC */
 
 # define MAINTENANCE_IRQ 25
index a421a27a10ea7c2526089ca5383d0aa33113de7b..fe8b0f1f4cacff799945fec2264a56a300c257fa 100644 (file)
@@ -313,7 +313,6 @@ int irqchip_init(void)
        pidr2 = mmio_read32(gicd_base + GICD_PIDR2);
        switch (GICD_PIDR2_ARCH(pidr2)) {
        case 0x2:
-               break;
        case 0x3:
        case 0x4:
                memcpy(&irqchip, &gic_irqchip, sizeof(struct irqchip_ops));