* [Qemu-devel] SH: Improve the interrupt controller 2
@ 2009-03-31 21:11 Vladimir Prus
2009-05-11 15:49 ` [Qemu-devel] " Vladimir Prus
0 siblings, 1 reply; 2+ messages in thread
From: Vladimir Prus @ 2009-03-31 21:11 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 854 bytes --]
Hello,
this patch is a revision of the patch I have posted at:
http://article.gmane.org/gmane.comp.emulators.qemu/34204
The original patch was necessary to get SH7785 emulation working,
but broke 7750. Takashi Yoshi investigated and a revised patch in:
http://article.gmane.org/gmane.comp.emulators.qemu/37891
with further refinements in:
http://article.gmane.org/gmane.comp.emulators.qemu/38459
http://article.gmane.org/gmane.comp.emulators.qemu/39662
The patch I am posting here is a mechanical composition of all
of above. I have previously said that it does not work for 7785,
but the problem turned out to be in my 7785 code, so, to the best
of my knowledge, this works for both 7750 and 7785. The patch
is against SVN r6963. It took quite a number of iterations and time
to arrive here, so I hope this patch can go in.
- Volodya
[-- Attachment #2: intc.diff --]
[-- Type: text/x-patch, Size: 17480 bytes --]
commit be3026950d578cf035931e10035b79f80255df48
Author: Vladimir Prus <vladimir@codesourcery.com>
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 <n> IRL interrupt.
+/* Assert level <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;
^ permalink raw reply related [flat|nested] 2+ messages in thread
* [Qemu-devel] Re: SH: Improve the interrupt controller 2
2009-03-31 21:11 [Qemu-devel] SH: Improve the interrupt controller 2 Vladimir Prus
@ 2009-05-11 15:49 ` Vladimir Prus
0 siblings, 0 replies; 2+ messages in thread
From: Vladimir Prus @ 2009-05-11 15:49 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: Text/Plain, Size: 958 bytes --]
On Wednesday 01 April 2009 01:11:05 Vladimir Prus wrote:
>
> Hello,
>
> this patch is a revision of the patch I have posted at:
>
> http://article.gmane.org/gmane.comp.emulators.qemu/34204
>
> The original patch was necessary to get SH7785 emulation working,
> but broke 7750. Takashi Yoshi investigated and a revised patch in:
>
> http://article.gmane.org/gmane.comp.emulators.qemu/37891
>
> with further refinements in:
>
> http://article.gmane.org/gmane.comp.emulators.qemu/38459
> http://article.gmane.org/gmane.comp.emulators.qemu/39662
>
> The patch I am posting here is a mechanical composition of all
> of above. I have previously said that it does not work for 7785,
> but the problem turned out to be in my 7785 code, so, to the best
> of my knowledge, this works for both 7750 and 7785. The patch
> is against SVN r6963. It took quite a number of iterations and time
> to arrive here, so I hope this patch can go in.
PING?
- Volodya
[-- Attachment #2: intc.diff --]
[-- Type: text/x-patch, Size: 0 bytes --]
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2009-05-11 15:50 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2009-03-31 21:11 [Qemu-devel] SH: Improve the interrupt controller 2 Vladimir Prus
2009-05-11 15:49 ` [Qemu-devel] " Vladimir Prus
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).