]> rtime.felk.cvut.cz Git - jailhouse.git/commitdiff
arm: GIC: handle distributor accesses
authorJean-Philippe Brucker <jean-philippe.brucker@arm.com>
Fri, 4 Jul 2014 18:04:43 +0000 (19:04 +0100)
committerJan Kiszka <jan.kiszka@siemens.com>
Fri, 19 Dec 2014 10:04:07 +0000 (11:04 +0100)
This patch adds the handling of MMIO accesses to the GICv3 distributor.
By restricting the SPI masks to the cell's configuration, it makes sure
that they do not touch the other cell's SPI's when writing the common
registers.
Except for the routing and SGIR registers, most of the code should be
common to both GICv2 and v3.

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 [new file with mode: 0644]
hypervisor/arch/arm/gic-v3.c
hypervisor/arch/arm/include/asm/gic_common.h
hypervisor/arch/arm/include/asm/irqchip.h

index f4bc5d8fa7f87ce7786620e4ba72e943bc1b5b8a..91b1659b5263fa0a2c5bdfefbfa9f982b2ec17e9 100644 (file)
@@ -20,6 +20,6 @@ obj-y := entry.o dbg-write.o exception.o setup.o control.o lib.o
 obj-y += traps.o mmio.o
 obj-y += paging.o mmu_hyp.o mmu_cell.o caches.o
 obj-y += psci.o psci_low.o spin.o
-obj-y += irqchip.o
+obj-y += irqchip.o gic-common.o
 obj-$(CONFIG_ARM_GIC_V3) += gic-v3.o
 obj-$(CONFIG_SERIAL_AMBA_PL011) += dbg-write-pl011.o
diff --git a/hypervisor/arch/arm/gic-common.c b/hypervisor/arch/arm/gic-common.c
new file mode 100644 (file)
index 0000000..b966216
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * 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/cell.h>
+#include <asm/gic_common.h>
+#include <asm/irqchip.h>
+#include <asm/percpu.h>
+#include <asm/platform.h>
+#include <asm/spinlock.h>
+#include <asm/traps.h>
+
+#define REG_RANGE(base, n, size)               \
+               (base) ... ((base) + (n - 1) * (size))
+
+extern void *gicd_base;
+extern unsigned int gicd_size;
+
+static DEFINE_SPINLOCK(dist_lock);
+
+/*
+ * Most of the GIC distributor writes only reconfigure the IRQs corresponding to
+ * the bits of the written value, by using separate `set' and `clear' registers.
+ * Such registers can be handled by setting the `is_poke' boolean, which allows
+ * to simply restrict the access->val with the cell configuration mask.
+ * Others, such as the priority registers, will need to be read and written back
+ * with a restricted value, by using the distributor lock.
+ */
+static int restrict_bitmask_access(struct per_cpu *cpu_data,
+                                  struct mmio_access *access,
+                                  unsigned int reg_index,
+                                  unsigned int bits_per_irq,
+                                  bool is_poke)
+{
+       unsigned int spi;
+       unsigned long access_mask = 0;
+       /*
+        * In order to avoid division, the number of bits per irq is limited
+        * to powers of 2 for the moment.
+        */
+       unsigned long irqs_per_reg = 32 >> ffsl(bits_per_irq);
+       unsigned long spi_bits = (1 << bits_per_irq) - 1;
+       /* First, extract the first interrupt affected by this access */
+       unsigned int first_irq = reg_index * irqs_per_reg;
+
+       /* For SGIs or PPIs, let the caller do the mmio access */
+       if (!is_spi(first_irq))
+               return TRAP_UNHANDLED;
+
+       /* For SPIs, compare against the cell config mask */
+       first_irq -= 32;
+       for (spi = first_irq; spi < first_irq + irqs_per_reg; spi++) {
+               unsigned int bit_nr = (spi - first_irq) * bits_per_irq;
+               if (spi_in_cell(cpu_data->cell, spi))
+                       access_mask |= spi_bits << bit_nr;
+       }
+
+       if (!access->is_write) {
+               /* Restrict the read value */
+               arch_mmio_access(access);
+               access->val &= access_mask;
+               return TRAP_HANDLED;
+       }
+
+       if (!is_poke) {
+               /*
+                * Modify the existing value of this register by first reading
+                * it into access->val
+                * Relies on a spinlock since we need two mmio accesses.
+                */
+               unsigned long access_val = access->val;
+
+               spin_lock(&dist_lock);
+
+               access->is_write = false;
+               arch_mmio_access(access);
+               access->is_write = true;
+
+               /* Clear 0 bits */
+               access->val &= ~(access_mask & ~access_val);
+               access->val |= access_val;
+               arch_mmio_access(access);
+
+               spin_unlock(&dist_lock);
+
+               return TRAP_HANDLED;
+       } else {
+               access->val &= access_mask;
+               /* Do the access */
+               return TRAP_UNHANDLED;
+       }
+}
+
+/*
+ * GICv3 uses a 64bit register IROUTER for each IRQ
+ */
+static int handle_irq_route(struct per_cpu *cpu_data,
+                           struct mmio_access *access, unsigned int irq)
+{
+       struct cell *cell = cpu_data->cell;
+       unsigned int cpu;
+
+       /* Ignore aff3 on AArch32 (return 0) */
+       if (access->size == 4 && (access->addr % 8))
+               return TRAP_HANDLED;
+
+       /* SGIs and PPIs are res0 */
+       if (!is_spi(irq))
+               return TRAP_HANDLED;
+
+       /*
+        * Ignore accesses to SPIs that do not belong to the cell. This isn't
+        * forbidden, because the guest driver may simply iterate over all
+        * registers at initialisation
+        */
+       if (!spi_in_cell(cell, irq - 32))
+               return TRAP_HANDLED;
+
+       /* Translate the virtual cpu id into the physical one */
+       if (access->is_write) {
+               access->val = arm_cpu_virt2phys(cell, access->val);
+               if (access->val == -1) {
+                       printk("Attempt to route IRQ%d outside of cell\n", irq);
+                       return TRAP_FORBIDDEN;
+               }
+               /* And do the access */
+               return TRAP_UNHANDLED;
+       } else {
+               cpu = mmio_read32(gicd_base + GICD_IROUTER + 8 * irq);
+               access->val = arm_cpu_phys2virt(cpu);
+               return TRAP_HANDLED;
+       }
+}
+
+int gic_handle_dist_access(struct per_cpu *cpu_data,
+                          struct mmio_access *access)
+{
+       int ret;
+       unsigned long reg = access->addr - (unsigned long)gicd_base;
+
+       switch (reg) {
+       case REG_RANGE(GICD_IROUTER, 1024, 8):
+               ret = handle_irq_route(cpu_data, access,
+                               (reg - GICD_IROUTER) / 8);
+               break;
+
+       case REG_RANGE(GICD_ICENABLER, 32, 4):
+       case REG_RANGE(GICD_ISENABLER, 32, 4):
+       case REG_RANGE(GICD_ICPENDR, 32, 4):
+       case REG_RANGE(GICD_ISPENDR, 32, 4):
+       case REG_RANGE(GICD_ICACTIVER, 32, 4):
+       case REG_RANGE(GICD_ISACTIVER, 32, 4):
+               ret = restrict_bitmask_access(cpu_data, access,
+                               (reg & 0x7f) / 4, 1, true);
+               break;
+
+       case REG_RANGE(GICD_IGROUPR, 32, 4):
+               ret = restrict_bitmask_access(cpu_data, access,
+                               (reg & 0x7f) / 4, 1, false);
+               break;
+
+       case REG_RANGE(GICD_ICFGR, 64, 4):
+               ret = restrict_bitmask_access(cpu_data, access,
+                               (reg & 0xff) / 4, 2, false);
+               break;
+
+       case REG_RANGE(GICD_IPRIORITYR, 255, 4):
+               ret = restrict_bitmask_access(cpu_data, access,
+                               (reg & 0x3ff) / 4, 8, false);
+               break;
+
+       case GICD_CTLR:
+       case GICD_TYPER:
+       case GICD_IIDR:
+       case REG_RANGE(GICD_PIDR0, 4, 4):
+       case REG_RANGE(GICD_PIDR4, 4, 4):
+       case REG_RANGE(GICD_CIDR0, 4, 4):
+               /* Allow read access, ignore write */
+               ret = (access->is_write ? TRAP_HANDLED : TRAP_UNHANDLED);
+               break;
+
+       default:
+               /* Ignore access. */
+               ret = TRAP_HANDLED;
+       }
+
+       /* The sub-handlers return TRAP_UNHANDLED to allow the access */
+       if (ret == TRAP_UNHANDLED) {
+               arch_mmio_access(access);
+               ret = TRAP_HANDLED;
+       }
+
+       return ret;
+}
index bffafc62af824af87f92471eb0b9c8d5dc9df593..aeaf890c2bb589e12fde7f1c051bdc97d3063b49 100644 (file)
@@ -166,7 +166,6 @@ static int gic_cpu_init(struct per_cpu *cpu_data)
 static void gic_route_spis(struct cell *config_cell, struct cell *dest_cell)
 {
        int i;
-       u64 spis = config_cell->arch.spis;
        void *irouter = gicd_base + GICD_IROUTER;
        unsigned int first_cpu;
 
@@ -175,7 +174,7 @@ static void gic_route_spis(struct cell *config_cell, struct cell *dest_cell)
                break;
 
        for (i = 0; i < 64; i++, irouter += 8) {
-               if (test_bit(i, (unsigned long *)&spis))
+               if (spi_in_cell(config_cell, i))
                        mmio_write64(irouter, first_cpu);
        }
 }
@@ -437,6 +436,9 @@ static int gic_mmio_access(struct per_cpu *cpu_data,
 {
        void *address = (void *)access->addr;
 
+       if (address >= gicd_base && address < gicd_base + gicd_size)
+               return gic_handle_dist_access(cpu_data, access);
+
        if (address >= gicr_base && address < gicr_base + gicr_size)
                return gic_handle_redist_access(cpu_data, access);
 
index a86109c567a6dbeae5b6afee246145ca21aab066..85975039de282f1b01848f2243dca503303c9994 100644 (file)
@@ -13,6 +13,7 @@
 #ifndef _JAILHOUSE_ASM_GIC_COMMON_H
 #define _JAILHOUSE_ASM_GIC_COMMON_H
 
+#include <jailhouse/mmio.h>
 #include <jailhouse/types.h>
 
 #define GICD_CTLR                      0x0000
 #define is_ppi(irqn)                   ((irqn) > 15 && (irqn) < 32)
 #define is_spi(irqn)                   ((irqn) > 31 && (irqn) < 1020)
 
+#ifndef __ASSEMBLY__
+
+struct arm_mmio_access;
+struct per_cpu;
+
+int gic_handle_dist_access(struct per_cpu *cpu_data,
+                          struct mmio_access *access);
+
+#endif /* !__ASSEMBLY__ */
 #endif /* !_JAILHOUSE_ASM_GIC_COMMON_H */
index ff9899b3407f8871dc3528ce7ed70553e61eeefd..7a86f3ac9888c796ff562965371eb48ff9fe682d 100644 (file)
@@ -101,5 +101,18 @@ int irqchip_insert_pending(struct per_cpu *cpu_data, struct pending_irq *irq);
 int irqchip_remove_pending(struct per_cpu *cpu_data, struct pending_irq *irq);
 int irqchip_set_pending(struct per_cpu *cpu_data, u32 irq_id, bool try_inject);
 
+static inline bool spi_in_cell(struct cell *cell, unsigned int spi)
+{
+       /* FIXME: Change the configuration to a bitmask range */
+       u64 spi_mask;
+
+       if (spi > 64)
+               return false;
+
+       spi_mask = cell->arch.spis;
+
+       return spi_mask & (1 << spi);
+}
+
 #endif /* __ASSEMBLY__ */
 #endif /* _JAILHOUSE_ASM_IRQCHIP_H */