mirror of https://github.com/xemu-project/xemu.git
hw/armv7m_nvic: Use MemoryRegions for NVIC specific registers
Implement the NVIC specific register areas using a set of overlaid MemoryRegions in a container, rather than by having the arm_gic read/write functions use special purpose callbacks. Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
parent
b3387ede2f
commit
2a29ddee82
33
hw/arm_gic.c
33
hw/arm_gic.c
|
@ -37,17 +37,17 @@ do { printf("arm_gic: " fmt , ## __VA_ARGS__); } while (0)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef NVIC
|
#ifdef NVIC
|
||||||
static const uint8_t gic_id[] =
|
|
||||||
{ 0x00, 0xb0, 0x1b, 0x00, 0x0d, 0xe0, 0x05, 0xb1 };
|
|
||||||
/* The NVIC has 16 internal vectors. However these are not exposed
|
/* The NVIC has 16 internal vectors. However these are not exposed
|
||||||
through the normal GIC interface. */
|
through the normal GIC interface. */
|
||||||
#define GIC_BASE_IRQ 32
|
#define GIC_BASE_IRQ 32
|
||||||
#else
|
#else
|
||||||
static const uint8_t gic_id[] =
|
|
||||||
{ 0x90, 0x13, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
|
|
||||||
#define GIC_BASE_IRQ 0
|
#define GIC_BASE_IRQ 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static const uint8_t gic_id[] = {
|
||||||
|
0x90, 0x13, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1
|
||||||
|
};
|
||||||
|
|
||||||
#define FROM_SYSBUSGIC(type, dev) \
|
#define FROM_SYSBUSGIC(type, dev) \
|
||||||
DO_UPCAST(type, gic, FROM_SYSBUS(gic_state, dev))
|
DO_UPCAST(type, gic, FROM_SYSBUS(gic_state, dev))
|
||||||
|
|
||||||
|
@ -312,7 +312,6 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
|
||||||
cpu = gic_get_current_cpu(s);
|
cpu = gic_get_current_cpu(s);
|
||||||
cm = 1 << cpu;
|
cm = 1 << cpu;
|
||||||
if (offset < 0x100) {
|
if (offset < 0x100) {
|
||||||
#ifndef NVIC
|
|
||||||
if (offset == 0)
|
if (offset == 0)
|
||||||
return s->enabled;
|
return s->enabled;
|
||||||
if (offset == 4)
|
if (offset == 4)
|
||||||
|
@ -323,7 +322,6 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
|
||||||
/* Interrupt Security , RAZ/WI */
|
/* Interrupt Security , RAZ/WI */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
} else if (offset < 0x200) {
|
} else if (offset < 0x200) {
|
||||||
/* Interrupt Set/Clear Enable. */
|
/* Interrupt Set/Clear Enable. */
|
||||||
|
@ -385,6 +383,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
|
||||||
} else {
|
} else {
|
||||||
res = GIC_TARGET(irq);
|
res = GIC_TARGET(irq);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
} 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;
|
||||||
|
@ -397,7 +396,6 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset)
|
||||||
if (GIC_TEST_TRIGGER(irq + i))
|
if (GIC_TEST_TRIGGER(irq + i))
|
||||||
res |= (2 << (i * 2));
|
res |= (2 << (i * 2));
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
} else if (offset < 0xfe0) {
|
} else if (offset < 0xfe0) {
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
} else /* offset >= 0xfe0 */ {
|
} else /* offset >= 0xfe0 */ {
|
||||||
|
@ -424,13 +422,6 @@ 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)
|
static uint32_t gic_dist_readl(void *opaque, target_phys_addr_t offset)
|
||||||
{
|
{
|
||||||
uint32_t val;
|
uint32_t val;
|
||||||
#ifdef NVIC
|
|
||||||
gic_state *s = (gic_state *)opaque;
|
|
||||||
uint32_t addr;
|
|
||||||
addr = offset;
|
|
||||||
if (addr < 0x100 || addr > 0xd00)
|
|
||||||
return nvic_readl(s, addr);
|
|
||||||
#endif
|
|
||||||
val = gic_dist_readw(opaque, offset);
|
val = gic_dist_readw(opaque, offset);
|
||||||
val |= gic_dist_readw(opaque, offset + 2) << 16;
|
val |= gic_dist_readw(opaque, offset + 2) << 16;
|
||||||
return val;
|
return val;
|
||||||
|
@ -446,9 +437,6 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
|
||||||
|
|
||||||
cpu = gic_get_current_cpu(s);
|
cpu = gic_get_current_cpu(s);
|
||||||
if (offset < 0x100) {
|
if (offset < 0x100) {
|
||||||
#ifdef NVIC
|
|
||||||
goto bad_reg;
|
|
||||||
#else
|
|
||||||
if (offset == 0) {
|
if (offset == 0) {
|
||||||
s->enabled = (value & 1);
|
s->enabled = (value & 1);
|
||||||
DPRINTF("Distribution %sabled\n", s->enabled ? "En" : "Dis");
|
DPRINTF("Distribution %sabled\n", s->enabled ? "En" : "Dis");
|
||||||
|
@ -459,7 +447,6 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
|
||||||
} else {
|
} else {
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
} 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;
|
||||||
|
@ -552,6 +539,7 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
|
||||||
else if (irq < GIC_INTERNAL)
|
else if (irq < GIC_INTERNAL)
|
||||||
value = ALL_CPU_MASK;
|
value = ALL_CPU_MASK;
|
||||||
s->irq_target[irq] = value & ALL_CPU_MASK;
|
s->irq_target[irq] = value & ALL_CPU_MASK;
|
||||||
|
#endif
|
||||||
} 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;
|
||||||
|
@ -571,7 +559,6 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset,
|
||||||
GIC_CLEAR_TRIGGER(irq + i);
|
GIC_CLEAR_TRIGGER(irq + i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
} else {
|
} else {
|
||||||
/* 0xf00 is only handled for 32-bit writes. */
|
/* 0xf00 is only handled for 32-bit writes. */
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
|
@ -593,14 +580,6 @@ static void gic_dist_writel(void *opaque, target_phys_addr_t offset,
|
||||||
uint32_t value)
|
uint32_t value)
|
||||||
{
|
{
|
||||||
gic_state *s = (gic_state *)opaque;
|
gic_state *s = (gic_state *)opaque;
|
||||||
#ifdef NVIC
|
|
||||||
uint32_t addr;
|
|
||||||
addr = offset;
|
|
||||||
if (addr < 0x100 || (addr > 0xd00 && addr != 0xf00)) {
|
|
||||||
nvic_writel(s, addr, value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (offset == 0xf00) {
|
if (offset == 0xf00) {
|
||||||
int cpu;
|
int cpu;
|
||||||
int irq;
|
int irq;
|
||||||
|
|
|
@ -30,9 +30,16 @@ typedef struct {
|
||||||
int64_t tick;
|
int64_t tick;
|
||||||
QEMUTimer *timer;
|
QEMUTimer *timer;
|
||||||
} systick;
|
} systick;
|
||||||
|
MemoryRegion sysregmem;
|
||||||
|
MemoryRegion gic_iomem_alias;
|
||||||
|
MemoryRegion container;
|
||||||
uint32_t num_irq;
|
uint32_t num_irq;
|
||||||
} nvic_state;
|
} nvic_state;
|
||||||
|
|
||||||
|
static const uint8_t nvic_id[] = {
|
||||||
|
0x00, 0xb0, 0x1b, 0x00, 0x0d, 0xe0, 0x05, 0xb1
|
||||||
|
};
|
||||||
|
|
||||||
/* qemu timers run at 1GHz. We want something closer to 1MHz. */
|
/* qemu timers run at 1GHz. We want something closer to 1MHz. */
|
||||||
#define SYSTICK_SCALE 1000ULL
|
#define SYSTICK_SCALE 1000ULL
|
||||||
|
|
||||||
|
@ -358,12 +365,54 @@ static void nvic_writel(void *opaque, uint32_t offset, uint32_t value)
|
||||||
case 0xd38: /* Bus Fault Address. */
|
case 0xd38: /* Bus Fault Address. */
|
||||||
case 0xd3c: /* Aux Fault Status. */
|
case 0xd3c: /* Aux Fault Status. */
|
||||||
goto bad_reg;
|
goto bad_reg;
|
||||||
|
case 0xf00: /* Software Triggered Interrupt Register */
|
||||||
|
if ((value & 0x1ff) < s->num_irq) {
|
||||||
|
gic_set_pending_private(&s->gic, 0, value & 0x1ff);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
bad_reg:
|
bad_reg:
|
||||||
hw_error("NVIC: Bad write offset 0x%x\n", offset);
|
hw_error("NVIC: Bad write offset 0x%x\n", offset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint64_t nvic_sysreg_read(void *opaque, target_phys_addr_t addr,
|
||||||
|
unsigned size)
|
||||||
|
{
|
||||||
|
/* At the moment we only support the ID registers for byte/word access.
|
||||||
|
* This is not strictly correct as a few of the other registers also
|
||||||
|
* allow byte access.
|
||||||
|
*/
|
||||||
|
uint32_t offset = addr;
|
||||||
|
if (offset >= 0xfe0) {
|
||||||
|
if (offset & 3) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return nvic_id[(offset - 0xfe0) >> 2];
|
||||||
|
}
|
||||||
|
if (size == 4) {
|
||||||
|
return nvic_readl(opaque, offset);
|
||||||
|
}
|
||||||
|
hw_error("NVIC: Bad read of size %d at offset 0x%x\n", size, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nvic_sysreg_write(void *opaque, target_phys_addr_t addr,
|
||||||
|
uint64_t value, unsigned size)
|
||||||
|
{
|
||||||
|
uint32_t offset = addr;
|
||||||
|
if (size == 4) {
|
||||||
|
nvic_writel(opaque, offset, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hw_error("NVIC: Bad write of size %d at offset 0x%x\n", size, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const MemoryRegionOps nvic_sysreg_ops = {
|
||||||
|
.read = nvic_sysreg_read,
|
||||||
|
.write = nvic_sysreg_write,
|
||||||
|
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||||
|
};
|
||||||
|
|
||||||
static const VMStateDescription vmstate_nvic = {
|
static const VMStateDescription vmstate_nvic = {
|
||||||
.name = "armv7m_nvic",
|
.name = "armv7m_nvic",
|
||||||
.version_id = 1,
|
.version_id = 1,
|
||||||
|
@ -399,7 +448,30 @@ static int armv7m_nvic_init(SysBusDevice *dev)
|
||||||
/* The NVIC always has only one CPU */
|
/* The NVIC always has only one CPU */
|
||||||
s->gic.num_cpu = 1;
|
s->gic.num_cpu = 1;
|
||||||
gic_init(&s->gic, s->num_irq);
|
gic_init(&s->gic, s->num_irq);
|
||||||
memory_region_add_subregion(get_system_memory(), 0xe000e000, &s->gic.iomem);
|
/* The NVIC and system controller register area looks like this:
|
||||||
|
* 0..0xff : system control registers, including systick
|
||||||
|
* 0x100..0xcff : GIC-like registers
|
||||||
|
* 0xd00..0xfff : system control registers
|
||||||
|
* We use overlaying to put the GIC like registers
|
||||||
|
* over the top of the system control register region.
|
||||||
|
*/
|
||||||
|
memory_region_init(&s->container, "nvic", 0x1000);
|
||||||
|
/* The system register region goes at the bottom of the priority
|
||||||
|
* stack as it covers the whole page.
|
||||||
|
*/
|
||||||
|
memory_region_init_io(&s->sysregmem, &nvic_sysreg_ops, s,
|
||||||
|
"nvic_sysregs", 0x1000);
|
||||||
|
memory_region_add_subregion(&s->container, 0, &s->sysregmem);
|
||||||
|
/* Alias the GIC region so we can get only the section of it
|
||||||
|
* we need, and layer it on top of the system register region.
|
||||||
|
*/
|
||||||
|
memory_region_init_alias(&s->gic_iomem_alias, "nvic-gic", &s->gic.iomem,
|
||||||
|
0x100, 0xc00);
|
||||||
|
memory_region_add_subregion_overlap(&s->container, 0x100, &s->gic.iomem, 1);
|
||||||
|
/* Map the whole thing into system memory at the location required
|
||||||
|
* by the v7M architecture.
|
||||||
|
*/
|
||||||
|
memory_region_add_subregion(get_system_memory(), 0xe000e000, &s->container);
|
||||||
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);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue