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