1 /* 2 * SuperH interrupt controller module 3 * 4 * Copyright (c) 2007 Magnus Damm 5 * Based on sh_timer.c and arm_timer.c by Paul Brook 6 * Copyright (c) 2005-2006 CodeSourcery. 7 * 8 * This code is licensed under the GPL. 9 */ 10 11 #include "qemu/osdep.h" 12 #include "qemu-common.h" 13 #include "cpu.h" 14 #include "hw/sh4/sh_intc.h" 15 #include "hw/hw.h" 16 #include "hw/sh4/sh.h" 17 18 //#define DEBUG_INTC 19 //#define DEBUG_INTC_SOURCES 20 21 #define INTC_A7(x) ((x) & 0x1fffffff) 22 23 void sh_intc_toggle_source(struct intc_source *source, 24 int enable_adj, int assert_adj) 25 { 26 int enable_changed = 0; 27 int pending_changed = 0; 28 int old_pending; 29 30 if ((source->enable_count == source->enable_max) && (enable_adj == -1)) 31 enable_changed = -1; 32 33 source->enable_count += enable_adj; 34 35 if (source->enable_count == source->enable_max) 36 enable_changed = 1; 37 38 source->asserted += assert_adj; 39 40 old_pending = source->pending; 41 source->pending = source->asserted && 42 (source->enable_count == source->enable_max); 43 44 if (old_pending != source->pending) 45 pending_changed = 1; 46 47 if (pending_changed) { 48 if (source->pending) { 49 source->parent->pending++; 50 if (source->parent->pending == 1) { 51 cpu_interrupt(first_cpu, CPU_INTERRUPT_HARD); 52 } 53 } else { 54 source->parent->pending--; 55 if (source->parent->pending == 0) { 56 cpu_reset_interrupt(first_cpu, CPU_INTERRUPT_HARD); 57 } 58 } 59 } 60 61 if (enable_changed || assert_adj || pending_changed) { 62 #ifdef DEBUG_INTC_SOURCES 63 printf("sh_intc: (%d/%d/%d/%d) interrupt source 0x%x %s%s%s\n", 64 source->parent->pending, 65 source->asserted, 66 source->enable_count, 67 source->enable_max, 68 source->vect, 69 source->asserted ? "asserted " : 70 assert_adj ? "deasserted" : "", 71 enable_changed == 1 ? "enabled " : 72 enable_changed == -1 ? "disabled " : "", 73 source->pending ? "pending" : ""); 74 #endif 75 } 76 } 77 78 static void sh_intc_set_irq (void *opaque, int n, int level) 79 { 80 struct intc_desc *desc = opaque; 81 struct intc_source *source = &(desc->sources[n]); 82 83 if (level && !source->asserted) 84 sh_intc_toggle_source(source, 0, 1); 85 else if (!level && source->asserted) 86 sh_intc_toggle_source(source, 0, -1); 87 } 88 89 int sh_intc_get_pending_vector(struct intc_desc *desc, int imask) 90 { 91 unsigned int i; 92 93 /* slow: use a linked lists of pending sources instead */ 94 /* wrong: take interrupt priority into account (one list per priority) */ 95 96 if (imask == 0x0f) { 97 return -1; /* FIXME, update code to include priority per source */ 98 } 99 100 for (i = 0; i < desc->nr_sources; i++) { 101 struct intc_source *source = desc->sources + i; 102 103 if (source->pending) { 104 #ifdef DEBUG_INTC_SOURCES 105 printf("sh_intc: (%d) returning interrupt source 0x%x\n", 106 desc->pending, source->vect); 107 #endif 108 return source->vect; 109 } 110 } 111 112 abort(); 113 } 114 115 #define INTC_MODE_NONE 0 116 #define INTC_MODE_DUAL_SET 1 117 #define INTC_MODE_DUAL_CLR 2 118 #define INTC_MODE_ENABLE_REG 3 119 #define INTC_MODE_MASK_REG 4 120 #define INTC_MODE_IS_PRIO 8 121 122 static unsigned int sh_intc_mode(unsigned long address, 123 unsigned long set_reg, unsigned long clr_reg) 124 { 125 if ((address != INTC_A7(set_reg)) && 126 (address != INTC_A7(clr_reg))) 127 return INTC_MODE_NONE; 128 129 if (set_reg && clr_reg) { 130 if (address == INTC_A7(set_reg)) 131 return INTC_MODE_DUAL_SET; 132 else 133 return INTC_MODE_DUAL_CLR; 134 } 135 136 if (set_reg) 137 return INTC_MODE_ENABLE_REG; 138 else 139 return INTC_MODE_MASK_REG; 140 } 141 142 static void sh_intc_locate(struct intc_desc *desc, 143 unsigned long address, 144 unsigned long **datap, 145 intc_enum **enums, 146 unsigned int *first, 147 unsigned int *width, 148 unsigned int *modep) 149 { 150 unsigned int i, mode; 151 152 /* this is slow but works for now */ 153 154 if (desc->mask_regs) { 155 for (i = 0; i < desc->nr_mask_regs; i++) { 156 struct intc_mask_reg *mr = desc->mask_regs + i; 157 158 mode = sh_intc_mode(address, mr->set_reg, mr->clr_reg); 159 if (mode == INTC_MODE_NONE) 160 continue; 161 162 *modep = mode; 163 *datap = &mr->value; 164 *enums = mr->enum_ids; 165 *first = mr->reg_width - 1; 166 *width = 1; 167 return; 168 } 169 } 170 171 if (desc->prio_regs) { 172 for (i = 0; i < desc->nr_prio_regs; i++) { 173 struct intc_prio_reg *pr = desc->prio_regs + i; 174 175 mode = sh_intc_mode(address, pr->set_reg, pr->clr_reg); 176 if (mode == INTC_MODE_NONE) 177 continue; 178 179 *modep = mode | INTC_MODE_IS_PRIO; 180 *datap = &pr->value; 181 *enums = pr->enum_ids; 182 *first = (pr->reg_width / pr->field_width) - 1; 183 *width = pr->field_width; 184 return; 185 } 186 } 187 188 abort(); 189 } 190 191 static void sh_intc_toggle_mask(struct intc_desc *desc, intc_enum id, 192 int enable, int is_group) 193 { 194 struct intc_source *source = desc->sources + id; 195 196 if (!id) 197 return; 198 199 if (!source->next_enum_id && (!source->enable_max || !source->vect)) { 200 #ifdef DEBUG_INTC_SOURCES 201 printf("sh_intc: reserved interrupt source %d modified\n", id); 202 #endif 203 return; 204 } 205 206 if (source->vect) 207 sh_intc_toggle_source(source, enable ? 1 : -1, 0); 208 209 #ifdef DEBUG_INTC 210 else { 211 printf("setting interrupt group %d to %d\n", id, !!enable); 212 } 213 #endif 214 215 if ((is_group || !source->vect) && source->next_enum_id) { 216 sh_intc_toggle_mask(desc, source->next_enum_id, enable, 1); 217 } 218 219 #ifdef DEBUG_INTC 220 if (!source->vect) { 221 printf("setting interrupt group %d to %d - done\n", id, !!enable); 222 } 223 #endif 224 } 225 226 static uint64_t sh_intc_read(void *opaque, hwaddr offset, 227 unsigned size) 228 { 229 struct intc_desc *desc = opaque; 230 intc_enum *enum_ids = NULL; 231 unsigned int first = 0; 232 unsigned int width = 0; 233 unsigned int mode = 0; 234 unsigned long *valuep; 235 236 #ifdef DEBUG_INTC 237 printf("sh_intc_read 0x%lx\n", (unsigned long) offset); 238 #endif 239 240 sh_intc_locate(desc, (unsigned long)offset, &valuep, 241 &enum_ids, &first, &width, &mode); 242 return *valuep; 243 } 244 245 static void sh_intc_write(void *opaque, hwaddr offset, 246 uint64_t value, unsigned size) 247 { 248 struct intc_desc *desc = opaque; 249 intc_enum *enum_ids = NULL; 250 unsigned int first = 0; 251 unsigned int width = 0; 252 unsigned int mode = 0; 253 unsigned int k; 254 unsigned long *valuep; 255 unsigned long mask; 256 257 #ifdef DEBUG_INTC 258 printf("sh_intc_write 0x%lx 0x%08x\n", (unsigned long) offset, value); 259 #endif 260 261 sh_intc_locate(desc, (unsigned long)offset, &valuep, 262 &enum_ids, &first, &width, &mode); 263 264 switch (mode) { 265 case INTC_MODE_ENABLE_REG | INTC_MODE_IS_PRIO: break; 266 case INTC_MODE_DUAL_SET: value |= *valuep; break; 267 case INTC_MODE_DUAL_CLR: value = *valuep & ~value; break; 268 default: abort(); 269 } 270 271 for (k = 0; k <= first; k++) { 272 mask = ((1 << width) - 1) << ((first - k) * width); 273 274 if ((*valuep & mask) == (value & mask)) 275 continue; 276 #if 0 277 printf("k = %d, first = %d, enum = %d, mask = 0x%08x\n", 278 k, first, enum_ids[k], (unsigned int)mask); 279 #endif 280 sh_intc_toggle_mask(desc, enum_ids[k], value & mask, 0); 281 } 282 283 *valuep = value; 284 285 #ifdef DEBUG_INTC 286 printf("sh_intc_write 0x%lx -> 0x%08x\n", (unsigned long) offset, value); 287 #endif 288 } 289 290 static const MemoryRegionOps sh_intc_ops = { 291 .read = sh_intc_read, 292 .write = sh_intc_write, 293 .endianness = DEVICE_NATIVE_ENDIAN, 294 }; 295 296 struct intc_source *sh_intc_source(struct intc_desc *desc, intc_enum id) 297 { 298 if (id) 299 return desc->sources + id; 300 301 return NULL; 302 } 303 304 static unsigned int sh_intc_register(MemoryRegion *sysmem, 305 struct intc_desc *desc, 306 const unsigned long address, 307 const char *type, 308 const char *action, 309 const unsigned int index) 310 { 311 char name[60]; 312 MemoryRegion *iomem, *iomem_p4, *iomem_a7; 313 314 if (!address) { 315 return 0; 316 } 317 318 iomem = &desc->iomem; 319 iomem_p4 = desc->iomem_aliases + index; 320 iomem_a7 = iomem_p4 + 1; 321 322 #define SH_INTC_IOMEM_FORMAT "interrupt-controller-%s-%s-%s" 323 snprintf(name, sizeof(name), SH_INTC_IOMEM_FORMAT, type, action, "p4"); 324 memory_region_init_alias(iomem_p4, NULL, name, iomem, INTC_A7(address), 4); 325 memory_region_add_subregion(sysmem, P4ADDR(address), iomem_p4); 326 327 snprintf(name, sizeof(name), SH_INTC_IOMEM_FORMAT, type, action, "a7"); 328 memory_region_init_alias(iomem_a7, NULL, name, iomem, INTC_A7(address), 4); 329 memory_region_add_subregion(sysmem, A7ADDR(address), iomem_a7); 330 #undef SH_INTC_IOMEM_FORMAT 331 332 /* used to increment aliases index */ 333 return 2; 334 } 335 336 static void sh_intc_register_source(struct intc_desc *desc, 337 intc_enum source, 338 struct intc_group *groups, 339 int nr_groups) 340 { 341 unsigned int i, k; 342 struct intc_source *s; 343 344 if (desc->mask_regs) { 345 for (i = 0; i < desc->nr_mask_regs; i++) { 346 struct intc_mask_reg *mr = desc->mask_regs + i; 347 348 for (k = 0; k < ARRAY_SIZE(mr->enum_ids); k++) { 349 if (mr->enum_ids[k] != source) 350 continue; 351 352 s = sh_intc_source(desc, mr->enum_ids[k]); 353 if (s) 354 s->enable_max++; 355 } 356 } 357 } 358 359 if (desc->prio_regs) { 360 for (i = 0; i < desc->nr_prio_regs; i++) { 361 struct intc_prio_reg *pr = desc->prio_regs + i; 362 363 for (k = 0; k < ARRAY_SIZE(pr->enum_ids); k++) { 364 if (pr->enum_ids[k] != source) 365 continue; 366 367 s = sh_intc_source(desc, pr->enum_ids[k]); 368 if (s) 369 s->enable_max++; 370 } 371 } 372 } 373 374 if (groups) { 375 for (i = 0; i < nr_groups; i++) { 376 struct intc_group *gr = groups + i; 377 378 for (k = 0; k < ARRAY_SIZE(gr->enum_ids); k++) { 379 if (gr->enum_ids[k] != source) 380 continue; 381 382 s = sh_intc_source(desc, gr->enum_ids[k]); 383 if (s) 384 s->enable_max++; 385 } 386 } 387 } 388 389 } 390 391 void sh_intc_register_sources(struct intc_desc *desc, 392 struct intc_vect *vectors, 393 int nr_vectors, 394 struct intc_group *groups, 395 int nr_groups) 396 { 397 unsigned int i, k; 398 struct intc_source *s; 399 400 for (i = 0; i < nr_vectors; i++) { 401 struct intc_vect *vect = vectors + i; 402 403 sh_intc_register_source(desc, vect->enum_id, groups, nr_groups); 404 s = sh_intc_source(desc, vect->enum_id); 405 if (s) { 406 s->vect = vect->vect; 407 408 #ifdef DEBUG_INTC_SOURCES 409 printf("sh_intc: registered source %d -> 0x%04x (%d/%d)\n", 410 vect->enum_id, s->vect, s->enable_count, s->enable_max); 411 #endif 412 } 413 } 414 415 if (groups) { 416 for (i = 0; i < nr_groups; i++) { 417 struct intc_group *gr = groups + i; 418 419 s = sh_intc_source(desc, gr->enum_id); 420 s->next_enum_id = gr->enum_ids[0]; 421 422 for (k = 1; k < ARRAY_SIZE(gr->enum_ids); k++) { 423 if (!gr->enum_ids[k]) 424 continue; 425 426 s = sh_intc_source(desc, gr->enum_ids[k - 1]); 427 s->next_enum_id = gr->enum_ids[k]; 428 } 429 430 #ifdef DEBUG_INTC_SOURCES 431 printf("sh_intc: registered group %d (%d/%d)\n", 432 gr->enum_id, s->enable_count, s->enable_max); 433 #endif 434 } 435 } 436 } 437 438 int sh_intc_init(MemoryRegion *sysmem, 439 struct intc_desc *desc, 440 int nr_sources, 441 struct intc_mask_reg *mask_regs, 442 int nr_mask_regs, 443 struct intc_prio_reg *prio_regs, 444 int nr_prio_regs) 445 { 446 unsigned int i, j; 447 448 desc->pending = 0; 449 desc->nr_sources = nr_sources; 450 desc->mask_regs = mask_regs; 451 desc->nr_mask_regs = nr_mask_regs; 452 desc->prio_regs = prio_regs; 453 desc->nr_prio_regs = nr_prio_regs; 454 /* Allocate 4 MemoryRegions per register (2 actions * 2 aliases). 455 **/ 456 desc->iomem_aliases = g_new0(MemoryRegion, 457 (nr_mask_regs + nr_prio_regs) * 4); 458 459 j = 0; 460 i = sizeof(struct intc_source) * nr_sources; 461 desc->sources = g_malloc0(i); 462 463 for (i = 0; i < desc->nr_sources; i++) { 464 struct intc_source *source = desc->sources + i; 465 466 source->parent = desc; 467 } 468 469 desc->irqs = qemu_allocate_irqs(sh_intc_set_irq, desc, nr_sources); 470 471 memory_region_init_io(&desc->iomem, NULL, &sh_intc_ops, desc, 472 "interrupt-controller", 0x100000000ULL); 473 474 #define INT_REG_PARAMS(reg_struct, type, action, j) \ 475 reg_struct->action##_reg, #type, #action, j 476 if (desc->mask_regs) { 477 for (i = 0; i < desc->nr_mask_regs; i++) { 478 struct intc_mask_reg *mr = desc->mask_regs + i; 479 480 j += sh_intc_register(sysmem, desc, 481 INT_REG_PARAMS(mr, mask, set, j)); 482 j += sh_intc_register(sysmem, desc, 483 INT_REG_PARAMS(mr, mask, clr, j)); 484 } 485 } 486 487 if (desc->prio_regs) { 488 for (i = 0; i < desc->nr_prio_regs; i++) { 489 struct intc_prio_reg *pr = desc->prio_regs + i; 490 491 j += sh_intc_register(sysmem, desc, 492 INT_REG_PARAMS(pr, prio, set, j)); 493 j += sh_intc_register(sysmem, desc, 494 INT_REG_PARAMS(pr, prio, clr, j)); 495 } 496 } 497 #undef INT_REG_PARAMS 498 499 return 0; 500 } 501 502 /* Assert level <n> IRL interrupt. 503 0:deassert. 1:lowest priority,... 15:highest priority. */ 504 void sh_intc_set_irl(void *opaque, int n, int level) 505 { 506 struct intc_source *s = opaque; 507 int i, irl = level ^ 15; 508 for (i = 0; (s = sh_intc_source(s->parent, s->next_enum_id)); i++) { 509 if (i == irl) 510 sh_intc_toggle_source(s, s->enable_count?0:1, s->asserted?0:1); 511 else 512 if (s->asserted) 513 sh_intc_toggle_source(s, 0, -1); 514 } 515 } 516