1 /* 2 * Arm PrimeCell PL190 Vector Interrupt Controller 3 * 4 * Copyright (c) 2006 CodeSourcery. 5 * Written by Paul Brook 6 * 7 * This code is licensed under the GPL. 8 */ 9 10 #include "hw/sysbus.h" 11 12 /* The number of virtual priority levels. 16 user vectors plus the 13 unvectored IRQ. Chained interrupts would require an additional level 14 if implemented. */ 15 16 #define PL190_NUM_PRIO 17 17 18 typedef struct { 19 SysBusDevice busdev; 20 MemoryRegion iomem; 21 uint32_t level; 22 uint32_t soft_level; 23 uint32_t irq_enable; 24 uint32_t fiq_select; 25 uint8_t vect_control[16]; 26 uint32_t vect_addr[PL190_NUM_PRIO]; 27 /* Mask containing interrupts with higher priority than this one. */ 28 uint32_t prio_mask[PL190_NUM_PRIO + 1]; 29 int protected; 30 /* Current priority level. */ 31 int priority; 32 int prev_prio[PL190_NUM_PRIO]; 33 qemu_irq irq; 34 qemu_irq fiq; 35 } pl190_state; 36 37 static const unsigned char pl190_id[] = 38 { 0x90, 0x11, 0x04, 0x00, 0x0D, 0xf0, 0x05, 0xb1 }; 39 40 static inline uint32_t pl190_irq_level(pl190_state *s) 41 { 42 return (s->level | s->soft_level) & s->irq_enable & ~s->fiq_select; 43 } 44 45 /* Update interrupts. */ 46 static void pl190_update(pl190_state *s) 47 { 48 uint32_t level = pl190_irq_level(s); 49 int set; 50 51 set = (level & s->prio_mask[s->priority]) != 0; 52 qemu_set_irq(s->irq, set); 53 set = ((s->level | s->soft_level) & s->fiq_select) != 0; 54 qemu_set_irq(s->fiq, set); 55 } 56 57 static void pl190_set_irq(void *opaque, int irq, int level) 58 { 59 pl190_state *s = (pl190_state *)opaque; 60 61 if (level) 62 s->level |= 1u << irq; 63 else 64 s->level &= ~(1u << irq); 65 pl190_update(s); 66 } 67 68 static void pl190_update_vectors(pl190_state *s) 69 { 70 uint32_t mask; 71 int i; 72 int n; 73 74 mask = 0; 75 for (i = 0; i < 16; i++) 76 { 77 s->prio_mask[i] = mask; 78 if (s->vect_control[i] & 0x20) 79 { 80 n = s->vect_control[i] & 0x1f; 81 mask |= 1 << n; 82 } 83 } 84 s->prio_mask[16] = mask; 85 pl190_update(s); 86 } 87 88 static uint64_t pl190_read(void *opaque, hwaddr offset, 89 unsigned size) 90 { 91 pl190_state *s = (pl190_state *)opaque; 92 int i; 93 94 if (offset >= 0xfe0 && offset < 0x1000) { 95 return pl190_id[(offset - 0xfe0) >> 2]; 96 } 97 if (offset >= 0x100 && offset < 0x140) { 98 return s->vect_addr[(offset - 0x100) >> 2]; 99 } 100 if (offset >= 0x200 && offset < 0x240) { 101 return s->vect_control[(offset - 0x200) >> 2]; 102 } 103 switch (offset >> 2) { 104 case 0: /* IRQSTATUS */ 105 return pl190_irq_level(s); 106 case 1: /* FIQSATUS */ 107 return (s->level | s->soft_level) & s->fiq_select; 108 case 2: /* RAWINTR */ 109 return s->level | s->soft_level; 110 case 3: /* INTSELECT */ 111 return s->fiq_select; 112 case 4: /* INTENABLE */ 113 return s->irq_enable; 114 case 6: /* SOFTINT */ 115 return s->soft_level; 116 case 8: /* PROTECTION */ 117 return s->protected; 118 case 12: /* VECTADDR */ 119 /* Read vector address at the start of an ISR. Increases the 120 * current priority level to that of the current interrupt. 121 * 122 * Since an enabled interrupt X at priority P causes prio_mask[Y] 123 * to have bit X set for all Y > P, this loop will stop with 124 * i == the priority of the highest priority set interrupt. 125 */ 126 for (i = 0; i < s->priority; i++) { 127 if ((s->level | s->soft_level) & s->prio_mask[i + 1]) { 128 break; 129 } 130 } 131 132 /* Reading this value with no pending interrupts is undefined. 133 We return the default address. */ 134 if (i == PL190_NUM_PRIO) 135 return s->vect_addr[16]; 136 if (i < s->priority) 137 { 138 s->prev_prio[i] = s->priority; 139 s->priority = i; 140 pl190_update(s); 141 } 142 return s->vect_addr[s->priority]; 143 case 13: /* DEFVECTADDR */ 144 return s->vect_addr[16]; 145 default: 146 qemu_log_mask(LOG_GUEST_ERROR, 147 "pl190_read: Bad offset %x\n", (int)offset); 148 return 0; 149 } 150 } 151 152 static void pl190_write(void *opaque, hwaddr offset, 153 uint64_t val, unsigned size) 154 { 155 pl190_state *s = (pl190_state *)opaque; 156 157 if (offset >= 0x100 && offset < 0x140) { 158 s->vect_addr[(offset - 0x100) >> 2] = val; 159 pl190_update_vectors(s); 160 return; 161 } 162 if (offset >= 0x200 && offset < 0x240) { 163 s->vect_control[(offset - 0x200) >> 2] = val; 164 pl190_update_vectors(s); 165 return; 166 } 167 switch (offset >> 2) { 168 case 0: /* SELECT */ 169 /* This is a readonly register, but linux tries to write to it 170 anyway. Ignore the write. */ 171 break; 172 case 3: /* INTSELECT */ 173 s->fiq_select = val; 174 break; 175 case 4: /* INTENABLE */ 176 s->irq_enable |= val; 177 break; 178 case 5: /* INTENCLEAR */ 179 s->irq_enable &= ~val; 180 break; 181 case 6: /* SOFTINT */ 182 s->soft_level |= val; 183 break; 184 case 7: /* SOFTINTCLEAR */ 185 s->soft_level &= ~val; 186 break; 187 case 8: /* PROTECTION */ 188 /* TODO: Protection (supervisor only access) is not implemented. */ 189 s->protected = val & 1; 190 break; 191 case 12: /* VECTADDR */ 192 /* Restore the previous priority level. The value written is 193 ignored. */ 194 if (s->priority < PL190_NUM_PRIO) 195 s->priority = s->prev_prio[s->priority]; 196 break; 197 case 13: /* DEFVECTADDR */ 198 s->vect_addr[16] = val; 199 break; 200 case 0xc0: /* ITCR */ 201 if (val) { 202 qemu_log_mask(LOG_UNIMP, "pl190: Test mode not implemented\n"); 203 } 204 break; 205 default: 206 qemu_log_mask(LOG_GUEST_ERROR, 207 "pl190_write: Bad offset %x\n", (int)offset); 208 return; 209 } 210 pl190_update(s); 211 } 212 213 static const MemoryRegionOps pl190_ops = { 214 .read = pl190_read, 215 .write = pl190_write, 216 .endianness = DEVICE_NATIVE_ENDIAN, 217 }; 218 219 static void pl190_reset(DeviceState *d) 220 { 221 pl190_state *s = DO_UPCAST(pl190_state, busdev.qdev, d); 222 int i; 223 224 for (i = 0; i < 16; i++) 225 { 226 s->vect_addr[i] = 0; 227 s->vect_control[i] = 0; 228 } 229 s->vect_addr[16] = 0; 230 s->prio_mask[17] = 0xffffffff; 231 s->priority = PL190_NUM_PRIO; 232 pl190_update_vectors(s); 233 } 234 235 static int pl190_init(SysBusDevice *dev) 236 { 237 pl190_state *s = FROM_SYSBUS(pl190_state, dev); 238 239 memory_region_init_io(&s->iomem, NULL, &pl190_ops, s, "pl190", 0x1000); 240 sysbus_init_mmio(dev, &s->iomem); 241 qdev_init_gpio_in(&dev->qdev, pl190_set_irq, 32); 242 sysbus_init_irq(dev, &s->irq); 243 sysbus_init_irq(dev, &s->fiq); 244 return 0; 245 } 246 247 static const VMStateDescription vmstate_pl190 = { 248 .name = "pl190", 249 .version_id = 1, 250 .minimum_version_id = 1, 251 .fields = (VMStateField[]) { 252 VMSTATE_UINT32(level, pl190_state), 253 VMSTATE_UINT32(soft_level, pl190_state), 254 VMSTATE_UINT32(irq_enable, pl190_state), 255 VMSTATE_UINT32(fiq_select, pl190_state), 256 VMSTATE_UINT8_ARRAY(vect_control, pl190_state, 16), 257 VMSTATE_UINT32_ARRAY(vect_addr, pl190_state, PL190_NUM_PRIO), 258 VMSTATE_UINT32_ARRAY(prio_mask, pl190_state, PL190_NUM_PRIO+1), 259 VMSTATE_INT32(protected, pl190_state), 260 VMSTATE_INT32(priority, pl190_state), 261 VMSTATE_INT32_ARRAY(prev_prio, pl190_state, PL190_NUM_PRIO), 262 VMSTATE_END_OF_LIST() 263 } 264 }; 265 266 static void pl190_class_init(ObjectClass *klass, void *data) 267 { 268 DeviceClass *dc = DEVICE_CLASS(klass); 269 SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); 270 271 k->init = pl190_init; 272 dc->no_user = 1; 273 dc->reset = pl190_reset; 274 dc->vmsd = &vmstate_pl190; 275 } 276 277 static const TypeInfo pl190_info = { 278 .name = "pl190", 279 .parent = TYPE_SYS_BUS_DEVICE, 280 .instance_size = sizeof(pl190_state), 281 .class_init = pl190_class_init, 282 }; 283 284 static void pl190_register_types(void) 285 { 286 type_register_static(&pl190_info); 287 } 288 289 type_init(pl190_register_types) 290