aboutsummaryrefslogtreecommitdiffstats
path: root/hw/arm_gic.c
diff options
context:
space:
mode:
authorpbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162>2007-11-11 00:04:49 +0000
committerpbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162>2007-11-11 00:04:49 +0000
commit9ee6e8bb853bdea7ef6c645a1a07aa55fd206aba (patch)
tree1cd430d3d9ac641c8550cfd8956dbcce1a4b9121 /hw/arm_gic.c
parentee4e83ed8ddc8dac572a0123398adf78b63014ae (diff)
ARMv7 support.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@3572 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'hw/arm_gic.c')
-rw-r--r--hw/arm_gic.c460
1 files changed, 294 insertions, 166 deletions
diff --git a/hw/arm_gic.c b/hw/arm_gic.c
index 8cd7182cf..774b79bf8 100644
--- a/hw/arm_gic.c
+++ b/hw/arm_gic.c
@@ -1,17 +1,15 @@
/*
- * ARM AMBA Generic/Distributed Interrupt Controller
+ * ARM Generic/Distributed Interrupt Controller
*
- * Copyright (c) 2006 CodeSourcery.
+ * Copyright (c) 2006-2007 CodeSourcery.
* Written by Paul Brook
*
* This code is licenced under the GPL.
*/
-/* TODO: Some variants of this controller can handle multiple CPUs.
- Currently only single CPU operation is implemented. */
-
-#include "vl.h"
-#include "arm_pic.h"
+/* This file contains implementation code for the RealView EB interrupt
+ controller, MPCore distributed interrupt controller and ARMv7-M
+ Nested Vectored Interrupt Controller. */
//#define DEBUG_GIC
@@ -22,58 +20,84 @@ do { printf("arm_gic: " fmt , ##args); } while (0)
#define DPRINTF(fmt, args...) do {} while(0)
#endif
-/* Distributed interrupt controller. */
-
+#ifdef NVIC
+static const uint8_t gic_id[] =
+{ 0x00, 0xb0, 0x1b, 0x00, 0x0d, 0xe0, 0x05, 0xb1 };
+#define GIC_DIST_OFFSET 0
+/* The NVIC has 16 internal vectors. However these are not exposed
+ through the normal GIC interface. */
+#define GIC_BASE_IRQ 32
+#else
static const uint8_t gic_id[] =
{ 0x90, 0x13, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
-
-#define GIC_NIRQ 96
+#define GIC_DIST_OFFSET 0x1000
+#define GIC_BASE_IRQ 0
+#endif
typedef struct gic_irq_state
{
+ /* ??? The documentation seems to imply the enable bits are global, even
+ for per-cpu interrupts. This seems strange. */
unsigned enabled:1;
- unsigned pending:1;
- unsigned active:1;
+ unsigned pending:NCPU;
+ unsigned active:NCPU;
unsigned level:1;
- unsigned model:1; /* 0 = 1:N, 1 = N:N */
+ unsigned model:1; /* 0 = N:N, 1 = 1:N */
unsigned trigger:1; /* nonzero = edge triggered. */
} gic_irq_state;
+#define ALL_CPU_MASK ((1 << NCPU) - 1)
+
#define GIC_SET_ENABLED(irq) s->irq_state[irq].enabled = 1
#define GIC_CLEAR_ENABLED(irq) s->irq_state[irq].enabled = 0
#define GIC_TEST_ENABLED(irq) s->irq_state[irq].enabled
-#define GIC_SET_PENDING(irq) s->irq_state[irq].pending = 1
-#define GIC_CLEAR_PENDING(irq) s->irq_state[irq].pending = 0
-#define GIC_TEST_PENDING(irq) s->irq_state[irq].pending
-#define GIC_SET_ACTIVE(irq) s->irq_state[irq].active = 1
-#define GIC_CLEAR_ACTIVE(irq) s->irq_state[irq].active = 0
-#define GIC_TEST_ACTIVE(irq) s->irq_state[irq].active
+#define GIC_SET_PENDING(irq, cm) s->irq_state[irq].pending |= (cm)
+#define GIC_CLEAR_PENDING(irq, cm) s->irq_state[irq].pending &= ~(cm)
+#define GIC_TEST_PENDING(irq, cm) ((s->irq_state[irq].pending & (cm)) != 0)
+#define GIC_SET_ACTIVE(irq, cm) s->irq_state[irq].active |= (cm)
+#define GIC_CLEAR_ACTIVE(irq, cm) s->irq_state[irq].active &= ~(cm)
+#define GIC_TEST_ACTIVE(irq, cm) ((s->irq_state[irq].active & (cm)) != 0)
#define GIC_SET_MODEL(irq) s->irq_state[irq].model = 1
#define GIC_CLEAR_MODEL(irq) s->irq_state[irq].model = 0
#define GIC_TEST_MODEL(irq) s->irq_state[irq].model
-#define GIC_SET_LEVEL(irq) s->irq_state[irq].level = 1
-#define GIC_CLEAR_LEVEL(irq) s->irq_state[irq].level = 0
-#define GIC_TEST_LEVEL(irq) s->irq_state[irq].level
+#define GIC_SET_LEVEL(irq, cm) s->irq_state[irq].level = (cm)
+#define GIC_CLEAR_LEVEL(irq, cm) s->irq_state[irq].level &= ~(cm)
+#define GIC_TEST_LEVEL(irq, cm) (s->irq_state[irq].level & (cm)) != 0
#define GIC_SET_TRIGGER(irq) s->irq_state[irq].trigger = 1
#define GIC_CLEAR_TRIGGER(irq) s->irq_state[irq].trigger = 0
#define GIC_TEST_TRIGGER(irq) s->irq_state[irq].trigger
+#define GIC_GET_PRIORITY(irq, cpu) \
+ (((irq) < 32) ? s->priority1[irq][cpu] : s->priority2[(irq) - 32])
+#ifdef NVIC
+#define GIC_TARGET(irq) 1
+#else
+#define GIC_TARGET(irq) s->irq_target[irq]
+#endif
typedef struct gic_state
{
uint32_t base;
- qemu_irq parent_irq;
+ qemu_irq parent_irq[NCPU];
int enabled;
- int cpu_enabled;
+ int cpu_enabled[NCPU];
gic_irq_state irq_state[GIC_NIRQ];
+#ifndef NVIC
int irq_target[GIC_NIRQ];
- int priority[GIC_NIRQ];
- int last_active[GIC_NIRQ];
-
- int priority_mask;
- int running_irq;
- int running_priority;
- int current_pending;
+#endif
+ int priority1[32][NCPU];
+ int priority2[GIC_NIRQ - 32];
+ int last_active[GIC_NIRQ][NCPU];
+
+ int priority_mask[NCPU];
+ int running_irq[NCPU];
+ int running_priority[NCPU];
+ int current_pending[NCPU];
+
+ qemu_irq *in;
+#ifdef NVIC
+ void *nvic;
+#endif
} gic_state;
/* TODO: Many places that call this routine could be optimized. */
@@ -83,112 +107,136 @@ static void gic_update(gic_state *s)
int best_irq;
int best_prio;
int irq;
-
- s->current_pending = 1023;
- if (!s->enabled || !s->cpu_enabled) {
- qemu_irq_lower(s->parent_irq);
- return;
- }
- best_prio = 0x100;
- best_irq = 1023;
- for (irq = 0; irq < 96; irq++) {
- if (GIC_TEST_ENABLED(irq) && GIC_TEST_PENDING(irq)) {
- if (s->priority[irq] < best_prio) {
- best_prio = s->priority[irq];
- best_irq = irq;
+ int level;
+ int cpu;
+ int cm;
+
+ for (cpu = 0; cpu < NCPU; cpu++) {
+ cm = 1 << cpu;
+ s->current_pending[cpu] = 1023;
+ if (!s->enabled || !s->cpu_enabled[cpu]) {
+ qemu_irq_lower(s->parent_irq[cpu]);
+ return;
+ }
+ best_prio = 0x100;
+ best_irq = 1023;
+ for (irq = 0; irq < GIC_NIRQ; irq++) {
+ if (GIC_TEST_ENABLED(irq) && GIC_TEST_PENDING(irq, cm)) {
+ if (GIC_GET_PRIORITY(irq, cpu) < best_prio) {
+ best_prio = GIC_GET_PRIORITY(irq, cpu);
+ best_irq = irq;
+ }
}
}
- }
- if (best_prio > s->priority_mask) {
- qemu_irq_lower(s->parent_irq);
- } else {
- s->current_pending = best_irq;
- if (best_prio < s->running_priority) {
- DPRINTF("Raised pending IRQ %d\n", best_irq);
- qemu_irq_raise(s->parent_irq);
+ level = 0;
+ if (best_prio <= s->priority_mask[cpu]) {
+ s->current_pending[cpu] = best_irq;
+ if (best_prio < s->running_priority[cpu]) {
+ DPRINTF("Raised pending IRQ %d\n", best_irq);
+ level = 1;
+ }
}
+ qemu_set_irq(s->parent_irq[cpu], level);
}
}
+static void __attribute__((unused))
+gic_set_pending_private(gic_state *s, int cpu, int irq)
+{
+ int cm = 1 << cpu;
+
+ if (GIC_TEST_PENDING(irq, cm))
+ return;
+
+ DPRINTF("Set %d pending cpu %d\n", irq, cpu);
+ GIC_SET_PENDING(irq, cm);
+ gic_update(s);
+}
+
+/* Process a change in an external IRQ input. */
static void gic_set_irq(void *opaque, int irq, int level)
{
gic_state *s = (gic_state *)opaque;
/* The first external input line is internal interrupt 32. */
irq += 32;
- if (level == GIC_TEST_LEVEL(irq))
+ if (level == GIC_TEST_LEVEL(irq, ALL_CPU_MASK))
return;
if (level) {
- GIC_SET_LEVEL(irq);
+ GIC_SET_LEVEL(irq, ALL_CPU_MASK);
if (GIC_TEST_TRIGGER(irq) || GIC_TEST_ENABLED(irq)) {
- DPRINTF("Set %d pending\n", irq);
- GIC_SET_PENDING(irq);
+ DPRINTF("Set %d pending mask %x\n", irq, GIC_TARGET(irq));
+ GIC_SET_PENDING(irq, GIC_TARGET(irq));
}
} else {
- GIC_CLEAR_LEVEL(irq);
+ GIC_CLEAR_LEVEL(irq, ALL_CPU_MASK);
}
gic_update(s);
}
-static void gic_set_running_irq(gic_state *s, int irq)
+static void gic_set_running_irq(gic_state *s, int cpu, int irq)
{
- s->running_irq = irq;
- if (irq == 1023)
- s->running_priority = 0x100;
- else
- s->running_priority = s->priority[irq];
+ s->running_irq[cpu] = irq;
+ if (irq == 1023) {
+ s->running_priority[cpu] = 0x100;
+ } else {
+ s->running_priority[cpu] = GIC_GET_PRIORITY(irq, cpu);
+ }
gic_update(s);
}
-static uint32_t gic_acknowledge_irq(gic_state *s)
+static uint32_t gic_acknowledge_irq(gic_state *s, int cpu)
{
int new_irq;
- new_irq = s->current_pending;
- if (new_irq == 1023 || s->priority[new_irq] >= s->running_priority) {
+ int cm = 1 << cpu;
+ new_irq = s->current_pending[cpu];
+ if (new_irq == 1023
+ || GIC_GET_PRIORITY(new_irq, cpu) >= s->running_priority[cpu]) {
DPRINTF("ACK no pending IRQ\n");
return 1023;
}
- qemu_irq_lower(s->parent_irq);
- s->last_active[new_irq] = s->running_irq;
- /* For level triggered interrupts we clear the pending bit while
- the interrupt is active. */
- GIC_CLEAR_PENDING(new_irq);
- gic_set_running_irq(s, new_irq);
+ s->last_active[new_irq][cpu] = s->running_irq[cpu];
+ /* Clear pending flags for both level and edge triggered interrupts.
+ Level triggered IRQs will be reasserted once they become inactive. */
+ GIC_CLEAR_PENDING(new_irq, GIC_TEST_MODEL(new_irq) ? ALL_CPU_MASK : cm);
+ gic_set_running_irq(s, cpu, new_irq);
DPRINTF("ACK %d\n", new_irq);
return new_irq;
}
-static void gic_complete_irq(gic_state * s, int irq)
+static void gic_complete_irq(gic_state * s, int cpu, int irq)
{
int update = 0;
+ int cm = 1 << cpu;
DPRINTF("EOI %d\n", irq);
- if (s->running_irq == 1023)
+ if (s->running_irq[cpu] == 1023)
return; /* No active IRQ. */
if (irq != 1023) {
/* Mark level triggered interrupts as pending if they are still
raised. */
if (!GIC_TEST_TRIGGER(irq) && GIC_TEST_ENABLED(irq)
- && GIC_TEST_LEVEL(irq)) {
- GIC_SET_PENDING(irq);
+ && GIC_TEST_LEVEL(irq, cm) && (GIC_TARGET(irq) & cm) != 0) {
+ DPRINTF("Set %d pending mask %x\n", irq, cm);
+ GIC_SET_PENDING(irq, cm);
update = 1;
}
}
- if (irq != s->running_irq) {
+ if (irq != s->running_irq[cpu]) {
/* Complete an IRQ that is not currently running. */
- int tmp = s->running_irq;
- while (s->last_active[tmp] != 1023) {
- if (s->last_active[tmp] == irq) {
- s->last_active[tmp] = s->last_active[irq];
+ int tmp = s->running_irq[cpu];
+ while (s->last_active[tmp][cpu] != 1023) {
+ if (s->last_active[tmp][cpu] == irq) {
+ s->last_active[tmp][cpu] = s->last_active[irq][cpu];
break;
}
- tmp = s->last_active[tmp];
+ tmp = s->last_active[tmp][cpu];
}
if (update) {
gic_update(s);
}
} else {
/* Complete the current running IRQ. */
- gic_set_running_irq(s, s->last_active[s->running_irq]);
+ gic_set_running_irq(s, cpu, s->last_active[s->running_irq[cpu]][cpu]);
}
}
@@ -198,15 +246,22 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
uint32_t res;
int irq;
int i;
+ int cpu;
+ int cm;
+ int mask;
- offset -= s->base + 0x1000;
+ cpu = gic_get_current_cpu();
+ cm = 1 << cpu;
+ offset -= s->base + GIC_DIST_OFFSET;
if (offset < 0x100) {
+#ifndef NVIC
if (offset == 0)
return s->enabled;
if (offset == 4)
- return (GIC_NIRQ / 32) - 1;
+ return ((GIC_NIRQ / 32) - 1) | ((NCPU - 1) << 5);
if (offset < 0x08)
return 0;
+#endif
goto bad_reg;
} else if (offset < 0x200) {
/* Interrupt Set/Clear Enable. */
@@ -214,6 +269,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
irq = (offset - 0x100) * 8;
else
irq = (offset - 0x180) * 8;
+ irq += GIC_BASE_IRQ;
if (irq >= GIC_NIRQ)
goto bad_reg;
res = 0;
@@ -228,40 +284,48 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
irq = (offset - 0x200) * 8;
else
irq = (offset - 0x280) * 8;
+ irq += GIC_BASE_IRQ;
if (irq >= GIC_NIRQ)
goto bad_reg;
res = 0;
+ mask = (irq < 32) ? cm : ALL_CPU_MASK;
for (i = 0; i < 8; i++) {
- if (GIC_TEST_PENDING(irq + i)) {
+ if (GIC_TEST_PENDING(irq + i, mask)) {
res |= (1 << i);
}
}
} else if (offset < 0x400) {
/* Interrupt Active. */
- irq = (offset - 0x300) * 8;
+ irq = (offset - 0x300) * 8 + GIC_BASE_IRQ;
if (irq >= GIC_NIRQ)
goto bad_reg;
res = 0;
+ mask = (irq < 32) ? cm : ALL_CPU_MASK;
for (i = 0; i < 8; i++) {
- if (GIC_TEST_ACTIVE(irq + i)) {
+ if (GIC_TEST_ACTIVE(irq + i, mask)) {
res |= (1 << i);
}
}
} else if (offset < 0x800) {
/* Interrupt Priority. */
- irq = offset - 0x400;
+ irq = (offset - 0x400) + GIC_BASE_IRQ;
if (irq >= GIC_NIRQ)
goto bad_reg;
- res = s->priority[irq];
+ res = GIC_GET_PRIORITY(irq, cpu);
+#ifndef NVIC
} else if (offset < 0xc00) {
/* Interrupt CPU Target. */
- irq = offset - 0x800;
+ irq = (offset - 0x800) + GIC_BASE_IRQ;
if (irq >= GIC_NIRQ)
goto bad_reg;
- res = s->irq_target[irq];
+ if (irq >= 29 && irq <= 31) {
+ res = cm;
+ } else {
+ res = GIC_TARGET(irq);
+ }
} else if (offset < 0xf00) {
/* Interrupt Configuration. */
- irq = (offset - 0xc00) * 2;
+ irq = (offset - 0xc00) * 2 + GIC_BASE_IRQ;
if (irq >= GIC_NIRQ)
goto bad_reg;
res = 0;
@@ -271,6 +335,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
if (GIC_TEST_TRIGGER(irq + i))
res |= (2 << (i * 2));
}
+#endif
} else if (offset < 0xfe0) {
goto bad_reg;
} else /* offset >= 0xfe0 */ {
@@ -282,7 +347,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
}
return res;
bad_reg:
- cpu_abort (cpu_single_env, "gic_dist_readb: Bad offset %x\n", offset);
+ cpu_abort(cpu_single_env, "gic_dist_readb: Bad offset %x\n", (int)offset);
return 0;
}
@@ -297,6 +362,13 @@ static uint32_t gic_dist_readw(void *opaque, target_phys_addr_t offset)
static uint32_t gic_dist_readl(void *opaque, target_phys_addr_t offset)
{
uint32_t val;
+#ifdef NVIC
+ gic_state *s = (gic_state *)opaque;
+ uint32_t addr;
+ addr = offset - s->base;
+ if (addr < 0x100 || addr > 0xd00)
+ return nvic_readl(s->nvic, addr);
+#endif
val = gic_dist_readw(opaque, offset);
val |= gic_dist_readw(opaque, offset + 2) << 16;
return val;
@@ -308,9 +380,14 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
gic_state *s = (gic_state *)opaque;
int irq;
int i;
+ int cpu;
- offset -= s->base + 0x1000;
+ cpu = gic_get_current_cpu();
+ offset -= s->base + GIC_DIST_OFFSET;
if (offset < 0x100) {
+#ifdef NVIC
+ goto bad_reg;
+#else
if (offset == 0) {
s->enabled = (value & 1);
DPRINTF("Distribution %sabled\n", s->enabled ? "En" : "Dis");
@@ -319,27 +396,36 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
} else {
goto bad_reg;
}
+#endif
} else if (offset < 0x180) {
/* Interrupt Set Enable. */
- irq = (offset - 0x100) * 8;
+ irq = (offset - 0x100) * 8 + GIC_BASE_IRQ;
if (irq >= GIC_NIRQ)
goto bad_reg;
+ if (irq < 16)
+ value = 0xff;
for (i = 0; i < 8; i++) {
if (value & (1 << i)) {
+ int mask = (irq < 32) ? (1 << cpu) : GIC_TARGET(irq);
if (!GIC_TEST_ENABLED(irq + i))
DPRINTF("Enabled IRQ %d\n", irq + i);
GIC_SET_ENABLED(irq + i);
/* If a raised level triggered IRQ enabled then mark
is as pending. */
- if (GIC_TEST_LEVEL(irq + i) && !GIC_TEST_TRIGGER(irq + i))
- GIC_SET_PENDING(irq + i);
+ if (GIC_TEST_LEVEL(irq + i, mask)
+ && !GIC_TEST_TRIGGER(irq + i)) {
+ DPRINTF("Set %d pending mask %x\n", irq + i, mask);
+ GIC_SET_PENDING(irq + i, mask);
+ }
}
}
} else if (offset < 0x200) {
/* Interrupt Clear Enable. */
- irq = (offset - 0x180) * 8;
+ irq = (offset - 0x180) * 8 + GIC_BASE_IRQ;
if (irq >= GIC_NIRQ)
goto bad_reg;
+ if (irq < 16)
+ value = 0;
for (i = 0; i < 8; i++) {
if (value & (1 << i)) {
if (GIC_TEST_ENABLED(irq + i))
@@ -349,22 +435,28 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
}
} else if (offset < 0x280) {
/* Interrupt Set Pending. */
- irq = (offset - 0x200) * 8;
+ irq = (offset - 0x200) * 8 + GIC_BASE_IRQ;
if (irq >= GIC_NIRQ)
goto bad_reg;
+ if (irq < 16)
+ irq = 0;
+
for (i = 0; i < 8; i++) {
if (value & (1 << i)) {
- GIC_SET_PENDING(irq + i);
+ GIC_SET_PENDING(irq + i, GIC_TARGET(irq));
}
}
} else if (offset < 0x300) {
/* Interrupt Clear Pending. */
- irq = (offset - 0x280) * 8;
+ irq = (offset - 0x280) * 8 + GIC_BASE_IRQ;
if (irq >= GIC_NIRQ)
goto bad_reg;
for (i = 0; i < 8; i++) {
+ /* ??? This currently clears the pending bit for all CPUs, even
+ for per-CPU interrupts. It's unclear whether this is the
+ corect behavior. */
if (value & (1 << i)) {
- GIC_CLEAR_PENDING(irq + i);
+ GIC_CLEAR_PENDING(irq + i, ALL_CPU_MASK);
}
}
} else if (offset < 0x400) {
@@ -372,21 +464,32 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
goto bad_reg;
} else if (offset < 0x800) {
/* Interrupt Priority. */
- irq = offset - 0x400;
+ irq = (offset - 0x400) + GIC_BASE_IRQ;
if (irq >= GIC_NIRQ)
goto bad_reg;
- s->priority[irq] = value;
+ if (irq < 32) {
+ s->priority1[irq][cpu] = value;
+ } else {
+ s->priority2[irq - 32] = value;
+ }
+#ifndef NVIC
} else if (offset < 0xc00) {
/* Interrupt CPU Target. */
- irq = offset - 0x800;
+ irq = (offset - 0x800) + GIC_BASE_IRQ;
if (irq >= GIC_NIRQ)
goto bad_reg;
- s->irq_target[irq] = value;
+ if (irq < 29)
+ value = 0;
+ else if (irq < 32)
+ value = ALL_CPU_MASK;
+ s->irq_target[irq] = value & ALL_CPU_MASK;
} else if (offset < 0xf00) {
/* Interrupt Configuration. */
- irq = (offset - 0xc00) * 4;
+ irq = (offset - 0xc00) * 4 + GIC_BASE_IRQ;
if (irq >= GIC_NIRQ)
goto bad_reg;
+ if (irq < 32)
+ value |= 0xaa;
for (i = 0; i < 4; i++) {
if (value & (1 << (i * 2))) {
GIC_SET_MODEL(irq + i);
@@ -399,25 +502,20 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
GIC_CLEAR_TRIGGER(irq + i);
}
}
+#endif
} else {
- /* 0xf00 is only handled for word writes. */
+ /* 0xf00 is only handled for 32-bit writes. */
goto bad_reg;
}
gic_update(s);
return;
bad_reg:
- cpu_abort (cpu_single_env, "gic_dist_writeb: Bad offset %x\n", offset);
+ cpu_abort(cpu_single_env, "gic_dist_writeb: Bad offset %x\n", (int)offset);
}
static void gic_dist_writew(void *opaque, target_phys_addr_t offset,
uint32_t value)
{
- gic_state *s = (gic_state *)opaque;
- if (offset - s->base == 0xf00) {
- GIC_SET_PENDING(value & 0x3ff);
- gic_update(s);
- return;
- }
gic_dist_writeb(opaque, offset, value & 0xff);
gic_dist_writeb(opaque, offset + 1, value >> 8);
}
@@ -425,6 +523,41 @@ static void gic_dist_writew(void *opaque, target_phys_addr_t offset,
static void gic_dist_writel(void *opaque, target_phys_addr_t offset,
uint32_t value)
{
+ gic_state *s = (gic_state *)opaque;
+#ifdef NVIC
+ uint32_t addr;
+ addr = offset - s->base;
+ if (addr < 0x100 || (addr > 0xd00 && addr != 0xf00)) {
+ nvic_writel(s->nvic, addr, value);
+ return;
+ }
+#endif
+ if (offset - s->base == GIC_DIST_OFFSET + 0xf00) {
+ int cpu;
+ int irq;
+ int mask;
+
+ cpu = gic_get_current_cpu();
+ irq = value & 0x3ff;
+ switch ((value >> 24) & 3) {
+ case 0:
+ mask = (value >> 16) & ALL_CPU_MASK;
+ break;
+ case 1:
+ mask = 1 << cpu;
+ break;
+ case 2:
+ mask = ALL_CPU_MASK ^ (1 << cpu);
+ break;
+ default:
+ DPRINTF("Bad Soft Int target filter\n");
+ mask = ALL_CPU_MASK;
+ break;
+ }
+ GIC_SET_PENDING(irq, mask);
+ gic_update(s);
+ return;
+ }
gic_dist_writew(opaque, offset, value & 0xffff);
gic_dist_writew(opaque, offset + 2, value >> 16);
}
@@ -441,105 +574,100 @@ static CPUWriteMemoryFunc *gic_dist_writefn[] = {
gic_dist_writel
};
-static uint32_t gic_cpu_read(void *opaque, target_phys_addr_t offset)
+#ifndef NVIC
+static uint32_t gic_cpu_read(gic_state *s, int cpu, int offset)
{
- gic_state *s = (gic_state *)opaque;
- offset -= s->base;
switch (offset) {
case 0x00: /* Control */
- return s->cpu_enabled;
+ return s->cpu_enabled[cpu];
case 0x04: /* Priority mask */
- return s->priority_mask;
+ return s->priority_mask[cpu];
case 0x08: /* Binary Point */
/* ??? Not implemented. */
return 0;
case 0x0c: /* Acknowledge */
- return gic_acknowledge_irq(s);
+ return gic_acknowledge_irq(s, cpu);
case 0x14: /* Runing Priority */
- return s->running_priority;
+ return s->running_priority[cpu];
case 0x18: /* Highest Pending Interrupt */
- return s->current_pending;
+ return s->current_pending[cpu];
default:
- cpu_abort (cpu_single_env, "gic_cpu_read: Bad offset %x\n", offset);
+ cpu_abort(cpu_single_env, "gic_cpu_read: Bad offset %x\n",
+ (int)offset);
return 0;
}
}
-static void gic_cpu_write(void *opaque, target_phys_addr_t offset,
- uint32_t value)
+static void gic_cpu_write(gic_state *s, int cpu, int offset, uint32_t value)
{
- gic_state *s = (gic_state *)opaque;
- offset -= s->base;
switch (offset) {
case 0x00: /* Control */
- s->cpu_enabled = (value & 1);
+ s->cpu_enabled[cpu] = (value & 1);
DPRINTF("CPU %sabled\n", s->cpu_enabled ? "En" : "Dis");
break;
case 0x04: /* Priority mask */
- s->priority_mask = (value & 0x3ff);
+ s->priority_mask[cpu] = (value & 0xff);
break;
case 0x08: /* Binary Point */
/* ??? Not implemented. */
break;
case 0x10: /* End Of Interrupt */
- return gic_complete_irq(s, value & 0x3ff);
+ return gic_complete_irq(s, cpu, value & 0x3ff);
default:
- cpu_abort (cpu_single_env, "gic_cpu_write: Bad offset %x\n", offset);
+ cpu_abort(cpu_single_env, "gic_cpu_write: Bad offset %x\n",
+ (int)offset);
return;
}
gic_update(s);
}
-
-static CPUReadMemoryFunc *gic_cpu_readfn[] = {
- gic_cpu_read,
- gic_cpu_read,
- gic_cpu_read
-};
-
-static CPUWriteMemoryFunc *gic_cpu_writefn[] = {
- gic_cpu_write,
- gic_cpu_write,
- gic_cpu_write
-};
+#endif
static void gic_reset(gic_state *s)
{
int i;
memset(s->irq_state, 0, GIC_NIRQ * sizeof(gic_irq_state));
- s->priority_mask = 0xf0;
- s->current_pending = 1023;
- s->running_irq = 1023;
- s->running_priority = 0x100;
+ for (i = 0 ; i < NCPU; i++) {
+ s->priority_mask[i] = 0xf0;
+ s->current_pending[i] = 1023;
+ s->running_irq[i] = 1023;
+ s->running_priority[i] = 0x100;
+#ifdef NVIC
+ /* The NVIC doesn't have per-cpu interfaces, so enable by default. */
+ s->cpu_enabled[i] = 1;
+#else
+ s->cpu_enabled[i] = 0;
+#endif
+ }
for (i = 0; i < 15; i++) {
GIC_SET_ENABLED(i);
GIC_SET_TRIGGER(i);
}
+#ifdef NVIC
+ /* The NVIC is always enabled. */
+ s->enabled = 1;
+#else
s->enabled = 0;
- s->cpu_enabled = 0;
+#endif
}
-qemu_irq *arm_gic_init(uint32_t base, qemu_irq parent_irq)
+static gic_state *gic_init(uint32_t base, qemu_irq *parent_irq)
{
gic_state *s;
- qemu_irq *qi;
int iomemtype;
+ int i;
s = (gic_state *)qemu_mallocz(sizeof(gic_state));
if (!s)
return NULL;
- qi = qemu_allocate_irqs(gic_set_irq, s, GIC_NIRQ);
- s->parent_irq = parent_irq;
- if (base != 0xffffffff) {
- iomemtype = cpu_register_io_memory(0, gic_cpu_readfn,
- gic_cpu_writefn, s);
- cpu_register_physical_memory(base, 0x00001000, iomemtype);
- iomemtype = cpu_register_io_memory(0, gic_dist_readfn,
- gic_dist_writefn, s);
- cpu_register_physical_memory(base + 0x1000, 0x00001000, iomemtype);
- s->base = base;
- } else {
- s->base = 0;
+ s->in = qemu_allocate_irqs(gic_set_irq, s, GIC_NIRQ);
+ for (i = 0; i < NCPU; i++) {
+ s->parent_irq[i] = parent_irq[i];
}
+ iomemtype = cpu_register_io_memory(0, gic_dist_readfn,
+ gic_dist_writefn, s);
+ cpu_register_physical_memory(base + GIC_DIST_OFFSET, 0x00001000,
+ iomemtype);
+ s->base = base;
gic_reset(s);
- return qi;
+ return s;
}