/* * Arm PrimeCell PL190 Vector Interrupt Controller * * Copyright (c) 2006 CodeSourcery. * Written by Paul Brook * * This code is licensed under the GPL. */ #include "qemu/osdep.h" #include "hw/irq.h" #include "hw/sysbus.h" #include "migration/vmstate.h" #include "qemu/log.h" #include "qemu/module.h" #include "qom/object.h" /* The number of virtual priority levels. 16 user vectors plus the unvectored IRQ. Chained interrupts would require an additional level if implemented. */ #define PL190_NUM_PRIO 17 #define TYPE_PL190 "pl190" OBJECT_DECLARE_SIMPLE_TYPE(PL190State, PL190) struct PL190State { SysBusDevice parent_obj; MemoryRegion iomem; uint32_t level; uint32_t soft_level; uint32_t irq_enable; uint32_t fiq_select; uint8_t vect_control[16]; uint32_t vect_addr[PL190_NUM_PRIO]; /* Mask containing interrupts with higher priority than this one. */ uint32_t prio_mask[PL190_NUM_PRIO + 1]; int protected; /* Current priority level. */ int priority; int prev_prio[PL190_NUM_PRIO]; qemu_irq irq; qemu_irq fiq; }; static const unsigned char pl190_id[] = { 0x90, 0x11, 0x04, 0x00, 0x0D, 0xf0, 0x05, 0xb1 }; static inline uint32_t pl190_irq_level(PL190State *s) { return (s->level | s->soft_level) & s->irq_enable & ~s->fiq_select; } /* Update interrupts. */ static void pl190_update(PL190State *s) { uint32_t level = pl190_irq_level(s); int set; set = (level & s->prio_mask[s->priority]) != 0; qemu_set_irq(s->irq, set); set = ((s->level | s->soft_level) & s->fiq_select) != 0; qemu_set_irq(s->fiq, set); } static void pl190_set_irq(void *opaque, int irq, int level) { PL190State *s = (PL190State *)opaque; if (level) s->level |= 1u << irq; else s->level &= ~(1u << irq); pl190_update(s); } static void pl190_update_vectors(PL190State *s) { uint32_t mask; int i; int n; mask = 0; for (i = 0; i < 16; i++) { s->prio_mask[i] = mask; if (s->vect_control[i] & 0x20) { n = s->vect_control[i] & 0x1f; mask |= 1 << n; } } s->prio_mask[16] = mask; pl190_update(s); } static uint64_t pl190_read(void *opaque, hwaddr offset, unsigned size) { PL190State *s = (PL190State *)opaque; int i; if (offset >= 0xfe0 && offset < 0x1000) { return pl190_id[(offset - 0xfe0) >> 2]; } if (offset >= 0x100 && offset < 0x140) { return s->vect_addr[(offset - 0x100) >> 2]; } if (offset >= 0x200 && offset < 0x240) { return s->vect_control[(offset - 0x200) >> 2]; } switch (offset >> 2) { case 0: /* IRQSTATUS */ return pl190_irq_level(s); case 1: /* FIQSATUS */ return (s->level | s->soft_level) & s->fiq_select; case 2: /* RAWINTR */ return s->level | s->soft_level; case 3: /* INTSELECT */ return s->fiq_select; case 4: /* INTENABLE */ return s->irq_enable; case 6: /* SOFTINT */ return s->soft_level; case 8: /* PROTECTION */ return s->protected; case 12: /* VECTADDR */ /* Read vector address at the start of an ISR. Increases the * current priority level to that of the current interrupt. * * Since an enabled interrupt X at priority P causes prio_mask[Y] * to have bit X set for all Y > P, this loop will stop with * i == the priority of the highest priority set interrupt. */ for (i = 0; i < s->priority; i++) { if ((s->level | s->soft_level) & s->prio_mask[i + 1]) { break; } } /* Reading this value with no pending interrupts is undefined. We return the default address. */ if (i == PL190_NUM_PRIO) return s->vect_addr[16]; if (i < s->priority) { s->prev_prio[i] = s->priority; s->priority = i; pl190_update(s); } return s->vect_addr[s->priority]; case 13: /* DEFVECTADDR */ return s->vect_addr[16]; default: qemu_log_mask(LOG_GUEST_ERROR, "pl190_read: Bad offset %x\n", (int)offset); return 0; } } static void pl190_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) { PL190State *s = (PL190State *)opaque; if (offset >= 0x100 && offset < 0x140) { s->vect_addr[(offset - 0x100) >> 2] = val; pl190_update_vectors(s); return; } if (offset >= 0x200 && offset < 0x240) { s->vect_control[(offset - 0x200) >> 2] = val; pl190_update_vectors(s); return; } switch (offset >> 2) { case 0: /* SELECT */ /* This is a readonly register, but linux tries to write to it anyway. Ignore the write. */ break; case 3: /* INTSELECT */ s->fiq_select = val; break; case 4: /* INTENABLE */ s->irq_enable |= val; break; case 5: /* INTENCLEAR */ s->irq_enable &= ~val; break; case 6: /* SOFTINT */ s->soft_level |= val; break; case 7: /* SOFTINTCLEAR */ s->soft_level &= ~val; break; case 8: /* PROTECTION */ /* TODO: Protection (supervisor only access) is not implemented. */ s->protected = val & 1; break; case 12: /* VECTADDR */ /* Restore the previous priority level. The value written is ignored. */ if (s->priority < PL190_NUM_PRIO) s->priority = s->prev_prio[s->priority]; break; case 13: /* DEFVECTADDR */ s->vect_addr[16] = val; break; case 0xc0: /* ITCR */ if (val) { qemu_log_mask(LOG_UNIMP, "pl190: Test mode not implemented\n"); } break; default: qemu_log_mask(LOG_GUEST_ERROR, "pl190_write: Bad offset %x\n", (int)offset); return; } pl190_update(s); } static const MemoryRegionOps pl190_ops = { .read = pl190_read, .write = pl190_write, .endianness = DEVICE_NATIVE_ENDIAN, }; static void pl190_reset(DeviceState *d) { PL190State *s = PL190(d); int i; for (i = 0; i < 16; i++) { s->vect_addr[i] = 0; s->vect_control[i] = 0; } s->vect_addr[16] = 0; s->prio_mask[17] = 0xffffffff; s->priority = PL190_NUM_PRIO; pl190_update_vectors(s); } static void pl190_init(Object *obj) { DeviceState *dev = DEVICE(obj); PL190State *s = PL190(obj); SysBusDevice *sbd = SYS_BUS_DEVICE(obj); memory_region_init_io(&s->iomem, obj, &pl190_ops, s, "pl190", 0x1000); sysbus_init_mmio(sbd, &s->iomem); qdev_init_gpio_in(dev, pl190_set_irq, 32); sysbus_init_irq(sbd, &s->irq); sysbus_init_irq(sbd, &s->fiq); } static const VMStateDescription vmstate_pl190 = { .name = "pl190", .version_id = 1, .minimum_version_id = 1, .fields = (const VMStateField[]) { VMSTATE_UINT32(level, PL190State), VMSTATE_UINT32(soft_level, PL190State), VMSTATE_UINT32(irq_enable, PL190State), VMSTATE_UINT32(fiq_select, PL190State), VMSTATE_UINT8_ARRAY(vect_control, PL190State, 16), VMSTATE_UINT32_ARRAY(vect_addr, PL190State, PL190_NUM_PRIO), VMSTATE_UINT32_ARRAY(prio_mask, PL190State, PL190_NUM_PRIO+1), VMSTATE_INT32(protected, PL190State), VMSTATE_INT32(priority, PL190State), VMSTATE_INT32_ARRAY(prev_prio, PL190State, PL190_NUM_PRIO), VMSTATE_END_OF_LIST() } }; static void pl190_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_legacy_reset(dc, pl190_reset); dc->vmsd = &vmstate_pl190; } static const TypeInfo pl190_info = { .name = TYPE_PL190, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(PL190State), .instance_init = pl190_init, .class_init = pl190_class_init, }; static void pl190_register_types(void) { type_register_static(&pl190_info); } type_init(pl190_register_types)