commit be3026950d578cf035931e10035b79f80255df48 Author: Vladimir Prus Date: Mon Mar 30 22:49:44 2009 +0400 Improve the interrupt controller. diff --git a/hw/sh7750.c b/hw/sh7750.c index 423c43f..addd435 100644 --- a/hw/sh7750.c +++ b/hw/sh7750.c @@ -502,13 +502,13 @@ static struct intc_group groups[] = { }; static struct intc_prio_reg prio_registers[] = { - { 0xffd00004, 0, 16, 4, /* IPRA */ { TMU0, TMU1, TMU2, RTC } }, - { 0xffd00008, 0, 16, 4, /* IPRB */ { WDT, REF, SCI1, 0 } }, - { 0xffd0000c, 0, 16, 4, /* IPRC */ { GPIOI, DMAC, SCIF, HUDI } }, - { 0xffd00010, 0, 16, 4, /* IPRD */ { IRL0, IRL1, IRL2, IRL3 } }, - { 0xfe080000, 0, 32, 4, /* INTPRI00 */ { 0, 0, 0, 0, - TMU4, TMU3, - PCIC1, PCIC0_PCISERR } }, + { 0xffd00004, 0, 16, 4, /* IPRA */ { TMU0, TMU1, TMU2, RTC } }, + { 0xffd00008, 0, 16, 4, /* IPRB */ { WDT, REF, SCI1, 0 } }, + { 0xffd0000c, 0, 16, 4, /* IPRC */ { GPIOI, DMAC, SCIF, HUDI } }, + { 0xffd00010, 0, 16, 4, /* IPRD */ { IRL0, IRL1, IRL2, IRL3 }, 0xda74 }, + { 0xfe080000, 0, 32, 4, /* INTPRI00 */ { 0, 0, 0, 0, + TMU4, TMU3, + PCIC1, PCIC0_PCISERR } }, }; /* SH7750, SH7750S, SH7751 and SH7091 all have 4-channel DMA controllers */ @@ -730,11 +730,11 @@ SH7750State *sh7750_init(CPUSH4State * cpu) sh7750_mmct_read, sh7750_mmct_write, s); cpu_register_physical_memory(0xf0000000, 0x08000000, - sh7750_mm_cache_and_tlb); + sh7750_mm_cache_and_tlb); - sh_intc_init(&s->intc, NR_SOURCES, - _INTC_ARRAY(mask_registers), - _INTC_ARRAY(prio_registers)); + sh_intc_init(&s->intc, 0x1fd00000, NR_SOURCES, + _INTC_ARRAY(mask_registers), + _INTC_ARRAY(prio_registers)); sh_intc_register_sources(&s->intc, _INTC_ARRAY(vectors), @@ -806,7 +806,8 @@ SH7750State *sh7750_init(CPUSH4State * cpu) qemu_irq sh7750_irl(SH7750State *s) { - sh_intc_toggle_source(sh_intc_source(&s->intc, IRL), 1, 0); /* enable */ - return qemu_allocate_irqs(sh_intc_set_irl, sh_intc_source(&s->intc, IRL), - 1)[0]; + struct intc_source *irl = sh_intc_source(&s->intc, IRL); + + sh_intc_init_irl_priorities(irl); + return qemu_allocate_irqs(sh_intc_set_irl, irl, 1)[0]; } diff --git a/hw/sh_intc.c b/hw/sh_intc.c index f4138fd..2a576a9 100644 --- a/hw/sh_intc.c +++ b/hw/sh_intc.c @@ -18,6 +18,8 @@ #define INTC_A7(x) ((x) & 0x1fffffff) +#define ICR0_LVLMODE (1 << 21) + void sh_intc_toggle_source(struct intc_source *source, int enable_adj, int assert_adj) { @@ -83,30 +85,44 @@ static void sh_intc_set_irq (void *opaque, int n, int level) sh_intc_toggle_source(source, 0, -1); } -int sh_intc_get_pending_vector(struct intc_desc *desc, int imask) +int sh_intc_get_pending_vector(struct intc_desc *desc, int *priority) { unsigned int i; + unsigned highest_priority = 0; + int found = -1; - /* slow: use a linked lists of pending sources instead */ - /* wrong: take interrupt priority into account (one list per priority) */ + /* slow: use a linked list of pending sources instead */ - if (imask == 0x0f) { - return -1; /* FIXME, update code to include priority per source */ - } + if (*priority == 0x0f) + return -1; for (i = 0; i < desc->nr_sources; i++) { struct intc_source *source = desc->sources + i; - if (source->pending) { + if (source->pending && source->priority > highest_priority) { + highest_priority = source->priority; + found = i; + } + } + + /* Priority in intc is 5 bits, whereas processor knows only 4 bits. */ + highest_priority >>= 1; + + if (found != -1 && highest_priority > *priority) { + struct intc_source *source = desc->sources + found; + #ifdef DEBUG_INTC_SOURCES - printf("sh_intc: (%d) returning interrupt source 0x%x\n", - desc->pending, source->vect); + printf("sh_intc: (%d) returning interrupt source 0x%x\n", + desc->pending, source->vect); #endif - return source->vect; - } - } + if (desc->icr0 & ICR0_LVLMODE) + sh_intc_toggle_source(source, 0, -1); - assert(0); + *priority = highest_priority; + + return source->vect; + } + return -1; } #define INTC_MODE_NONE 0 @@ -186,7 +202,7 @@ static void sh_intc_locate(struct intc_desc *desc, } static void sh_intc_toggle_mask(struct intc_desc *desc, intc_enum id, - int enable, int is_group) + int enable, int priority, int is_group) { struct intc_source *source = desc->sources + id; @@ -200,8 +216,14 @@ static void sh_intc_toggle_mask(struct intc_desc *desc, intc_enum id, return; } - if (source->vect) - sh_intc_toggle_source(source, enable ? 1 : -1, 0); + if (source->vect) { + if (priority != -1) + source->priority = priority; + if (enable && source->enable_count < source->enable_max) + sh_intc_toggle_source(source, 1, 0); + if (!enable && source->enable_count > 0) + sh_intc_toggle_source(source, -1, 0); + } #ifdef DEBUG_INTC else { @@ -210,7 +232,7 @@ static void sh_intc_toggle_mask(struct intc_desc *desc, intc_enum id, #endif if ((is_group || !source->vect) && source->next_enum_id) { - sh_intc_toggle_mask(desc, source->next_enum_id, enable, 1); + sh_intc_toggle_mask(desc, source->next_enum_id, enable, priority, 1); } #ifdef DEBUG_INTC @@ -232,6 +254,8 @@ static uint32_t sh_intc_read(void *opaque, target_phys_addr_t offset) #ifdef DEBUG_INTC printf("sh_intc_read 0x%lx\n", (unsigned long) offset); #endif + if (offset == desc->base) + return desc->icr0; sh_intc_locate(desc, (unsigned long)offset, &valuep, &enum_ids, &first, &width, &mode); @@ -254,6 +278,11 @@ static void sh_intc_write(void *opaque, target_phys_addr_t offset, printf("sh_intc_write 0x%lx 0x%08x\n", (unsigned long) offset, value); #endif + if (offset == desc->base) { + desc->icr0 = value; + return; + } + sh_intc_locate(desc, (unsigned long)offset, &valuep, &enum_ids, &first, &width, &mode); @@ -265,7 +294,8 @@ static void sh_intc_write(void *opaque, target_phys_addr_t offset, } for (k = 0; k <= first; k++) { - mask = ((1 << width) - 1) << ((first - k) * width); + unsigned shift = ((first - k) * width); + mask = ((1 << width) - 1) << shift; if ((*valuep & mask) == (value & mask)) continue; @@ -273,7 +303,16 @@ static void sh_intc_write(void *opaque, target_phys_addr_t offset, printf("k = %d, first = %d, enum = %d, mask = 0x%08x\n", k, first, enum_ids[k], (unsigned int)mask); #endif - sh_intc_toggle_mask(desc, enum_ids[k], value & mask, 0); + if (mode & INTC_MODE_IS_PRIO) { + assert (width == 4 || width == 8); + int priority = (value & mask) >> shift; + if (width == 8) + priority &= 0x1f; + else if (width == 4) + priority <<= 1; + sh_intc_toggle_mask(desc, enum_ids[k], (priority>1), priority, 0); + } else + sh_intc_toggle_mask(desc, enum_ids[k], !(value & mask), -1, 0); } *valuep = value; @@ -317,56 +356,42 @@ static void sh_intc_register(struct intc_desc *desc, static void sh_intc_register_source(struct intc_desc *desc, intc_enum source, struct intc_group *groups, - int nr_groups) + int nr_groups) { unsigned int i, k; - struct intc_source *s; + struct intc_source *s = sh_intc_source(desc, source); + assert(s); if (desc->mask_regs) { for (i = 0; i < desc->nr_mask_regs; i++) { - struct intc_mask_reg *mr = desc->mask_regs + i; - - for (k = 0; k < ARRAY_SIZE(mr->enum_ids); k++) { - if (mr->enum_ids[k] != source) - continue; - - s = sh_intc_source(desc, mr->enum_ids[k]); - if (s) - s->enable_max++; - } - } + struct intc_mask_reg *mr = desc->mask_regs + i; + + for (k = 0; k < ARRAY_SIZE(mr->enum_ids); k++) { + int shift = mr->reg_width - (k+1); + if (mr->enum_ids[k] == source) { + s->enable_max++; + s->enable_count += (mr->value >> shift) & 1; + break; + } + } + } } - if (desc->prio_regs) { for (i = 0; i < desc->nr_prio_regs; i++) { - struct intc_prio_reg *pr = desc->prio_regs + i; - - for (k = 0; k < ARRAY_SIZE(pr->enum_ids); k++) { - if (pr->enum_ids[k] != source) - continue; - - s = sh_intc_source(desc, pr->enum_ids[k]); - if (s) - s->enable_max++; - } - } - } - - if (groups) { - for (i = 0; i < nr_groups; i++) { - struct intc_group *gr = groups + i; - - for (k = 0; k < ARRAY_SIZE(gr->enum_ids); k++) { - if (gr->enum_ids[k] != source) - continue; - - s = sh_intc_source(desc, gr->enum_ids[k]); - if (s) - s->enable_max++; - } - } + struct intc_prio_reg *pr = desc->prio_regs + i; + + for (k = 0; k < ARRAY_SIZE(pr->enum_ids); k++) { + int shift = pr->reg_width - pr->field_width * (k+1); + int mask = (1 << pr->field_width) - 1; + if (pr->enum_ids[k] == source) { + s->priority = (pr->value >> shift) & mask; + s->enable_max++; + s->enable_count += s->priority?1:0; + break; + } + } + } } - } void sh_intc_register_sources(struct intc_desc *desc, @@ -393,19 +418,43 @@ void sh_intc_register_sources(struct intc_desc *desc, } if (groups) { + /* First of all, register group's sources, so that enable_max is + property set. */ + for (i = 0; i < nr_groups; i++) { + struct intc_group *gr = groups + i; + sh_intc_register_source(desc, gr->enum_id, groups, nr_groups); + } + for (i = 0; i < nr_groups; i++) { - struct intc_group *gr = groups + i; + struct intc_group *gr = groups + i; + + s = sh_intc_source(desc, gr->enum_id); + + /* Propagate group's enable_max to children. */ + for (k = 0; k < ARRAY_SIZE(gr->enum_ids); k++) { + struct intc_source *child; + if (!gr->enum_ids[k]) + continue; + + child = sh_intc_source(desc, gr->enum_ids[k]); + child->enable_max += s->enable_max; + } + + /* Chain sources within each group via source->next_enum_id, + so that we can easily enable/disable all sources in + a group later. */ - s = sh_intc_source(desc, gr->enum_id); - s->next_enum_id = gr->enum_ids[0]; + assert(s->next_enum_id == 0); + s->next_enum_id = gr->enum_ids[0]; - for (k = 1; k < ARRAY_SIZE(gr->enum_ids); k++) { + for (k = 1; k < ARRAY_SIZE(gr->enum_ids); k++) { if (!gr->enum_ids[k]) continue; - s = sh_intc_source(desc, gr->enum_ids[k - 1]); - s->next_enum_id = gr->enum_ids[k]; - } + s = sh_intc_source(desc, gr->enum_ids[k - 1]); + assert(s->next_enum_id == 0); + s->next_enum_id = gr->enum_ids[k]; + } #ifdef DEBUG_INTC_SOURCES printf("sh_intc: registered group %d (%d/%d)\n", @@ -416,9 +465,10 @@ void sh_intc_register_sources(struct intc_desc *desc, } int sh_intc_init(struct intc_desc *desc, - int nr_sources, - struct intc_mask_reg *mask_regs, - int nr_mask_regs, + target_phys_addr_t base, + int nr_sources, + struct intc_mask_reg *mask_regs, + int nr_mask_regs, struct intc_prio_reg *prio_regs, int nr_prio_regs) { @@ -430,6 +480,7 @@ int sh_intc_init(struct intc_desc *desc, desc->nr_mask_regs = nr_mask_regs; desc->prio_regs = prio_regs; desc->nr_prio_regs = nr_prio_regs; + desc->base = INTC_A7(base); i = sizeof(struct intc_source) * nr_sources; desc->sources = qemu_malloc(i); @@ -443,12 +494,15 @@ int sh_intc_init(struct intc_desc *desc, desc->irqs = qemu_allocate_irqs(sh_intc_set_irq, desc, nr_sources); + desc->iomemtype = cpu_register_io_memory(0, sh_intc_readfn, - sh_intc_writefn, desc); + sh_intc_writefn, desc); + + sh_intc_register(desc, base); /* icr0 */ + if (desc->mask_regs) { for (i = 0; i < desc->nr_mask_regs; i++) { - struct intc_mask_reg *mr = desc->mask_regs + i; - + struct intc_mask_reg *mr = desc->mask_regs + i; sh_intc_register(desc, mr->set_reg); sh_intc_register(desc, mr->clr_reg); } @@ -466,17 +520,42 @@ int sh_intc_init(struct intc_desc *desc, return 0; } -/* Assert level IRL interrupt. +/* Assert level IRL interrupt. 0:deassert. 1:lowest priority,... 15:highest priority. */ void sh_intc_set_irl(void *opaque, int n, int level) { struct intc_source *s = opaque; - int i, irl = level ^ 15; - for (i = 0; (s = sh_intc_source(s->parent, s->next_enum_id)); i++) { - if (i == irl) - sh_intc_toggle_source(s, s->enable_count?0:1, s->asserted?0:1); - else - if (s->asserted) - sh_intc_toggle_source(s, 0, -1); + while ((s = sh_intc_source(s->parent, s->next_enum_id))) { + if (s->priority/2 == level) + sh_intc_toggle_source(s, 0, s->asserted?0:1); + else + if (s->asserted) + sh_intc_toggle_source(s, 0, -1); + } +} + +/* Initialize priorities for IRL interrupt sources. + Member #1 of the IRL group is set to 15, ... #15 is set to 1. + The number of members must be 15 */ +void sh_intc_init_irl_priorities(struct intc_source *s) +{ + int priority = 15; + while ((s = sh_intc_source(s->parent, s->next_enum_id))) + s->priority = priority-- *2; + assert(priority == 0); +} + +void sh_intc_debug_lockup (struct intc_desc *desc) +{ + int i; + for (i = 0; i < desc->nr_sources; i++) + { + struct intc_source *source = desc->sources + i; + + if (source->asserted && !source->pending) + { + printf ("source %d (%x) asserted, not pending, e = %d/%d\n", + i, source->vect, source->enable_count, source->enable_max); + } } } diff --git a/hw/sh_intc.h b/hw/sh_intc.h index a9750ae..83b646e 100644 --- a/hw/sh_intc.h +++ b/hw/sh_intc.h @@ -42,6 +42,7 @@ struct intc_source { int enable_count; int enable_max; int pending; /* emulates the result of signal and masking */ + int priority; struct intc_desc *parent; }; @@ -55,9 +56,11 @@ struct intc_desc { int nr_prio_regs; int iomemtype; int pending; /* number of interrupt sources that has pending set */ + target_phys_addr_t base; + unsigned icr0; }; -int sh_intc_get_pending_vector(struct intc_desc *desc, int imask); +int sh_intc_get_pending_vector(struct intc_desc *desc, int *priority); struct intc_source *sh_intc_source(struct intc_desc *desc, intc_enum id); void sh_intc_toggle_source(struct intc_source *source, int enable_adj, int assert_adj); @@ -69,6 +72,7 @@ void sh_intc_register_sources(struct intc_desc *desc, int nr_groups); int sh_intc_init(struct intc_desc *desc, + target_phys_addr_t base, int nr_sources, struct intc_mask_reg *mask_regs, int nr_mask_regs, @@ -77,4 +81,6 @@ int sh_intc_init(struct intc_desc *desc, void sh_intc_set_irl(void *opaque, int n, int level); +void sh_intc_init_irl_priorities(struct intc_source *s); + #endif /* __SH_INTC_H__ */ diff --git a/target-sh4/cpu.h b/target-sh4/cpu.h index aea7108..e9eee2c 100644 --- a/target-sh4/cpu.h +++ b/target-sh4/cpu.h @@ -141,6 +141,7 @@ typedef struct CPUSH4State { uint32_t pvr; /* Processor Version Register */ uint32_t prr; /* Processor Revision Register */ uint32_t cvr; /* Cache Version Register */ + uint32_t opm; /* CPU Operation Mode Register */ uint32_t ldst; diff --git a/target-sh4/helper.c b/target-sh4/helper.c index c506086..f1feca7 100644 --- a/target-sh4/helper.c +++ b/target-sh4/helper.c @@ -81,6 +81,7 @@ void do_interrupt(CPUState * env) { int do_irq = env->interrupt_request & CPU_INTERRUPT_HARD; int do_exp, irq_vector = env->exception_index; + int priority = (env->sr >> 4) & 0xf; /* prioritize exceptions over interrupts */ @@ -99,7 +100,7 @@ void do_interrupt(CPUState * env) if (do_irq) { irq_vector = sh_intc_get_pending_vector(env->intc_handle, - (env->sr >> 4) & 0xf); + &priority); if (irq_vector == -1) { return; /* masked */ } @@ -157,6 +158,13 @@ void do_interrupt(CPUState * env) } env->ssr = env->sr; + + if (env->opm & (1 << 3)) { + unsigned mask = 0xf << 4; + env->ssr &= ~mask; + env->ssr |= (priority << 4) & mask; + } + env->spc = env->pc; env->sgr = env->gregs[15]; env->sr |= SR_BL | SR_MD | SR_RB;