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.mayd...@linaro.org> --- hw/arm_gic.c | 33 ++++------------------- hw/armv7m_nvic.c | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 28 deletions(-) diff --git a/hw/arm_gic.c b/hw/arm_gic.c index 3293ae4..2ec10ce 100644 --- a/hw/arm_gic.c +++ b/hw/arm_gic.c @@ -37,17 +37,17 @@ do { printf("arm_gic: " fmt , ## __VA_ARGS__); } while (0) #endif #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 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_BASE_IRQ 0 #endif +static const uint8_t gic_id[] = { + 0x90, 0x13, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 +}; + #define FROM_SYSBUSGIC(type, 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); cm = 1 << cpu; if (offset < 0x100) { -#ifndef NVIC if (offset == 0) return s->enabled; if (offset == 4) @@ -323,7 +322,6 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset) /* Interrupt Security , RAZ/WI */ return 0; } -#endif goto bad_reg; } else if (offset < 0x200) { /* Interrupt Set/Clear Enable. */ @@ -385,6 +383,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset) } else { res = GIC_TARGET(irq); } +#endif } else if (offset < 0xf00) { /* Interrupt Configuration. */ 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)) res |= (2 << (i * 2)); } -#endif } else if (offset < 0xfe0) { goto bad_reg; } 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) { 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 + 2) << 16; return val; @@ -446,9 +437,6 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset, cpu = gic_get_current_cpu(s); if (offset < 0x100) { -#ifdef NVIC - goto bad_reg; -#else if (offset == 0) { s->enabled = (value & 1); 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 { goto bad_reg; } -#endif } else if (offset < 0x180) { /* Interrupt Set Enable. */ 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) value = ALL_CPU_MASK; s->irq_target[irq] = value & ALL_CPU_MASK; +#endif } else if (offset < 0xf00) { /* Interrupt Configuration. */ 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); } } -#endif } else { /* 0xf00 is only handled for 32-bit writes. */ goto bad_reg; @@ -593,14 +580,6 @@ 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; - if (addr < 0x100 || (addr > 0xd00 && addr != 0xf00)) { - nvic_writel(s, addr, value); - return; - } -#endif if (offset == 0xf00) { int cpu; int irq; diff --git a/hw/armv7m_nvic.c b/hw/armv7m_nvic.c index 653c011..747e245 100644 --- a/hw/armv7m_nvic.c +++ b/hw/armv7m_nvic.c @@ -30,9 +30,16 @@ typedef struct { int64_t tick; QEMUTimer *timer; } systick; + MemoryRegion sysregmem; + MemoryRegion gic_iomem_alias; + MemoryRegion container; uint32_t num_irq; } 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. */ #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 0xd3c: /* Aux Fault Status. */ 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: bad_reg: 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 = { .name = "armv7m_nvic", .version_id = 1, @@ -399,7 +448,30 @@ static int armv7m_nvic_init(SysBusDevice *dev) /* The NVIC always has only one CPU */ s->gic.num_cpu = 1; 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); return 0; } -- 1.7.1