]> rtime.felk.cvut.cz Git - jailhouse.git/blobdiff - hypervisor/arch/arm/irqchip.c
arm: Fold irqchip_root_cell_shrink into irqchip_cell_init
[jailhouse.git] / hypervisor / arch / arm / irqchip.c
index 1e2a4e494a6a08a83bf81e3a6de58f6635b1ff25..ef017913e96bfd066e080ead0fc5b12ced8e8a4b 100644 (file)
@@ -2,9 +2,11 @@
  * Jailhouse, a Linux-based partitioning hypervisor
  *
  * Copyright (c) ARM Limited, 2014
+ * Copyright (c) Siemens AG, 2016
  *
  * Authors:
  *  Jean-Philippe Brucker <jean-philippe.brucker@arm.com>
+ *  Jan Kiszka <jan.kiszka@siemens.com>
  *
  * This work is licensed under the terms of the GNU GPL, version 2.  See
  * the COPYING file in the top-level directory.
@@ -15,6 +17,7 @@
 #include <jailhouse/paging.h>
 #include <jailhouse/printk.h>
 #include <jailhouse/string.h>
+#include <asm/control.h>
 #include <asm/gic_common.h>
 #include <asm/irqchip.h>
 #include <asm/platform.h>
 /* AMBA's biosfood */
 #define AMBA_DEVICE    0xb105f00d
 
+#define for_each_irqchip(chip, config, counter)                                \
+       for ((chip) = jailhouse_cell_irqchips(config), (counter) = 0;   \
+            (counter) < (config)->num_irqchips;                        \
+            (chip)++, (counter)++)
+
+extern struct irqchip_ops irqchip;
+
 void *gicd_base;
 unsigned long gicd_size;
 
@@ -32,157 +42,74 @@ unsigned long gicd_size;
  * per-cpu setup, which means that a bool must be set by the master CPU
  */
 static bool irqchip_is_init;
-static struct irqchip_ops irqchip;
 
-bool spi_in_cell(struct cell *cell, unsigned int spi)
+bool irqchip_irq_in_cell(struct cell *cell, unsigned int irq_id)
 {
-       /* FIXME: Change the configuration to a bitmask range */
-       u32 spi_mask;
-
-       if (spi >= 64)
+       if (irq_id >= sizeof(cell->arch.irq_bitmap) * 8)
                return false;
-       else if (spi >= 32)
-               spi_mask = cell->arch.spis >> 32;
-       else
-               spi_mask = cell->arch.spis;
 
-       return spi_mask & (1 << (spi & 31));
+       return (cell->arch.irq_bitmap[irq_id / 32] & (1 << (irq_id % 32))) != 0;
 }
 
-static int irqchip_init_pending(struct per_cpu *cpu_data)
+void irqchip_set_pending(struct per_cpu *cpu_data, u16 irq_id)
 {
-       struct pending_irq *pend_array;
-
-       if (cpu_data->pending_irqs == NULL) {
-               cpu_data->pending_irqs = pend_array = page_alloc(&mem_pool, 1);
-               if (pend_array == NULL)
-                       return -ENOMEM;
-       } else {
-               pend_array = cpu_data->pending_irqs;
-       }
-
-       memset(pend_array, 0, PAGE_SIZE);
+       bool local_injection = (this_cpu_data() == cpu_data);
+       unsigned int new_tail;
 
-       cpu_data->pending_irqs = pend_array;
-       cpu_data->first_pending = NULL;
-
-       return 0;
-}
-
-/*
- * Find the first available pending struct for insertion. The `prev' pointer is
- * set to the previous pending interrupt, if any, to help inserting the new one
- * into the list.
- * Returns NULL when no slot is available
- */
-static struct pending_irq* get_pending_slot(struct per_cpu *cpu_data,
-                                           struct pending_irq **prev)
-{
-       u32 i, pending_idx;
-       struct pending_irq *pending = cpu_data->first_pending;
+       if (local_injection && irqchip.inject_irq(cpu_data, irq_id) != -EBUSY)
+               return;
 
-       *prev = NULL;
+       spin_lock(&cpu_data->pending_irqs_lock);
 
-       for (i = 0; i < MAX_PENDING_IRQS; i++) {
-               pending_idx = pending - cpu_data->pending_irqs;
-               if (pending == NULL || i < pending_idx)
-                       return cpu_data->pending_irqs + i;
+       new_tail = (cpu_data->pending_irqs_tail + 1) % MAX_PENDING_IRQS;
 
-               *prev = pending;
-               pending = pending->next;
+       /* Queue space available? */
+       if (new_tail != cpu_data->pending_irqs_head) {
+               cpu_data->pending_irqs[cpu_data->pending_irqs_tail] = irq_id;
+               cpu_data->pending_irqs_tail = new_tail;
+               /*
+                * Make the change to pending_irqs_tail visible before the
+                * caller sends SGI_INJECT.
+                */
+               memory_barrier();
        }
 
-       return NULL;
-}
-
-int irqchip_insert_pending(struct per_cpu *cpu_data, struct pending_irq *irq)
-{
-       struct pending_irq *prev = NULL;
-       struct pending_irq *slot;
-
-       spin_lock(&cpu_data->gic_lock);
-
-       slot = get_pending_slot(cpu_data, &prev);
-       if (slot == NULL) {
-               spin_unlock(&cpu_data->gic_lock);
-               return -ENOMEM;
-       }
+       spin_unlock(&cpu_data->pending_irqs_lock);
 
        /*
-        * Don't override the pointers yet, they may be read by the injection
-        * loop. Odds are astronomically low, but hey.
+        * The list registers are full, trigger maintenance interrupt if we are
+        * on the target CPU. In the other case, the caller will send a
+        * SGI_INJECT, and irqchip_inject_pending will take care.
         */
-       memcpy(slot, irq, sizeof(struct pending_irq) - 2 * sizeof(void *));
-       slot->prev = prev;
-       if (prev) {
-               slot->next = prev->next;
-               prev->next = slot;
-       } else {
-               slot->next = cpu_data->first_pending;
-               cpu_data->first_pending = slot;
-       }
-       if (slot->next)
-               slot->next->prev = slot;
-
-       spin_unlock(&cpu_data->gic_lock);
-
-       return 0;
-}
-
-int irqchip_set_pending(struct per_cpu *cpu_data, u32 irq_id, bool try_inject)
-{
-       struct pending_irq pending;
-
-       pending.virt_id = irq_id;
-
-       if (try_inject && irqchip.inject_irq(cpu_data, &pending) == 0)
-               return 0;
-
-       return irqchip_insert_pending(cpu_data, &pending);
+       if (local_injection)
+               irqchip.enable_maint_irq(true);
 }
 
-/*
- * Only executed by `irqchip_inject_pending' on a CPU to inject its own stuff.
- */
-int irqchip_remove_pending(struct per_cpu *cpu_data, struct pending_irq *irq)
+void irqchip_inject_pending(struct per_cpu *cpu_data)
 {
-       spin_lock(&cpu_data->gic_lock);
+       u16 irq_id;
 
-       if (cpu_data->first_pending == irq)
-               cpu_data->first_pending = irq->next;
-       if (irq->prev)
-               irq->prev->next = irq->next;
-       if (irq->next)
-               irq->next->prev = irq->prev;
-
-       spin_unlock(&cpu_data->gic_lock);
-
-       return 0;
-}
+       while (cpu_data->pending_irqs_head != cpu_data->pending_irqs_tail) {
+               irq_id = cpu_data->pending_irqs[cpu_data->pending_irqs_head];
 
-void irqchip_inject_pending(struct per_cpu *cpu_data)
-{
-       int err;
-       struct pending_irq *pending = cpu_data->first_pending;
-
-       while (pending != NULL) {
-               err = irqchip.inject_irq(cpu_data, pending);
-               if (err == -EBUSY)
-                       /* The list registers are full. */
-                       break;
-               else
+               if (irqchip.inject_irq(cpu_data, irq_id) == -EBUSY) {
                        /*
-                        * Removal only changes the pointers, but does not
-                        * deallocate anything.
-                        * Concurrent accesses are avoided with the spinlock,
-                        * but the `next' pointer of the current pending object
-                        * may be rewritten by an external insert before or
-                        * after this removal, which isn't an issue.
+                        * The list registers are full, trigger maintenance
+                        * interrupt and leave.
                         */
-                       irqchip_remove_pending(cpu_data, pending);
+                       irqchip.enable_maint_irq(true);
+                       return;
+               }
 
-               pending = pending->next;
+               cpu_data->pending_irqs_head =
+                       (cpu_data->pending_irqs_head + 1) % MAX_PENDING_IRQS;
        }
+
+       /*
+        * The software interrupt queue is empty - turn off the maintenance
+        * interrupt.
+        */
+       irqchip.enable_maint_irq(false);
 }
 
 void irqchip_handle_irq(struct per_cpu *cpu_data)
@@ -202,30 +129,14 @@ int irqchip_send_sgi(struct sgi *sgi)
 
 int irqchip_cpu_init(struct per_cpu *cpu_data)
 {
-       int err;
-
-       err = irqchip_init_pending(cpu_data);
-       if (err)
-               return err;
-
-       if (irqchip.cpu_init)
-               return irqchip.cpu_init(cpu_data);
-
-       return 0;
+       return irqchip.cpu_init(cpu_data);
 }
 
 int irqchip_cpu_reset(struct per_cpu *cpu_data)
 {
-       int err;
-
-       err = irqchip_init_pending(cpu_data);
-       if (err)
-               return err;
-
-       if (irqchip.cpu_reset)
-               return irqchip.cpu_reset(cpu_data, false);
+       cpu_data->pending_irqs_head = cpu_data->pending_irqs_tail = 0;
 
-       return 0;
+       return irqchip.cpu_reset(cpu_data, false);
 }
 
 void irqchip_cpu_shutdown(struct per_cpu *cpu_data)
@@ -235,55 +146,80 @@ void irqchip_cpu_shutdown(struct per_cpu *cpu_data)
         * it has been initialised: this function may be executed during the
         * setup phase.
         */
-       if (irqchip.cpu_reset)
-               irqchip.cpu_reset(cpu_data, true);
+       irqchip.cpu_reset(cpu_data, true);
 }
 
-static const struct jailhouse_irqchip *
-irqchip_find_config(struct jailhouse_cell_desc *config)
+int irqchip_cell_init(struct cell *cell)
 {
-       const struct jailhouse_irqchip *irq_config =
-               jailhouse_cell_irqchips(config);
+       const struct jailhouse_irqchip *chip;
+       unsigned int n, pos;
+       int err;
 
-       if (config->num_irqchips)
-               return irq_config;
-       else
-               return NULL;
-}
+       for_each_irqchip(chip, cell->config, n) {
+               if (chip->address != (unsigned long)gicd_base)
+                       continue;
+               if (chip->pin_base % 32 != 0 ||
+                   chip->pin_base + sizeof(chip->pin_bitmap) * 8 >
+                   sizeof(cell->arch.irq_bitmap) * 8)
+                       return trace_error(-EINVAL);
+               memcpy(&cell->arch.irq_bitmap[chip->pin_base / 32],
+                      chip->pin_bitmap, sizeof(chip->pin_bitmap));
+       }
+       /*
+        * Permit direct access to all SGIs and PPIs except for those used by
+        * the hypervisor.
+        */
+       cell->arch.irq_bitmap[0] = ~((1 << SGI_INJECT) | (1 << SGI_CPU_OFF) |
+                                    (1 << MAINTENANCE_IRQ));
 
-int irqchip_cell_init(struct cell *cell)
-{
-       const struct jailhouse_irqchip *pins = irqchip_find_config(cell->config);
+       err = irqchip.cell_init(cell);
+       if (err)
+               return err;
 
-       cell->arch.spis = (pins ? pins->pin_bitmap : 0);
+       if (cell != &root_cell)
+               for_each_irqchip(chip, cell->config, n) {
+                       if (chip->address != (unsigned long)gicd_base)
+                               continue;
+                       for (pos = 0; pos < ARRAY_SIZE(chip->pin_bitmap); pos++)
+                               root_cell.arch.irq_bitmap[chip->pin_base/32] &=
+                                       ~chip->pin_bitmap[pos];
+               }
 
-       return irqchip.cell_init(cell);
+       return 0;
 }
 
 void irqchip_cell_exit(struct cell *cell)
 {
-       const struct jailhouse_irqchip *root_pins =
-               irqchip_find_config(root_cell.config);
+       const struct jailhouse_irqchip *chip;
+       unsigned int n, pos;
 
        /* might be called by arch_shutdown while rolling back
         * a failed setup */
        if (!irqchip_is_init)
                return;
 
-       if (root_pins)
-               root_cell.arch.spis |= cell->arch.spis & root_pins->pin_bitmap;
+       /* set all pins of the old cell in the root cell */
+       for_each_irqchip(chip, cell->config, n) {
+               if (chip->address != (unsigned long)gicd_base)
+                       continue;
+               for (pos = 0; pos < ARRAY_SIZE(chip->pin_bitmap); pos++)
+                       root_cell.arch.irq_bitmap[chip->pin_base / 32] |=
+                               chip->pin_bitmap[pos];
+       }
 
-       irqchip.cell_exit(cell);
-}
+       /* mask out pins again that actually didn't belong to the root cell */
+       for_each_irqchip(chip, root_cell.config, n) {
+               if (chip->address != (unsigned long)gicd_base)
+                       continue;
+               for (pos = 0; pos < ARRAY_SIZE(chip->pin_bitmap); pos++)
+                       root_cell.arch.irq_bitmap[chip->pin_base / 32] &=
+                               chip->pin_bitmap[pos];
+       }
 
-void irqchip_root_cell_shrink(struct cell *cell)
-{
-       root_cell.arch.spis &= ~(cell->arch.spis);
+       if (irqchip.cell_exit)
+               irqchip.cell_exit(cell);
 }
 
-/* Only the GIC is implemented */
-extern struct irqchip_ops gic_irqchip;
-
 int irqchip_init(void)
 {
        int i, err;
@@ -314,8 +250,9 @@ int irqchip_init(void)
        case 0x2:
        case 0x3:
        case 0x4:
-               memcpy(&irqchip, &gic_irqchip, sizeof(struct irqchip_ops));
                break;
+       default:
+               goto err_no_distributor;
        }
 
        if (irqchip.init) {
@@ -326,7 +263,7 @@ int irqchip_init(void)
        }
 
 err_no_distributor:
-       printk("GIC: no distributor found\n");
+       printk("GIC: no supported distributor found\n");
        arch_unmap_device(gicd_base, gicd_size);
 
        return -ENODEV;