* 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.
#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;
* 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;
-static int irqchip_init_pending(struct per_cpu *cpu_data)
+bool irqchip_irq_in_cell(struct cell *cell, unsigned int 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);
+ if (irq_id >= sizeof(cell->arch.irq_bitmap) * 8)
+ return false;
- cpu_data->pending_irqs = pend_array;
- cpu_data->first_pending = NULL;
-
- return 0;
+ return (cell->arch.irq_bitmap[irq_id / 32] & (1 << (irq_id % 32))) != 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)
+void irqchip_set_pending(struct per_cpu *cpu_data, u16 irq_id)
{
- u32 i, pending_idx;
- struct pending_irq *pending = cpu_data->first_pending;
+ bool local_injection = (this_cpu_data() == cpu_data);
+ unsigned int new_tail;
- *prev = NULL;
+ if (local_injection && irqchip.inject_irq(cpu_data, irq_id) != -EBUSY)
+ return;
- 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;
+ spin_lock(&cpu_data->pending_irqs_lock);
- *prev = pending;
- pending = pending->next;
- }
+ new_tail = (cpu_data->pending_irqs_tail + 1) % MAX_PENDING_IRQS;
- 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;
+ /* 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();
}
+ 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;
+ if (local_injection)
+ irqchip.enable_maint_irq(true);
}
-int irqchip_set_pending(struct per_cpu *cpu_data, u32 irq_id, bool try_inject)
+void irqchip_inject_pending(struct per_cpu *cpu_data)
{
- struct pending_irq pending;
-
- pending.virt_id = irq_id;
- /* Priority must be less than ICC_PMR */
- pending.priority = 0;
-
- if (is_sgi(irq_id)) {
- pending.hw = 0;
- pending.type.sgi.maintenance = 0;
- pending.type.sgi.cpuid = 0;
- } else {
- pending.hw = 1;
- pending.type.irq = irq_id;
- }
-
- if (try_inject && irqchip.inject_irq(cpu_data, &pending) == 0)
- return 0;
+ u16 irq_id;
- return irqchip_insert_pending(cpu_data, &pending);
-}
+ while (cpu_data->pending_irqs_head != cpu_data->pending_irqs_tail) {
+ irq_id = cpu_data->pending_irqs[cpu_data->pending_irqs_head];
-/*
- * 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
+ 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;
}
- return 0;
+ /*
+ * 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)
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;
+ cpu_data->pending_irqs_head = cpu_data->pending_irqs_tail = 0;
- if (irqchip.cpu_reset)
- return irqchip.cpu_reset(cpu_data, false);
-
- return 0;
+ return irqchip.cpu_reset(cpu_data, false);
}
void irqchip_cpu_shutdown(struct per_cpu *cpu_data)
{
- if (irqchip.cpu_reset)
- irqchip.cpu_reset(cpu_data, true);
-}
-
-int irqchip_mmio_access(struct per_cpu *cpu_data, struct mmio_access *access)
-{
- if (irqchip.mmio_access)
- return irqchip.mmio_access(cpu_data, access);
-
- return TRAP_UNHANDLED;
-}
-
-static const struct jailhouse_irqchip *
-irqchip_find_config(struct jailhouse_cell_desc *config)
-{
- const struct jailhouse_irqchip *irq_config =
- jailhouse_cell_irqchips(config);
-
- if (config->num_irqchips)
- return irq_config;
- else
- return NULL;
+ /*
+ * The GIC backend must take care of only resetting the hyp interface if
+ * it has been initialised: this function may be executed during the
+ * setup phase.
+ */
+ irqchip.cpu_reset(cpu_data, true);
}
-void irqchip_cell_init(struct cell *cell)
+int irqchip_cell_init(struct cell *cell)
{
- const struct jailhouse_irqchip *pins = irqchip_find_config(cell->config);
-
- cell->arch.spis = (pins ? pins->pin_bitmap : 0);
+ const struct jailhouse_irqchip *chip;
+ unsigned int n;
+
+ 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));
- irqchip.cell_init(cell);
+ return irqchip.cell_init(cell);
}
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;
+
+ /* 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];
+ }
- if (root_pins)
- root_cell.arch.spis |= cell->arch.spis & root_pins->pin_bitmap;
+ /* 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];
+ }
irqchip.cell_exit(cell);
}
void irqchip_root_cell_shrink(struct cell *cell)
{
- root_cell.arch.spis &= ~(cell->arch.spis);
+ const struct jailhouse_irqchip *irqchip;
+ unsigned int n, pos;
+
+ for_each_irqchip(irqchip, cell->config, n) {
+ if (irqchip->address != (unsigned long)gicd_base)
+ continue;
+ for (pos = 0; pos < ARRAY_SIZE(irqchip->pin_bitmap); pos++)
+ root_cell.arch.irq_bitmap[irqchip->pin_base / 32] &=
+ ~irqchip->pin_bitmap[pos];
+ }
}
-/* Only the GIC is implemented */
-extern struct irqchip_ops gic_irqchip;
-
int irqchip_init(void)
{
int i, err;
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));
break;
+ default:
+ goto err_no_distributor;
}
if (irqchip.init) {
}
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;