summaryrefslogtreecommitdiff
path: root/big-little/common/vgiclib.c
diff options
context:
space:
mode:
Diffstat (limited to 'big-little/common/vgiclib.c')
-rw-r--r--big-little/common/vgiclib.c443
1 files changed, 443 insertions, 0 deletions
diff --git a/big-little/common/vgiclib.c b/big-little/common/vgiclib.c
new file mode 100644
index 0000000..5ee82ae
--- /dev/null
+++ b/big-little/common/vgiclib.c
@@ -0,0 +1,443 @@
+/*
+ * $Copyright:
+ * ----------------------------------------------------------------
+ * This confidential and proprietary software may be used only as
+ * authorised by a licensing agreement from ARM Limited
+ * (C) COPYRIGHT 2008-2011 ARM Limited
+ * ALL RIGHTS RESERVED
+ * The entire notice above must be reproduced on all authorised
+ * copies and copies may only be made to the extent permitted
+ * by a licensing agreement from ARM Limited.
+ * ----------------------------------------------------------------
+ * File: vgiclib.c
+ * ----------------------------------------------------------------
+ * $
+ */
+
+#include "vgiclib.h"
+#include "misc.h"
+#include "virt_helpers.h"
+#include "int_master.h"
+
+/*
+ * Manage overflowints somehow.. static pool with recycling allocators.
+ */
+
+#define MAXOVERFLOWINTS 200
+
+static struct overflowint *freeoverflows[NUM_CPUS];
+static struct overflowint theoverflowints[NUM_CPUS][MAXOVERFLOWINTS];
+static struct gic_cpuif cpuifs[NUM_CPUS];
+
+void dump_vgic_state()
+{
+ unsigned int i;
+
+ printf("VGIC state:\n");
+ printf(" Control : 0x%x \n", read32(VGIC_HV_PHY_BASE + GICH_CTL));
+ printf(" ActivePri: 0x%x \n", read32(VGIC_HV_PHY_BASE + GICH_APR0));
+ for (i = 0; i < 4; i++) {
+ printf(" List : 0x%x \n", read32(VGIC_HV_PHY_BASE + GICH_LR_BASE + (i * 4)));
+ }
+}
+
+static struct overflowint *get_overflowint(unsigned cpuid)
+{
+ struct overflowint *p = freeoverflows[cpuid];
+
+ if (!p) {
+ printf("Panic: Out of overflow interrupt slots.\n");
+ printf("Recompile with larger MAXOVERFLOWINTS.\n");
+ panic();
+ }
+
+ freeoverflows[cpuid] = p->next;
+
+ return p;
+}
+
+static void free_overflowint(struct overflowint *p, unsigned cpuid)
+{
+ p->next = freeoverflows[cpuid];
+ freeoverflows[cpuid] = p;
+}
+
+void vgic_init(void)
+{
+ unsigned int i;
+ unsigned cpuid = read_cpuid();
+
+ freeoverflows[cpuid] = 0x0;
+
+ for (i = 0; i < MAXOVERFLOWINTS; i++) {
+ free_overflowint(&(theoverflowints[cpuid][i]), cpuid);
+ }
+
+ /* Enable virtual interrupts & if required, maintenance interrupts */
+ write32(VGIC_HV_PHY_BASE + GICH_CTL, VGICH_HCR_EN);
+
+ return;
+}
+
+/*
+ * Abstracted entry accessor functions. Work for live or saved state
+ */
+static void set_vgic_entry(unsigned int descr, unsigned int slot)
+{
+ write32(VGIC_HV_PHY_BASE + GICH_LR_BASE + (slot * 4), descr);
+}
+
+static unsigned int get_vgic_entry(unsigned int slot)
+{
+ return read32(VGIC_HV_PHY_BASE + GICH_LR_BASE + (slot * 4));
+}
+
+/*
+ * Abstracted status accessor functions, as above
+ */
+static void set_vgic_status(unsigned int status)
+{
+ write32(VGIC_HV_PHY_BASE + GICH_CTL, status);
+}
+
+static unsigned int get_vgic_status(void)
+{
+ return read32(VGIC_HV_PHY_BASE + GICH_CTL);
+}
+
+/*
+ * Add an entry to the queue, the queue is kept in descending priority
+ * * (that is to say, ascending numerical priority) order.
+ * *
+ * * Static function to assist with this, only called if the int is going in the queue.
+ */
+static void set_vgic_queue_entry(struct gic_cpuif *cpuif, unsigned int descr)
+{
+ unsigned int pri = (descr >> 20) & 0xFF;
+ struct overflowint **oflowh, *oflowp;
+ unsigned cpuid = read_cpuid();
+
+ /*
+ * If we are queuing something and there is currently no queue, set the interrupt bit
+ */
+ if (!(cpuif->overflow))
+ set_vgic_status(get_vgic_status() | 0x2);
+
+ /*
+ * Determine insertion point, might be the end of the list
+ */
+ for (oflowh = &(cpuif->overflow); *oflowh; oflowh = &((*oflowh)->next))
+ if ((*oflowh)->priority > pri)
+ break;
+
+ oflowp = get_overflowint(cpuid);
+ oflowp->priority = pri;
+ oflowp->value = descr;
+ oflowp->next = *oflowh;
+ *oflowh = oflowp;
+}
+
+void vgic_savestate(unsigned int cpu)
+{
+ struct gic_cpuif *cpuif = &(cpuifs[cpu]);
+ unsigned int i, ctr = 0, cur_elrsr = 0;
+
+ for(ctr = 0; ctr < 2; ctr++) {
+ /* Negate read value so that set bit corresponds to a !inactive register */
+ cur_elrsr = ~(read32(VGIC_HV_PHY_BASE + GICH_ELRSR0 + (ctr << 2)));
+ cpuif->elrsr[ctr] = cur_elrsr;
+
+ for(i = bitindex(cur_elrsr); ((int) i) >= 0; i = bitindex(cur_elrsr)) {
+ unsigned list_reg =
+ read32(VGIC_HV_PHY_BASE + GICH_LR_BASE + (i << 2));
+ unsigned int_id = (list_reg >> 10) & 0x3ff;
+
+ /* Clear the saved bit index */
+ cur_elrsr &= ~(1 << i);
+
+ /*
+ * Invalidate the pending/active virtual interrupt. Since its a shared vGIC
+ * this irq will persist till the next switch and hence create a duplicate.
+ */
+ write32(VGIC_HV_PHY_BASE + GICH_LR_BASE + (i << 2), list_reg & ~(0x3 << 28));
+
+ /*
+ * While saving queued IPI context, ensure that the requesting cpu
+ * interface is mapped to it counterpart on the inbound cluster
+ */
+ if (int_id < 16) {
+ unsigned ob_cpuid = int_id & 0x7;
+ unsigned ob_clusterid = read_clusterid();
+ unsigned ib_cpuif = 0;
+
+ ib_cpuif = get_cpuif(!ob_clusterid, ob_cpuid);
+ /* Clear the cpu interface bits and place inbound cpu interface instead */
+ list_reg = (list_reg & ~(0x7 << 10)) | (ib_cpuif << 10);
+ } else if (int_id < 32) {
+ /*
+ * Pending Private peripheral interrupts will be recreated from scratch
+ * so no need to save them.
+ */
+ cpuif->elrsr[ctr] &= ~(1 << i);
+ continue;
+ }
+
+ cpuif->ints[i] = list_reg;
+
+ }
+ }
+
+ cpuif->status = read32(VGIC_HV_PHY_BASE + GICH_CTL);
+ cpuif->activepris = read32(VGIC_HV_PHY_BASE + GICH_APR0);
+
+ write32(VGIC_HV_PHY_BASE + GICH_CTL, 0); /* SMP */
+
+ return;
+}
+
+void vgic_loadstate(unsigned int cpu)
+{
+ struct gic_cpuif *cpuif = &(cpuifs[cpu]);
+ unsigned int i, ctr = 0, cur_elrsr = 0;
+
+ for(ctr = 0; ctr < 2; ctr++) {
+ cur_elrsr = cpuif->elrsr[ctr];
+
+ for(i = bitindex(cur_elrsr); ((int) i) >= 0; i = bitindex(cur_elrsr)) {
+ write32(VGIC_HV_PHY_BASE + GICH_LR_BASE + (i << 2), cpuif->ints[i]);
+
+ /* Clear the restored bit index */
+ cur_elrsr &= ~(1 << i);
+ }
+ }
+
+ write32(VGIC_HV_PHY_BASE + GICH_CTL, cpuif->status);
+ write32(VGIC_HV_PHY_BASE + GICH_APR0, cpuif->activepris);
+
+ return;
+}
+
+/*
+ * vgic_refresh: Generic "maintenance" routine for the VGIC
+ * *
+ * * This is called:
+ * * - On maintenance interrupt. We get maintenance interrupts for
+ * * two reasons:
+ * * o Non-zero EOI skid. This routine deals with the skid and sets
+ * * the field to 0, quenching the interrupt source.
+ * * o "Nearly empty" interrupt bit set, and nearly empty condition
+ * * exists. This interrupt source is quenched by filling the
+ * * slots (and clearing the interrupt bit if the queue is now empty)
+ * * - When a new interrupt arrives and the cached "free slot" value
+ * * indicates that there are no free slots. We expect to scavenge some
+ * * slots from interrupts which have been completed by the VM.
+ * *
+ * * This routine is O(n) in the number of skidded EOI's + O(m) in the number
+ * * of interrupt slots provided - since this is constant for an
+ * * implementation it's really O(1).
+ * *
+ * * If this VGIC instance is currently live on a CPU it is only legal to
+ * * execute this routine on that CPU.
+ */
+void vgic_refresh(unsigned int cpu)
+{
+ struct gic_cpuif *cpuif = &(cpuifs[cpu]);
+ unsigned int i, value, status, newstatus;
+ struct overflowint **oflowh, *oflowp;
+
+ /*
+ * Grab a copy of the status.
+ */
+ status = get_vgic_status();
+
+ /*
+ * "newstatus" is the value to be written back if needed. Whatever
+ * * happens, we will clear the slipped EOI count by the time we are done
+ */
+ newstatus = status & 0x07FFFFFF;
+
+ /*
+ * See if there are any "slipped" EOIs
+ */
+ i = (status >> 27) & 0x1F;
+
+ if (i) {
+ /*
+ * If there are, let's deal with them.
+ * *
+ * * We will walk through the list of queued interrupts, deactivating the
+ * * ACTIVE ones as needed until we either have no more slipped EOI's to
+ * * do or run out of queued interrupts. If we run out of queued
+ * * interrupts first, that's UNPREDICTABLE behaviour (and the fault of
+ * * the VM). In this case we will just ignore the surplus EOIs.
+ * *
+ * * After EOI'ing, we delete the entry if it was just ACTIVE or set it
+ * * to PENDING if it was PENDING+ACTIVE.
+ * *
+ * * Use a handle to point to the list entries to avoid the need for
+ * * special cases in the loop.
+ */
+ oflowh = &(cpuif->overflow);
+
+ while (i && *oflowh) {
+ value = (*oflowh)->value;
+ if (value & VGIC_ENTRY_ACTIVE) {
+ /*
+ * It's ACTIVE (or PENDING+ACTIVE)
+ */
+ i--;
+
+ if (value & VGIC_ENTRY_HW) {
+ /*
+ * HW bit set, so we need to pass on an EOI. This doesn't ever happen
+ * * for IPIs, so just pass on the 10-bit "Hardware ID"
+ */
+ gic_deactivate_int((value >> 10) &
+ 0x3FF);
+ }
+
+ if (value & VGIC_ENTRY_PENDING) {
+ /*
+ * It was PENDING+ACTIVE, clear the ACTIVE bit and move on
+ */
+ (*oflowh)->value &= ~VGIC_ENTRY_ACTIVE;
+ } else {
+ /*
+ * It was only ACTIVE, so we need to delete it..
+ */
+ oflowp = *oflowh;
+ oflowh = &(oflowp->next);
+ free_overflowint(oflowp, cpu);
+ }
+ } else {
+ /*
+ * It wasn't ACTIVE :( Try the next one.
+ */
+ oflowh = &((*oflowh)->next);
+ }
+ }
+ }
+
+ /*
+ * Now populate any spare slots with entries from the list (if any). Also fix up the free slot bitmap
+ */
+ for (i = 0; i < VGIC_LISTENTRIES; i++) {
+ value = get_vgic_entry(i);
+
+ if (value & 0x30000000) {
+ /*
+ * This entry already contains a valid interrupt, skip
+ */
+ continue;
+ }
+
+ /*
+ * Not a valid interrupt
+ */
+ oflowp = cpuif->overflow;
+ if (oflowp) {
+ /*
+ * If there's a queue, move the top entry out of the queue and into
+ * * this slot..
+ */
+ cpuif->overflow = oflowp->next;
+
+ set_vgic_entry(oflowp->value, i);
+ free_overflowint(oflowp, cpu);
+ } else {
+ /*
+ * .. otherwise mark it as available.
+ */
+ cpuif->freelist |= (1 << i);
+ }
+ }
+
+ /*
+ * If we now don't have any overflow, clear the status bit
+ */
+ if (!(cpuif->overflow)) {
+ newstatus &= ~0x2;
+ }
+
+ /*
+ * Refresh status if needed
+ */
+ if (newstatus != status) {
+ set_vgic_status(newstatus);
+ }
+}
+
+/*
+ * Adds the interrupt specified to the active list of the CPU specified.
+ * Expected to cope with the state being live on that CPU, or not.
+ *
+ * It's only valid to call this on the CPU which the corresponding VCPUIF is live on.
+ *
+ * This is O(n) in the number of queued interrupts on the CPUIF in question.
+ */
+void enqueue_interrupt(unsigned int descr, unsigned int cpu)
+{
+ unsigned int slot;
+ struct gic_cpuif *cpuif;
+
+ cpuif = &(cpuifs[cpu]);
+
+ /*
+ * If there are no free slots, trigger a maintenance
+ */
+ if (!(cpuif->freelist)) {
+ vgic_refresh(cpu);
+ }
+
+ if (cpuif->freelist) {
+ /*
+ * There is a free slot, use it.
+ */
+ slot = cpuif->freelist; /* Take the free list.. */
+ slot &= (-slot); /* .. extract one set bit .. */
+ cpuif->freelist &= (~slot); /* .. clear that bit from free list .. */
+ slot = bitindex(slot); /* .. and convert to number. */
+
+ set_vgic_entry(descr, slot);
+ } else {
+ /*
+ * There are no free slots, we are either queuing this one or swapping another out
+ */
+ unsigned int pri = (descr >> 20) & 0xFF;
+ unsigned int minpri = 0;
+ unsigned int minslot = 0;
+ unsigned int i, j;
+
+ if (cpuif->overflow && cpuif->overflow->priority <= pri) {
+ /*
+ * There are already queued interrupts with the same or higher priority, just queue this one
+ */
+ set_vgic_queue_entry(cpuif, descr);
+ return;
+ }
+
+ /*
+ * Otherwise find the lowest priority entry..
+ */
+ for (i = 0; i < VGIC_LISTENTRIES; i++) {
+ j = (get_vgic_entry(i) >> 20) & 0xFF; /* Get the priority for the current thing in this slot */
+ if (i == 0 || (j > minpri)) {
+ minpri = j;
+ minslot = i;
+ }
+ }
+
+ if (minpri > pri) {
+ /*
+ * If it's lower priority than this new one we kick it out
+ */
+ set_vgic_queue_entry(cpuif, get_vgic_entry(minslot));
+ set_vgic_entry(descr, minslot);
+ } else {
+ /*
+ * Otherwise just queue the new one
+ */
+ set_vgic_queue_entry(cpuif, descr);
+ }
+ }
+}