mirror of https://github.com/xemu-project/xemu.git
Merge remote-tracking branch 'pmaydell/arm-devs.for-upstream' into staging
* pmaydell/arm-devs.for-upstream: arm: make the number of GIC interrupts configurable hw/lan9118: Add save/load support arm: Remove incorrect comment in arm_timer vexpress, realview: Add (dummy) L2 cache controller
This commit is contained in:
commit
5414b32542
|
@ -11,9 +11,8 @@
|
||||||
#include "sysbus.h"
|
#include "sysbus.h"
|
||||||
|
|
||||||
/* Configuration for arm_gic.c:
|
/* Configuration for arm_gic.c:
|
||||||
* number of external IRQ lines, max number of CPUs, how to ID current CPU
|
* max number of CPUs, how to ID current CPU
|
||||||
*/
|
*/
|
||||||
#define GIC_NIRQ 96
|
|
||||||
#define NCPU 4
|
#define NCPU 4
|
||||||
|
|
||||||
static inline int
|
static inline int
|
||||||
|
@ -37,6 +36,7 @@ typedef struct a9mp_priv_state {
|
||||||
MemoryRegion ptimer_iomem;
|
MemoryRegion ptimer_iomem;
|
||||||
MemoryRegion container;
|
MemoryRegion container;
|
||||||
DeviceState *mptimer;
|
DeviceState *mptimer;
|
||||||
|
uint32_t num_irq;
|
||||||
} a9mp_priv_state;
|
} a9mp_priv_state;
|
||||||
|
|
||||||
static uint64_t a9_scu_read(void *opaque, target_phys_addr_t offset,
|
static uint64_t a9_scu_read(void *opaque, target_phys_addr_t offset,
|
||||||
|
@ -153,7 +153,7 @@ static int a9mp_priv_init(SysBusDevice *dev)
|
||||||
hw_error("a9mp_priv_init: num-cpu may not be more than %d\n", NCPU);
|
hw_error("a9mp_priv_init: num-cpu may not be more than %d\n", NCPU);
|
||||||
}
|
}
|
||||||
|
|
||||||
gic_init(&s->gic, s->num_cpu);
|
gic_init(&s->gic, s->num_cpu, s->num_irq);
|
||||||
|
|
||||||
s->mptimer = qdev_create(NULL, "arm_mptimer");
|
s->mptimer = qdev_create(NULL, "arm_mptimer");
|
||||||
qdev_prop_set_uint32(s->mptimer, "num-cpu", s->num_cpu);
|
qdev_prop_set_uint32(s->mptimer, "num-cpu", s->num_cpu);
|
||||||
|
@ -216,6 +216,13 @@ static SysBusDeviceInfo a9mp_priv_info = {
|
||||||
.qdev.reset = a9mp_priv_reset,
|
.qdev.reset = a9mp_priv_reset,
|
||||||
.qdev.props = (Property[]) {
|
.qdev.props = (Property[]) {
|
||||||
DEFINE_PROP_UINT32("num-cpu", a9mp_priv_state, num_cpu, 1),
|
DEFINE_PROP_UINT32("num-cpu", a9mp_priv_state, num_cpu, 1),
|
||||||
|
/* The Cortex-A9MP may have anything from 0 to 224 external interrupt
|
||||||
|
* IRQ lines (with another 32 internal). We default to 64+32, which
|
||||||
|
* is the number provided by the Cortex-A9MP test chip in the
|
||||||
|
* Realview PBX-A9 and Versatile Express A9 development boards.
|
||||||
|
* Other boards may differ and should set this property appropriately.
|
||||||
|
*/
|
||||||
|
DEFINE_PROP_UINT32("num-irq", a9mp_priv_state, num_irq, 96),
|
||||||
DEFINE_PROP_END_OF_LIST(),
|
DEFINE_PROP_END_OF_LIST(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,11 +10,6 @@
|
||||||
#include "sysbus.h"
|
#include "sysbus.h"
|
||||||
#include "qemu-timer.h"
|
#include "qemu-timer.h"
|
||||||
|
|
||||||
/* ??? The MPCore TRM says the on-chip controller has 224 external IRQ lines
|
|
||||||
(+ 32 internal). However my test chip only exposes/reports 32.
|
|
||||||
More importantly Linux falls over if more than 32 are present! */
|
|
||||||
#define GIC_NIRQ 64
|
|
||||||
|
|
||||||
#define NCPU 4
|
#define NCPU 4
|
||||||
|
|
||||||
static inline int
|
static inline int
|
||||||
|
@ -37,6 +32,7 @@ typedef struct mpcore_priv_state {
|
||||||
MemoryRegion iomem;
|
MemoryRegion iomem;
|
||||||
MemoryRegion container;
|
MemoryRegion container;
|
||||||
DeviceState *mptimer;
|
DeviceState *mptimer;
|
||||||
|
uint32_t num_irq;
|
||||||
} mpcore_priv_state;
|
} mpcore_priv_state;
|
||||||
|
|
||||||
/* Per-CPU private memory mapped IO. */
|
/* Per-CPU private memory mapped IO. */
|
||||||
|
@ -132,7 +128,7 @@ static int mpcore_priv_init(SysBusDevice *dev)
|
||||||
{
|
{
|
||||||
mpcore_priv_state *s = FROM_SYSBUSGIC(mpcore_priv_state, dev);
|
mpcore_priv_state *s = FROM_SYSBUSGIC(mpcore_priv_state, dev);
|
||||||
|
|
||||||
gic_init(&s->gic, s->num_cpu);
|
gic_init(&s->gic, s->num_cpu, s->num_irq);
|
||||||
s->mptimer = qdev_create(NULL, "arm_mptimer");
|
s->mptimer = qdev_create(NULL, "arm_mptimer");
|
||||||
qdev_prop_set_uint32(s->mptimer, "num-cpu", s->num_cpu);
|
qdev_prop_set_uint32(s->mptimer, "num-cpu", s->num_cpu);
|
||||||
qdev_init_nofail(s->mptimer);
|
qdev_init_nofail(s->mptimer);
|
||||||
|
@ -221,6 +217,15 @@ static SysBusDeviceInfo mpcore_priv_info = {
|
||||||
.qdev.size = sizeof(mpcore_priv_state),
|
.qdev.size = sizeof(mpcore_priv_state),
|
||||||
.qdev.props = (Property[]) {
|
.qdev.props = (Property[]) {
|
||||||
DEFINE_PROP_UINT32("num-cpu", mpcore_priv_state, num_cpu, 1),
|
DEFINE_PROP_UINT32("num-cpu", mpcore_priv_state, num_cpu, 1),
|
||||||
|
/* The ARM11 MPCORE TRM says the on-chip controller may have
|
||||||
|
* anything from 0 to 224 external interrupt IRQ lines (with another
|
||||||
|
* 32 internal). We default to 32+32, which is the number provided by
|
||||||
|
* the ARM11 MPCore test chip in the Realview Versatile Express
|
||||||
|
* coretile. Other boards may differ and should set this property
|
||||||
|
* appropriately. Some Linux kernels may not boot if the hardware
|
||||||
|
* has more IRQ lines than the kernel expects.
|
||||||
|
*/
|
||||||
|
DEFINE_PROP_UINT32("num-irq", mpcore_priv_state, num_irq, 64),
|
||||||
DEFINE_PROP_END_OF_LIST(),
|
DEFINE_PROP_END_OF_LIST(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
68
hw/arm_gic.c
68
hw/arm_gic.c
|
@ -11,6 +11,8 @@
|
||||||
controller, MPCore distributed interrupt controller and ARMv7-M
|
controller, MPCore distributed interrupt controller and ARMv7-M
|
||||||
Nested Vectored Interrupt Controller. */
|
Nested Vectored Interrupt Controller. */
|
||||||
|
|
||||||
|
/* Maximum number of possible interrupts, determined by the GIC architecture */
|
||||||
|
#define GIC_MAXIRQ 1020
|
||||||
//#define DEBUG_GIC
|
//#define DEBUG_GIC
|
||||||
|
|
||||||
#ifdef DEBUG_GIC
|
#ifdef DEBUG_GIC
|
||||||
|
@ -86,13 +88,13 @@ typedef struct gic_state
|
||||||
int enabled;
|
int enabled;
|
||||||
int cpu_enabled[NCPU];
|
int cpu_enabled[NCPU];
|
||||||
|
|
||||||
gic_irq_state irq_state[GIC_NIRQ];
|
gic_irq_state irq_state[GIC_MAXIRQ];
|
||||||
#ifndef NVIC
|
#ifndef NVIC
|
||||||
int irq_target[GIC_NIRQ];
|
int irq_target[GIC_MAXIRQ];
|
||||||
#endif
|
#endif
|
||||||
int priority1[32][NCPU];
|
int priority1[32][NCPU];
|
||||||
int priority2[GIC_NIRQ - 32];
|
int priority2[GIC_MAXIRQ - 32];
|
||||||
int last_active[GIC_NIRQ][NCPU];
|
int last_active[GIC_MAXIRQ][NCPU];
|
||||||
|
|
||||||
int priority_mask[NCPU];
|
int priority_mask[NCPU];
|
||||||
int running_irq[NCPU];
|
int running_irq[NCPU];
|
||||||
|
@ -111,6 +113,7 @@ typedef struct gic_state
|
||||||
struct gic_state *backref[NCPU];
|
struct gic_state *backref[NCPU];
|
||||||
MemoryRegion cpuiomem[NCPU+1]; /* CPU interfaces */
|
MemoryRegion cpuiomem[NCPU+1]; /* CPU interfaces */
|
||||||
#endif
|
#endif
|
||||||
|
uint32_t num_irq;
|
||||||
} gic_state;
|
} gic_state;
|
||||||
|
|
||||||
/* TODO: Many places that call this routine could be optimized. */
|
/* TODO: Many places that call this routine could be optimized. */
|
||||||
|
@ -133,7 +136,7 @@ static void gic_update(gic_state *s)
|
||||||
}
|
}
|
||||||
best_prio = 0x100;
|
best_prio = 0x100;
|
||||||
best_irq = 1023;
|
best_irq = 1023;
|
||||||
for (irq = 0; irq < GIC_NIRQ; irq++) {
|
for (irq = 0; irq < s->num_irq; irq++) {
|
||||||
if (GIC_TEST_ENABLED(irq, cm) && GIC_TEST_PENDING(irq, cm)) {
|
if (GIC_TEST_ENABLED(irq, cm) && GIC_TEST_PENDING(irq, cm)) {
|
||||||
if (GIC_GET_PRIORITY(irq, cpu) < best_prio) {
|
if (GIC_GET_PRIORITY(irq, cpu) < best_prio) {
|
||||||
best_prio = GIC_GET_PRIORITY(irq, cpu);
|
best_prio = GIC_GET_PRIORITY(irq, cpu);
|
||||||
|
@ -222,7 +225,7 @@ static void gic_complete_irq(gic_state * s, int cpu, int irq)
|
||||||
int update = 0;
|
int update = 0;
|
||||||
int cm = 1 << cpu;
|
int cm = 1 << cpu;
|
||||||
DPRINTF("EOI %d\n", irq);
|
DPRINTF("EOI %d\n", irq);
|
||||||
if (irq >= GIC_NIRQ) {
|
if (irq >= s->num_irq) {
|
||||||
/* This handles two cases:
|
/* This handles two cases:
|
||||||
* 1. If software writes the ID of a spurious interrupt [ie 1023]
|
* 1. If software writes the ID of a spurious interrupt [ie 1023]
|
||||||
* to the GICC_EOIR, the GIC ignores that write.
|
* to the GICC_EOIR, the GIC ignores that write.
|
||||||
|
@ -279,7 +282,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
|
||||||
if (offset == 0)
|
if (offset == 0)
|
||||||
return s->enabled;
|
return s->enabled;
|
||||||
if (offset == 4)
|
if (offset == 4)
|
||||||
return ((GIC_NIRQ / 32) - 1) | ((NUM_CPU(s) - 1) << 5);
|
return ((s->num_irq / 32) - 1) | ((NUM_CPU(s) - 1) << 5);
|
||||||
if (offset < 0x08)
|
if (offset < 0x08)
|
||||||
return 0;
|
return 0;
|
||||||
if (offset >= 0x80) {
|
if (offset >= 0x80) {
|
||||||
|
@ -295,7 +298,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
|
||||||
else
|
else
|
||||||
irq = (offset - 0x180) * 8;
|
irq = (offset - 0x180) * 8;
|
||||||
irq += GIC_BASE_IRQ;
|
irq += GIC_BASE_IRQ;
|
||||||
if (irq >= GIC_NIRQ)
|
if (irq >= s->num_irq)
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
res = 0;
|
res = 0;
|
||||||
for (i = 0; i < 8; i++) {
|
for (i = 0; i < 8; i++) {
|
||||||
|
@ -310,7 +313,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
|
||||||
else
|
else
|
||||||
irq = (offset - 0x280) * 8;
|
irq = (offset - 0x280) * 8;
|
||||||
irq += GIC_BASE_IRQ;
|
irq += GIC_BASE_IRQ;
|
||||||
if (irq >= GIC_NIRQ)
|
if (irq >= s->num_irq)
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
res = 0;
|
res = 0;
|
||||||
mask = (irq < 32) ? cm : ALL_CPU_MASK;
|
mask = (irq < 32) ? cm : ALL_CPU_MASK;
|
||||||
|
@ -322,7 +325,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
|
||||||
} else if (offset < 0x400) {
|
} else if (offset < 0x400) {
|
||||||
/* Interrupt Active. */
|
/* Interrupt Active. */
|
||||||
irq = (offset - 0x300) * 8 + GIC_BASE_IRQ;
|
irq = (offset - 0x300) * 8 + GIC_BASE_IRQ;
|
||||||
if (irq >= GIC_NIRQ)
|
if (irq >= s->num_irq)
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
res = 0;
|
res = 0;
|
||||||
mask = (irq < 32) ? cm : ALL_CPU_MASK;
|
mask = (irq < 32) ? cm : ALL_CPU_MASK;
|
||||||
|
@ -334,14 +337,14 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
|
||||||
} else if (offset < 0x800) {
|
} else if (offset < 0x800) {
|
||||||
/* Interrupt Priority. */
|
/* Interrupt Priority. */
|
||||||
irq = (offset - 0x400) + GIC_BASE_IRQ;
|
irq = (offset - 0x400) + GIC_BASE_IRQ;
|
||||||
if (irq >= GIC_NIRQ)
|
if (irq >= s->num_irq)
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
res = GIC_GET_PRIORITY(irq, cpu);
|
res = GIC_GET_PRIORITY(irq, cpu);
|
||||||
#ifndef NVIC
|
#ifndef NVIC
|
||||||
} else if (offset < 0xc00) {
|
} else if (offset < 0xc00) {
|
||||||
/* Interrupt CPU Target. */
|
/* Interrupt CPU Target. */
|
||||||
irq = (offset - 0x800) + GIC_BASE_IRQ;
|
irq = (offset - 0x800) + GIC_BASE_IRQ;
|
||||||
if (irq >= GIC_NIRQ)
|
if (irq >= s->num_irq)
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
if (irq >= 29 && irq <= 31) {
|
if (irq >= 29 && irq <= 31) {
|
||||||
res = cm;
|
res = cm;
|
||||||
|
@ -351,7 +354,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
|
||||||
} else if (offset < 0xf00) {
|
} else if (offset < 0xf00) {
|
||||||
/* Interrupt Configuration. */
|
/* Interrupt Configuration. */
|
||||||
irq = (offset - 0xc00) * 2 + GIC_BASE_IRQ;
|
irq = (offset - 0xc00) * 2 + GIC_BASE_IRQ;
|
||||||
if (irq >= GIC_NIRQ)
|
if (irq >= s->num_irq)
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
res = 0;
|
res = 0;
|
||||||
for (i = 0; i < 4; i++) {
|
for (i = 0; i < 4; i++) {
|
||||||
|
@ -426,7 +429,7 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
|
||||||
} else if (offset < 0x180) {
|
} else if (offset < 0x180) {
|
||||||
/* Interrupt Set Enable. */
|
/* Interrupt Set Enable. */
|
||||||
irq = (offset - 0x100) * 8 + GIC_BASE_IRQ;
|
irq = (offset - 0x100) * 8 + GIC_BASE_IRQ;
|
||||||
if (irq >= GIC_NIRQ)
|
if (irq >= s->num_irq)
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
if (irq < 16)
|
if (irq < 16)
|
||||||
value = 0xff;
|
value = 0xff;
|
||||||
|
@ -451,7 +454,7 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
|
||||||
} else if (offset < 0x200) {
|
} else if (offset < 0x200) {
|
||||||
/* Interrupt Clear Enable. */
|
/* Interrupt Clear Enable. */
|
||||||
irq = (offset - 0x180) * 8 + GIC_BASE_IRQ;
|
irq = (offset - 0x180) * 8 + GIC_BASE_IRQ;
|
||||||
if (irq >= GIC_NIRQ)
|
if (irq >= s->num_irq)
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
if (irq < 16)
|
if (irq < 16)
|
||||||
value = 0;
|
value = 0;
|
||||||
|
@ -468,7 +471,7 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
|
||||||
} else if (offset < 0x280) {
|
} else if (offset < 0x280) {
|
||||||
/* Interrupt Set Pending. */
|
/* Interrupt Set Pending. */
|
||||||
irq = (offset - 0x200) * 8 + GIC_BASE_IRQ;
|
irq = (offset - 0x200) * 8 + GIC_BASE_IRQ;
|
||||||
if (irq >= GIC_NIRQ)
|
if (irq >= s->num_irq)
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
if (irq < 16)
|
if (irq < 16)
|
||||||
irq = 0;
|
irq = 0;
|
||||||
|
@ -481,7 +484,7 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
|
||||||
} else if (offset < 0x300) {
|
} else if (offset < 0x300) {
|
||||||
/* Interrupt Clear Pending. */
|
/* Interrupt Clear Pending. */
|
||||||
irq = (offset - 0x280) * 8 + GIC_BASE_IRQ;
|
irq = (offset - 0x280) * 8 + GIC_BASE_IRQ;
|
||||||
if (irq >= GIC_NIRQ)
|
if (irq >= s->num_irq)
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
for (i = 0; i < 8; i++) {
|
for (i = 0; i < 8; i++) {
|
||||||
/* ??? This currently clears the pending bit for all CPUs, even
|
/* ??? This currently clears the pending bit for all CPUs, even
|
||||||
|
@ -497,7 +500,7 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
|
||||||
} else if (offset < 0x800) {
|
} else if (offset < 0x800) {
|
||||||
/* Interrupt Priority. */
|
/* Interrupt Priority. */
|
||||||
irq = (offset - 0x400) + GIC_BASE_IRQ;
|
irq = (offset - 0x400) + GIC_BASE_IRQ;
|
||||||
if (irq >= GIC_NIRQ)
|
if (irq >= s->num_irq)
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
if (irq < 32) {
|
if (irq < 32) {
|
||||||
s->priority1[irq][cpu] = value;
|
s->priority1[irq][cpu] = value;
|
||||||
|
@ -508,7 +511,7 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
|
||||||
} else if (offset < 0xc00) {
|
} else if (offset < 0xc00) {
|
||||||
/* Interrupt CPU Target. */
|
/* Interrupt CPU Target. */
|
||||||
irq = (offset - 0x800) + GIC_BASE_IRQ;
|
irq = (offset - 0x800) + GIC_BASE_IRQ;
|
||||||
if (irq >= GIC_NIRQ)
|
if (irq >= s->num_irq)
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
if (irq < 29)
|
if (irq < 29)
|
||||||
value = 0;
|
value = 0;
|
||||||
|
@ -518,7 +521,7 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
|
||||||
} else if (offset < 0xf00) {
|
} else if (offset < 0xf00) {
|
||||||
/* Interrupt Configuration. */
|
/* Interrupt Configuration. */
|
||||||
irq = (offset - 0xc00) * 4 + GIC_BASE_IRQ;
|
irq = (offset - 0xc00) * 4 + GIC_BASE_IRQ;
|
||||||
if (irq >= GIC_NIRQ)
|
if (irq >= s->num_irq)
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
if (irq < 32)
|
if (irq < 32)
|
||||||
value |= 0xaa;
|
value |= 0xaa;
|
||||||
|
@ -699,7 +702,7 @@ static const MemoryRegionOps gic_cpu_ops = {
|
||||||
static void gic_reset(gic_state *s)
|
static void gic_reset(gic_state *s)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
memset(s->irq_state, 0, GIC_NIRQ * sizeof(gic_irq_state));
|
memset(s->irq_state, 0, GIC_MAXIRQ * sizeof(gic_irq_state));
|
||||||
for (i = 0 ; i < NUM_CPU(s); i++) {
|
for (i = 0 ; i < NUM_CPU(s); i++) {
|
||||||
s->priority_mask[i] = 0xf0;
|
s->priority_mask[i] = 0xf0;
|
||||||
s->current_pending[i] = 1023;
|
s->current_pending[i] = 1023;
|
||||||
|
@ -735,17 +738,17 @@ static void gic_save(QEMUFile *f, void *opaque)
|
||||||
qemu_put_be32(f, s->cpu_enabled[i]);
|
qemu_put_be32(f, s->cpu_enabled[i]);
|
||||||
for (j = 0; j < 32; j++)
|
for (j = 0; j < 32; j++)
|
||||||
qemu_put_be32(f, s->priority1[j][i]);
|
qemu_put_be32(f, s->priority1[j][i]);
|
||||||
for (j = 0; j < GIC_NIRQ; j++)
|
for (j = 0; j < s->num_irq; j++)
|
||||||
qemu_put_be32(f, s->last_active[j][i]);
|
qemu_put_be32(f, s->last_active[j][i]);
|
||||||
qemu_put_be32(f, s->priority_mask[i]);
|
qemu_put_be32(f, s->priority_mask[i]);
|
||||||
qemu_put_be32(f, s->running_irq[i]);
|
qemu_put_be32(f, s->running_irq[i]);
|
||||||
qemu_put_be32(f, s->running_priority[i]);
|
qemu_put_be32(f, s->running_priority[i]);
|
||||||
qemu_put_be32(f, s->current_pending[i]);
|
qemu_put_be32(f, s->current_pending[i]);
|
||||||
}
|
}
|
||||||
for (i = 0; i < GIC_NIRQ - 32; i++) {
|
for (i = 0; i < s->num_irq - 32; i++) {
|
||||||
qemu_put_be32(f, s->priority2[i]);
|
qemu_put_be32(f, s->priority2[i]);
|
||||||
}
|
}
|
||||||
for (i = 0; i < GIC_NIRQ; i++) {
|
for (i = 0; i < s->num_irq; i++) {
|
||||||
#ifndef NVIC
|
#ifndef NVIC
|
||||||
qemu_put_be32(f, s->irq_target[i]);
|
qemu_put_be32(f, s->irq_target[i]);
|
||||||
#endif
|
#endif
|
||||||
|
@ -772,17 +775,17 @@ static int gic_load(QEMUFile *f, void *opaque, int version_id)
|
||||||
s->cpu_enabled[i] = qemu_get_be32(f);
|
s->cpu_enabled[i] = qemu_get_be32(f);
|
||||||
for (j = 0; j < 32; j++)
|
for (j = 0; j < 32; j++)
|
||||||
s->priority1[j][i] = qemu_get_be32(f);
|
s->priority1[j][i] = qemu_get_be32(f);
|
||||||
for (j = 0; j < GIC_NIRQ; j++)
|
for (j = 0; j < s->num_irq; j++)
|
||||||
s->last_active[j][i] = qemu_get_be32(f);
|
s->last_active[j][i] = qemu_get_be32(f);
|
||||||
s->priority_mask[i] = qemu_get_be32(f);
|
s->priority_mask[i] = qemu_get_be32(f);
|
||||||
s->running_irq[i] = qemu_get_be32(f);
|
s->running_irq[i] = qemu_get_be32(f);
|
||||||
s->running_priority[i] = qemu_get_be32(f);
|
s->running_priority[i] = qemu_get_be32(f);
|
||||||
s->current_pending[i] = qemu_get_be32(f);
|
s->current_pending[i] = qemu_get_be32(f);
|
||||||
}
|
}
|
||||||
for (i = 0; i < GIC_NIRQ - 32; i++) {
|
for (i = 0; i < s->num_irq - 32; i++) {
|
||||||
s->priority2[i] = qemu_get_be32(f);
|
s->priority2[i] = qemu_get_be32(f);
|
||||||
}
|
}
|
||||||
for (i = 0; i < GIC_NIRQ; i++) {
|
for (i = 0; i < s->num_irq; i++) {
|
||||||
#ifndef NVIC
|
#ifndef NVIC
|
||||||
s->irq_target[i] = qemu_get_be32(f);
|
s->irq_target[i] = qemu_get_be32(f);
|
||||||
#endif
|
#endif
|
||||||
|
@ -798,9 +801,9 @@ static int gic_load(QEMUFile *f, void *opaque, int version_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if NCPU > 1
|
#if NCPU > 1
|
||||||
static void gic_init(gic_state *s, int num_cpu)
|
static void gic_init(gic_state *s, int num_cpu, int num_irq)
|
||||||
#else
|
#else
|
||||||
static void gic_init(gic_state *s)
|
static void gic_init(gic_state *s, int num_irq)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
@ -808,7 +811,12 @@ static void gic_init(gic_state *s)
|
||||||
#if NCPU > 1
|
#if NCPU > 1
|
||||||
s->num_cpu = num_cpu;
|
s->num_cpu = num_cpu;
|
||||||
#endif
|
#endif
|
||||||
qdev_init_gpio_in(&s->busdev.qdev, gic_set_irq, GIC_NIRQ - 32);
|
s->num_irq = num_irq + GIC_BASE_IRQ;
|
||||||
|
if (s->num_irq > GIC_MAXIRQ) {
|
||||||
|
hw_error("requested %u interrupt lines exceeds GIC maximum %d\n",
|
||||||
|
num_irq, GIC_MAXIRQ);
|
||||||
|
}
|
||||||
|
qdev_init_gpio_in(&s->busdev.qdev, gic_set_irq, s->num_irq - 32);
|
||||||
for (i = 0; i < NUM_CPU(s); i++) {
|
for (i = 0; i < NUM_CPU(s); i++) {
|
||||||
sysbus_init_irq(&s->busdev, &s->parent_irq[i]);
|
sysbus_init_irq(&s->busdev, &s->parent_irq[i]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -273,11 +273,8 @@ static int sp804_init(SysBusDevice *dev)
|
||||||
|
|
||||||
qi = qemu_allocate_irqs(sp804_set_irq, s, 2);
|
qi = qemu_allocate_irqs(sp804_set_irq, s, 2);
|
||||||
sysbus_init_irq(dev, &s->irq);
|
sysbus_init_irq(dev, &s->irq);
|
||||||
/* The timers are configurable between 32kHz and 1MHz
|
|
||||||
* defaulting to 1MHz but overrideable as individual properties */
|
|
||||||
s->timer[0] = arm_timer_init(s->freq0);
|
s->timer[0] = arm_timer_init(s->freq0);
|
||||||
s->timer[1] = arm_timer_init(s->freq1);
|
s->timer[1] = arm_timer_init(s->freq1);
|
||||||
|
|
||||||
s->timer[0]->irq = qi[0];
|
s->timer[0]->irq = qi[0];
|
||||||
s->timer[1]->irq = qi[1];
|
s->timer[1]->irq = qi[1];
|
||||||
memory_region_init_io(&s->iomem, &sp804_ops, s, "sp804", 0x1000);
|
memory_region_init_io(&s->iomem, &sp804_ops, s, "sp804", 0x1000);
|
||||||
|
|
|
@ -15,9 +15,6 @@
|
||||||
#include "arm-misc.h"
|
#include "arm-misc.h"
|
||||||
#include "exec-memory.h"
|
#include "exec-memory.h"
|
||||||
|
|
||||||
/* 32 internal lines (16 used for system exceptions) plus 64 external
|
|
||||||
interrupt lines. */
|
|
||||||
#define GIC_NIRQ 96
|
|
||||||
#define NCPU 1
|
#define NCPU 1
|
||||||
#define NVIC 1
|
#define NVIC 1
|
||||||
|
|
||||||
|
@ -41,6 +38,7 @@ typedef struct {
|
||||||
int64_t tick;
|
int64_t tick;
|
||||||
QEMUTimer *timer;
|
QEMUTimer *timer;
|
||||||
} systick;
|
} systick;
|
||||||
|
uint32_t num_irq;
|
||||||
} nvic_state;
|
} nvic_state;
|
||||||
|
|
||||||
/* qemu timers run at 1GHz. We want something closer to 1MHz. */
|
/* qemu timers run at 1GHz. We want something closer to 1MHz. */
|
||||||
|
@ -125,7 +123,7 @@ static uint32_t nvic_readl(void *opaque, uint32_t offset)
|
||||||
|
|
||||||
switch (offset) {
|
switch (offset) {
|
||||||
case 4: /* Interrupt Control Type. */
|
case 4: /* Interrupt Control Type. */
|
||||||
return (GIC_NIRQ / 32) - 1;
|
return (s->num_irq / 32) - 1;
|
||||||
case 0x10: /* SysTick Control and Status. */
|
case 0x10: /* SysTick Control and Status. */
|
||||||
val = s->systick.control;
|
val = s->systick.control;
|
||||||
s->systick.control &= ~SYSTICK_COUNTFLAG;
|
s->systick.control &= ~SYSTICK_COUNTFLAG;
|
||||||
|
@ -169,7 +167,7 @@ static uint32_t nvic_readl(void *opaque, uint32_t offset)
|
||||||
if (s->gic.current_pending[0] != 1023)
|
if (s->gic.current_pending[0] != 1023)
|
||||||
val |= (s->gic.current_pending[0] << 12);
|
val |= (s->gic.current_pending[0] << 12);
|
||||||
/* ISRPENDING */
|
/* ISRPENDING */
|
||||||
for (irq = 32; irq < GIC_NIRQ; irq++) {
|
for (irq = 32; irq < s->num_irq; irq++) {
|
||||||
if (s->gic.irq_state[irq].pending) {
|
if (s->gic.irq_state[irq].pending) {
|
||||||
val |= (1 << 22);
|
val |= (1 << 22);
|
||||||
break;
|
break;
|
||||||
|
@ -384,16 +382,33 @@ static int armv7m_nvic_init(SysBusDevice *dev)
|
||||||
{
|
{
|
||||||
nvic_state *s= FROM_SYSBUSGIC(nvic_state, dev);
|
nvic_state *s= FROM_SYSBUSGIC(nvic_state, dev);
|
||||||
|
|
||||||
gic_init(&s->gic);
|
/* note that for the M profile gic_init() takes the number of external
|
||||||
|
* interrupt lines only.
|
||||||
|
*/
|
||||||
|
gic_init(&s->gic, s->num_irq);
|
||||||
memory_region_add_subregion(get_system_memory(), 0xe000e000, &s->gic.iomem);
|
memory_region_add_subregion(get_system_memory(), 0xe000e000, &s->gic.iomem);
|
||||||
s->systick.timer = qemu_new_timer_ns(vm_clock, systick_timer_tick, s);
|
s->systick.timer = qemu_new_timer_ns(vm_clock, systick_timer_tick, s);
|
||||||
vmstate_register(&dev->qdev, -1, &vmstate_nvic, s);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static SysBusDeviceInfo armv7m_nvic_priv_info = {
|
||||||
|
.init = armv7m_nvic_init,
|
||||||
|
.qdev.name = "armv7m_nvic",
|
||||||
|
.qdev.size = sizeof(nvic_state),
|
||||||
|
.qdev.vmsd = &vmstate_nvic,
|
||||||
|
.qdev.props = (Property[]) {
|
||||||
|
/* The ARM v7m may have anything from 0 to 496 external interrupt
|
||||||
|
* IRQ lines. We default to 64. Other boards may differ and should
|
||||||
|
* set this property appropriately.
|
||||||
|
*/
|
||||||
|
DEFINE_PROP_UINT32("num-irq", nvic_state, num_irq, 64),
|
||||||
|
DEFINE_PROP_END_OF_LIST(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static void armv7m_nvic_register_devices(void)
|
static void armv7m_nvic_register_devices(void)
|
||||||
{
|
{
|
||||||
sysbus_register_dev("armv7m_nvic", sizeof(nvic_state), armv7m_nvic_init);
|
sysbus_register_withprop(&armv7m_nvic_priv_info);
|
||||||
}
|
}
|
||||||
|
|
||||||
device_init(armv7m_nvic_register_devices)
|
device_init(armv7m_nvic_register_devices)
|
||||||
|
|
126
hw/lan9118.c
126
hw/lan9118.c
|
@ -140,17 +140,36 @@ enum tx_state {
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
enum tx_state state;
|
/* state is a tx_state but we can't put enums in VMStateDescriptions. */
|
||||||
|
uint32_t state;
|
||||||
uint32_t cmd_a;
|
uint32_t cmd_a;
|
||||||
uint32_t cmd_b;
|
uint32_t cmd_b;
|
||||||
int buffer_size;
|
int32_t buffer_size;
|
||||||
int offset;
|
int32_t offset;
|
||||||
int pad;
|
int32_t pad;
|
||||||
int fifo_used;
|
int32_t fifo_used;
|
||||||
int len;
|
int32_t len;
|
||||||
uint8_t data[2048];
|
uint8_t data[2048];
|
||||||
} LAN9118Packet;
|
} LAN9118Packet;
|
||||||
|
|
||||||
|
static const VMStateDescription vmstate_lan9118_packet = {
|
||||||
|
.name = "lan9118_packet",
|
||||||
|
.version_id = 1,
|
||||||
|
.minimum_version_id = 1,
|
||||||
|
.fields = (VMStateField[]) {
|
||||||
|
VMSTATE_UINT32(state, LAN9118Packet),
|
||||||
|
VMSTATE_UINT32(cmd_a, LAN9118Packet),
|
||||||
|
VMSTATE_UINT32(cmd_b, LAN9118Packet),
|
||||||
|
VMSTATE_INT32(buffer_size, LAN9118Packet),
|
||||||
|
VMSTATE_INT32(offset, LAN9118Packet),
|
||||||
|
VMSTATE_INT32(pad, LAN9118Packet),
|
||||||
|
VMSTATE_INT32(fifo_used, LAN9118Packet),
|
||||||
|
VMSTATE_INT32(len, LAN9118Packet),
|
||||||
|
VMSTATE_UINT8_ARRAY(data, LAN9118Packet, 2048),
|
||||||
|
VMSTATE_END_OF_LIST()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
SysBusDevice busdev;
|
SysBusDevice busdev;
|
||||||
NICState *nic;
|
NICState *nic;
|
||||||
|
@ -190,34 +209,95 @@ typedef struct {
|
||||||
uint32_t phy_int;
|
uint32_t phy_int;
|
||||||
uint32_t phy_int_mask;
|
uint32_t phy_int_mask;
|
||||||
|
|
||||||
int eeprom_writable;
|
int32_t eeprom_writable;
|
||||||
uint8_t eeprom[128];
|
uint8_t eeprom[128];
|
||||||
|
|
||||||
int tx_fifo_size;
|
int32_t tx_fifo_size;
|
||||||
LAN9118Packet *txp;
|
LAN9118Packet *txp;
|
||||||
LAN9118Packet tx_packet;
|
LAN9118Packet tx_packet;
|
||||||
|
|
||||||
int tx_status_fifo_used;
|
int32_t tx_status_fifo_used;
|
||||||
int tx_status_fifo_head;
|
int32_t tx_status_fifo_head;
|
||||||
uint32_t tx_status_fifo[512];
|
uint32_t tx_status_fifo[512];
|
||||||
|
|
||||||
int rx_status_fifo_size;
|
int32_t rx_status_fifo_size;
|
||||||
int rx_status_fifo_used;
|
int32_t rx_status_fifo_used;
|
||||||
int rx_status_fifo_head;
|
int32_t rx_status_fifo_head;
|
||||||
uint32_t rx_status_fifo[896];
|
uint32_t rx_status_fifo[896];
|
||||||
int rx_fifo_size;
|
int32_t rx_fifo_size;
|
||||||
int rx_fifo_used;
|
int32_t rx_fifo_used;
|
||||||
int rx_fifo_head;
|
int32_t rx_fifo_head;
|
||||||
uint32_t rx_fifo[3360];
|
uint32_t rx_fifo[3360];
|
||||||
int rx_packet_size_head;
|
int32_t rx_packet_size_head;
|
||||||
int rx_packet_size_tail;
|
int32_t rx_packet_size_tail;
|
||||||
int rx_packet_size[1024];
|
int32_t rx_packet_size[1024];
|
||||||
|
|
||||||
int rxp_offset;
|
int32_t rxp_offset;
|
||||||
int rxp_size;
|
int32_t rxp_size;
|
||||||
int rxp_pad;
|
int32_t rxp_pad;
|
||||||
} lan9118_state;
|
} lan9118_state;
|
||||||
|
|
||||||
|
static const VMStateDescription vmstate_lan9118 = {
|
||||||
|
.name = "lan9118",
|
||||||
|
.version_id = 1,
|
||||||
|
.minimum_version_id = 1,
|
||||||
|
.fields = (VMStateField[]) {
|
||||||
|
VMSTATE_PTIMER(timer, lan9118_state),
|
||||||
|
VMSTATE_UINT32(irq_cfg, lan9118_state),
|
||||||
|
VMSTATE_UINT32(int_sts, lan9118_state),
|
||||||
|
VMSTATE_UINT32(int_en, lan9118_state),
|
||||||
|
VMSTATE_UINT32(fifo_int, lan9118_state),
|
||||||
|
VMSTATE_UINT32(rx_cfg, lan9118_state),
|
||||||
|
VMSTATE_UINT32(tx_cfg, lan9118_state),
|
||||||
|
VMSTATE_UINT32(hw_cfg, lan9118_state),
|
||||||
|
VMSTATE_UINT32(pmt_ctrl, lan9118_state),
|
||||||
|
VMSTATE_UINT32(gpio_cfg, lan9118_state),
|
||||||
|
VMSTATE_UINT32(gpt_cfg, lan9118_state),
|
||||||
|
VMSTATE_UINT32(word_swap, lan9118_state),
|
||||||
|
VMSTATE_UINT32(free_timer_start, lan9118_state),
|
||||||
|
VMSTATE_UINT32(mac_cmd, lan9118_state),
|
||||||
|
VMSTATE_UINT32(mac_data, lan9118_state),
|
||||||
|
VMSTATE_UINT32(afc_cfg, lan9118_state),
|
||||||
|
VMSTATE_UINT32(e2p_cmd, lan9118_state),
|
||||||
|
VMSTATE_UINT32(e2p_data, lan9118_state),
|
||||||
|
VMSTATE_UINT32(mac_cr, lan9118_state),
|
||||||
|
VMSTATE_UINT32(mac_hashh, lan9118_state),
|
||||||
|
VMSTATE_UINT32(mac_hashl, lan9118_state),
|
||||||
|
VMSTATE_UINT32(mac_mii_acc, lan9118_state),
|
||||||
|
VMSTATE_UINT32(mac_mii_data, lan9118_state),
|
||||||
|
VMSTATE_UINT32(mac_flow, lan9118_state),
|
||||||
|
VMSTATE_UINT32(phy_status, lan9118_state),
|
||||||
|
VMSTATE_UINT32(phy_control, lan9118_state),
|
||||||
|
VMSTATE_UINT32(phy_advertise, lan9118_state),
|
||||||
|
VMSTATE_UINT32(phy_int, lan9118_state),
|
||||||
|
VMSTATE_UINT32(phy_int_mask, lan9118_state),
|
||||||
|
VMSTATE_INT32(eeprom_writable, lan9118_state),
|
||||||
|
VMSTATE_UINT8_ARRAY(eeprom, lan9118_state, 128),
|
||||||
|
VMSTATE_INT32(tx_fifo_size, lan9118_state),
|
||||||
|
/* txp always points at tx_packet so need not be saved */
|
||||||
|
VMSTATE_STRUCT(tx_packet, lan9118_state, 0,
|
||||||
|
vmstate_lan9118_packet, LAN9118Packet),
|
||||||
|
VMSTATE_INT32(tx_status_fifo_used, lan9118_state),
|
||||||
|
VMSTATE_INT32(tx_status_fifo_head, lan9118_state),
|
||||||
|
VMSTATE_UINT32_ARRAY(tx_status_fifo, lan9118_state, 512),
|
||||||
|
VMSTATE_INT32(rx_status_fifo_size, lan9118_state),
|
||||||
|
VMSTATE_INT32(rx_status_fifo_used, lan9118_state),
|
||||||
|
VMSTATE_INT32(rx_status_fifo_head, lan9118_state),
|
||||||
|
VMSTATE_UINT32_ARRAY(rx_status_fifo, lan9118_state, 896),
|
||||||
|
VMSTATE_INT32(rx_fifo_size, lan9118_state),
|
||||||
|
VMSTATE_INT32(rx_fifo_used, lan9118_state),
|
||||||
|
VMSTATE_INT32(rx_fifo_head, lan9118_state),
|
||||||
|
VMSTATE_UINT32_ARRAY(rx_fifo, lan9118_state, 3360),
|
||||||
|
VMSTATE_INT32(rx_packet_size_head, lan9118_state),
|
||||||
|
VMSTATE_INT32(rx_packet_size_tail, lan9118_state),
|
||||||
|
VMSTATE_INT32_ARRAY(rx_packet_size, lan9118_state, 1024),
|
||||||
|
VMSTATE_INT32(rxp_offset, lan9118_state),
|
||||||
|
VMSTATE_INT32(rxp_size, lan9118_state),
|
||||||
|
VMSTATE_INT32(rxp_pad, lan9118_state),
|
||||||
|
VMSTATE_END_OF_LIST()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static void lan9118_update(lan9118_state *s)
|
static void lan9118_update(lan9118_state *s)
|
||||||
{
|
{
|
||||||
int level;
|
int level;
|
||||||
|
@ -1155,7 +1235,6 @@ static int lan9118_init1(SysBusDevice *dev)
|
||||||
ptimer_set_freq(s->timer, 10000);
|
ptimer_set_freq(s->timer, 10000);
|
||||||
ptimer_set_limit(s->timer, 0xffff, 1);
|
ptimer_set_limit(s->timer, 0xffff, 1);
|
||||||
|
|
||||||
/* ??? Save/restore. */
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1164,6 +1243,7 @@ static SysBusDeviceInfo lan9118_info = {
|
||||||
.qdev.name = "lan9118",
|
.qdev.name = "lan9118",
|
||||||
.qdev.size = sizeof(lan9118_state),
|
.qdev.size = sizeof(lan9118_state),
|
||||||
.qdev.reset = lan9118_reset,
|
.qdev.reset = lan9118_reset,
|
||||||
|
.qdev.vmsd = &vmstate_lan9118,
|
||||||
.qdev.props = (Property[]) {
|
.qdev.props = (Property[]) {
|
||||||
DEFINE_NIC_PROPERTIES(lan9118_state, conf),
|
DEFINE_NIC_PROPERTIES(lan9118_state, conf),
|
||||||
DEFINE_PROP_END_OF_LIST(),
|
DEFINE_PROP_END_OF_LIST(),
|
||||||
|
|
|
@ -227,6 +227,8 @@ static void realview_init(ram_addr_t ram_size,
|
||||||
for (n = 0; n < smp_cpus; n++) {
|
for (n = 0; n < smp_cpus; n++) {
|
||||||
sysbus_connect_irq(busdev, n, cpu_irq[n]);
|
sysbus_connect_irq(busdev, n, cpu_irq[n]);
|
||||||
}
|
}
|
||||||
|
sysbus_create_varargs("l2x0", realview_binfo.smp_priv_base + 0x2000,
|
||||||
|
NULL);
|
||||||
} else {
|
} else {
|
||||||
uint32_t gic_addr = is_pb ? 0x1e000000 : 0x10040000;
|
uint32_t gic_addr = is_pb ? 0x1e000000 : 0x10040000;
|
||||||
/* For now just create the nIRQ GIC, and ignore the others. */
|
/* For now just create the nIRQ GIC, and ignore the others. */
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
|
|
||||||
#include "sysbus.h"
|
#include "sysbus.h"
|
||||||
|
|
||||||
#define GIC_NIRQ 96
|
|
||||||
#define NCPU 1
|
#define NCPU 1
|
||||||
|
|
||||||
/* Only a single "CPU" interface is present. */
|
/* Only a single "CPU" interface is present. */
|
||||||
|
@ -37,7 +36,11 @@ static int realview_gic_init(SysBusDevice *dev)
|
||||||
{
|
{
|
||||||
RealViewGICState *s = FROM_SYSBUSGIC(RealViewGICState, dev);
|
RealViewGICState *s = FROM_SYSBUSGIC(RealViewGICState, dev);
|
||||||
|
|
||||||
gic_init(&s->gic);
|
/* The GICs on the RealView boards have a fixed nonconfigurable
|
||||||
|
* number of interrupt lines, so we don't need to expose this as
|
||||||
|
* a qdev property.
|
||||||
|
*/
|
||||||
|
gic_init(&s->gic, 96);
|
||||||
realview_gic_map_setup(s);
|
realview_gic_map_setup(s);
|
||||||
sysbus_init_mmio(dev, &s->container);
|
sysbus_init_mmio(dev, &s->container);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -182,6 +182,7 @@ static void vexpress_a9_init(ram_addr_t ram_size,
|
||||||
/* 0x100ec000 TrustZone Address Space Controller */
|
/* 0x100ec000 TrustZone Address Space Controller */
|
||||||
/* 0x10200000 CoreSight debug APB */
|
/* 0x10200000 CoreSight debug APB */
|
||||||
/* 0x1e00a000 PL310 L2 Cache Controller */
|
/* 0x1e00a000 PL310 L2 Cache Controller */
|
||||||
|
sysbus_create_varargs("l2x0", 0x1e00a000, NULL);
|
||||||
|
|
||||||
/* CS0: NOR0 flash : 0x40000000 .. 0x44000000 */
|
/* CS0: NOR0 flash : 0x40000000 .. 0x44000000 */
|
||||||
/* CS4: NOR1 flash : 0x44000000 .. 0x48000000 */
|
/* CS4: NOR1 flash : 0x44000000 .. 0x48000000 */
|
||||||
|
|
Loading…
Reference in New Issue