]> rtime.felk.cvut.cz Git - jailhouse.git/commitdiff
arm: store the pending virtual interrupts
authorJean-Philippe Brucker <jean-philippe.brucker@arm.com>
Fri, 20 Jun 2014 18:32:11 +0000 (19:32 +0100)
committerJan Kiszka <jan.kiszka@siemens.com>
Fri, 19 Dec 2014 10:04:07 +0000 (11:04 +0100)
This patch introduces a pending_irq structure to provide a level of
abstraction, in order to store the interrupts waiting to be injected in
the cell. They are allocated as a static array of 256 IRQs for each CPU,
which should be more than enough. Insertion finds the first available slot
and builds a linked list of pending vIRQs.

Two cases justify the need for this structure:
- The GIC has a limited number of list registers for injecting virtual
  interrupts. Once they are full, software must store the pending ones
  itself, and use the GIC's maintenance IRQ to be informed when they are
  available again.
  In jailhouse, this case should be very rare since IRQs are directly
  injected, but it must be taken into account nonetheless.
- IPIs sent by a core need to be stored somewhere to let the other CPUs
  inject them into their own list registers.

The GIC backend will need to call irqchip_inject_pending when receiving
a maintenance IRQ or a synchronisation SGI in order to clean the list.

Signed-off-by: Jean-Philippe Brucker <jean-philippe.brucker@arm.com>
Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
hypervisor/arch/arm/gic-v3.c
hypervisor/arch/arm/include/asm/irqchip.h
hypervisor/arch/arm/include/asm/percpu.h
hypervisor/arch/arm/irqchip.c

index 2c3e424ec3a4bc5b9a25f10c299749d2eb5c3c25..4cee4a7e52397de185aeb41a7981534e3a125638 100644 (file)
@@ -151,9 +151,15 @@ static void gic_handle_irq(struct per_cpu *cpu_data)
 {
 }
 
+static int gic_inject_irq(struct per_cpu *cpu_data, struct pending_irq *irq)
+{
+       return 0;
+}
+
 struct irqchip_ops gic_irqchip = {
        .init = gic_init,
        .cpu_init = gic_cpu_init,
        .send_sgi = gic_send_sgi,
        .handle_irq = gic_handle_irq,
+       .inject_irq = gic_inject_irq,
 };
index 0ef5fe026382fbc5f9c6ecfd1aed53681e85c713..3fa37fdc7f3d9137ff4e43f5f25c691b4dae3ca0 100644 (file)
 #ifndef _JAILHOUSE_ASM_IRQCHIP_H
 #define _JAILHOUSE_ASM_IRQCHIP_H
 
+/*
+ * Since there is no finer-grained allocation than page-alloc for the moment,
+ * and it is very complicated to predict the total size needed at
+ * initialisation, each cpu is allocated one page of pending irqs.
+ * This allows for 256 pending IRQs, which should be sufficient.
+ */
+#define MAX_PENDING_IRQS       (PAGE_SIZE / sizeof(struct pending_irq))
+
 #include <asm/percpu.h>
 
 #ifndef __ASSEMBLY__
@@ -40,13 +48,39 @@ struct irqchip_ops {
 
        int     (*send_sgi)(struct sgi *sgi);
        void    (*handle_irq)(struct per_cpu *cpu_data);
+       int     (*inject_irq)(struct per_cpu *cpu_data, struct pending_irq *irq);
 };
 
+/* Virtual interrupts waiting to be injected */
+struct pending_irq {
+       u32     virt_id;
+
+       u8      priority;
+       u8      hw;
+       union {
+               /* Physical id, when hw is 1 */
+               u16 irq;
+               struct {
+                       /* GICv2 needs cpuid for SGIs */
+                       u16 cpuid       : 15;
+                       /* EOI generates a maintenance irq */
+                       u16 maintenance : 1;
+               } sgi __attribute__((packed));
+       } type;
+
+       struct pending_irq *next;
+       struct pending_irq *prev;
+} __attribute__((packed));
+
 int irqchip_init(void);
 int irqchip_cpu_init(struct per_cpu *cpu_data);
 
 int irqchip_send_sgi(struct sgi *sgi);
 void irqchip_handle_irq(struct per_cpu *cpu_data);
 
+int irqchip_inject_pending(struct per_cpu *cpu_data);
+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);
+
 #endif /* __ASSEMBLY__ */
 #endif /* _JAILHOUSE_ASM_IRQCHIP_H */
index bc3db93fff8fc2155e6a4b155fa92df0cf39cf54..2f5506f33bf098b4a7486c63abd3c2c72c347ab5 100644 (file)
 #ifndef __ASSEMBLY__
 
 #include <asm/cell.h>
+#include <asm/spinlock.h>
 #include <asm/sysregs.h>
 
+struct pending_irq;
+
 struct per_cpu {
        /* Keep these two in sync with defines above! */
        u8 stack[PAGE_SIZE];
@@ -37,6 +40,11 @@ struct per_cpu {
        unsigned long linux_reg[NUM_ENTRY_REGS];
 
        unsigned int cpu_id;
+
+       /* Other CPUs can insert sgis into the pending array */
+       spinlock_t gic_lock;
+       struct pending_irq *pending_irqs;
+       struct pending_irq *first_pending;
        /* Only GICv3: redistributor base */
        void *gicr_base;
 
index 0848780eab2baa7e70be74d06cbab65c28206335..1d958197e14c16e465c6f977adf441b228bb0d4b 100644 (file)
@@ -34,6 +34,126 @@ unsigned long gicd_size;
 static bool irqchip_is_init;
 static struct irqchip_ops irqchip;
 
+static int irqchip_init_pending(struct per_cpu *cpu_data)
+{
+       struct pending_irq *pend_array = page_alloc(&mem_pool, 1);
+
+       if (pend_array == NULL)
+               return -ENOMEM;
+       memset(pend_array, 0, PAGE_SIZE);
+
+       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;
+
+       *prev = NULL;
+
+       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;
+
+               *prev = pending;
+               pending = pending->next;
+       }
+
+       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;
+       }
+
+       /*
+        * Don't override the pointers yet, they may be read by the injection
+        * loop. Odds are astronomically low, but hey.
+        */
+       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;
+}
+
+/*
+ * 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)
+{
+       spin_lock(&cpu_data->gic_lock);
+
+       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;
+}
+
+int 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
+                       /*
+                        * 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.
+                        */
+                       irqchip_remove_pending(cpu_data, pending);
+
+               pending = pending->next;
+       }
+
+       return 0;
+}
+
 void irqchip_handle_irq(struct per_cpu *cpu_data)
 {
        irqchip.handle_irq(cpu_data);
@@ -46,6 +166,12 @@ 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);