From: Vladimir Prus <vladimir@codesourcery.com>
To: qemu-devel@nongnu.org
Subject: [Qemu-devel] SH: Improve the interrupt controller
Date: Thu, 11 Dec 2008 22:52:17 +0300 [thread overview]
Message-ID: <200812112252.17620.vladimir@codesourcery.com> (raw)
[-- Attachment #1: Type: text/plain, Size: 831 bytes --]
This patch improves the intc implementation in these ways:
- On interrupt, the priority mask in SSR is updated,
if OPM register tells it should be
- We check interrupt priority and compare it with
priority mask
- Priorities for IRL interrupts (which are fixed), are
assigned
- The ICR register is supported, and LVLMODE bit, which
controls if interrupt is automatically de-asserted,
is implemented
- A bug where handling of paired set mask / clear mask
registers was done backward is fixed
- A bug where enabling a group did not work was fixed.
I have tested this only with SH4A and it's desirable to test
with 7751/R2D. However, I no longer sure I know which kernel
to use for that. Can anybody either provide me with instructions,
or test this patch with R2D for me?
- Volodya
[-- Attachment #2: intc.diff --]
[-- Type: text/x-diff, Size: 14947 bytes --]
commit aac8877a31db83fe5cba30a742fc23b2dccee518
Author: Vladimir Prus <vladimir@codesourcery.com>
Date: Sun Oct 12 20:02:08 2008 +0400
SH: Improve the interrupt controller
* hw/sh7750.c (sh7750_init): Pass the base address to
sh_intc_init.
* hw/sh_intc.c (ICR0_LVLMODE): New.
(sh_intc_get_pending_vector): Remame parameter imask to priority,
and make it in/out. Find source with the highest priority.
(sh_intc_toggle_mask): New parameter 'priority'. Set source
priority, if provided and pass priority to other members of a
group.
(sh_intc_read): Handle read of ICR.
(sh_intc_write): Handle write of ICR. Fix masking in set/clear
register pair case.
(sh_intc_register_source, sh_intc_register_sources): If we have
priority register that controls a group, properly set enable_max
on the group members.
(sh_intc_init): Remember and register the base address.
(sh_intc_set_irl_priorities): New.
* hw/sh_intc.h (struct intc_source): New field priority.
(struct intc_desc): New fields base, icr0.
(sh_intc_get_pending_vector): Adjust prototype.
(sh_intc_init): New parameter 'base'.
(sh_intc_set_irl_priorities): New.
* target-sh4/cpu.h (struct CPUSH4State): New field opm.
* helper.c (do_interrupt): Update interrupt mask in SR if
necessary.
diff --git a/hw/sh7750.c b/hw/sh7750.c
index af86f0e..a43c2f1 100644
--- a/hw/sh7750.c
+++ b/hw/sh7750.c
@@ -701,6 +701,7 @@ SH7750State *sh7750_init(CPUSH4State * cpu)
sh7750_mm_cache_and_tlb);
sh_intc_init(&s->intc, NR_SOURCES,
+ 0x1fd00000,
_INTC_ARRAY(mask_registers),
_INTC_ARRAY(prio_registers));
diff --git a/hw/sh_intc.c b/hw/sh_intc.c
index 136e7dd..0f2ddf0 100644
--- a/hw/sh_intc.c
+++ b/hw/sh_intc.c
@@ -19,6 +19,8 @@
#define INTC_A7(x) ((x) & 0x1fffffff)
#define INTC_ARRAY(x) (sizeof(x) / sizeof(x[0]))
+#define ICR0_LVLMODE (1 << 21)
+
void sh_intc_toggle_source(struct intc_source *source,
int enable_adj, int assert_adj)
{
@@ -46,8 +48,9 @@ void sh_intc_toggle_source(struct intc_source *source,
if (pending_changed) {
if (source->pending) {
source->parent->pending++;
- if (source->parent->pending == 1)
+ if (source->parent->pending == 1) {
cpu_interrupt(first_cpu, CPU_INTERRUPT_HARD);
+ }
}
else {
source->parent->pending--;
@@ -84,30 +87,46 @@ 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;
+ }
+ }
+
+ 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",
+ printf("sh_intc: (%d) returning interrupt source 0x%x\n",
desc->pending, source->vect);
#endif
- return source->vect;
- }
- }
- assert(0);
+ if (desc->icr0 & ICR0_LVLMODE)
+ sh_intc_toggle_source(source, 0, -1);
+
+ /* Priority in intc is 5 bits, whereas processor knows only 4 bits. */
+ *priority = highest_priority >> 1;
+
+ return source->vect;
+ }
+ return -1;
}
#define INTC_MODE_NONE 0
@@ -187,7 +206,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;
@@ -202,7 +221,11 @@ static void sh_intc_toggle_mask(struct intc_desc *desc, intc_enum id,
}
if (source->vect)
+ {
sh_intc_toggle_source(source, enable ? 1 : -1, 0);
+ if (priority != -1)
+ source->priority = priority;
+ }
#ifdef DEBUG_INTC
else {
@@ -211,7 +234,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
@@ -233,6 +256,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 (INTC_A7(offset) == INTC_A7(desc->base))
+ return desc->icr0;
sh_intc_locate(desc, (unsigned long)offset, &valuep,
&enum_ids, &first, &width, &mode);
@@ -255,18 +280,26 @@ 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 (INTC_A7(offset) == INTC_A7(desc->base))
+ {
+ desc->icr0 = value;
+ return;
+ }
+
sh_intc_locate(desc, (unsigned long)offset, &valuep,
&enum_ids, &first, &width, &mode);
switch (mode) {
case INTC_MODE_ENABLE_REG | INTC_MODE_IS_PRIO: break;
- case INTC_MODE_DUAL_SET: value |= *valuep; break;
- case INTC_MODE_DUAL_CLR: value = *valuep & ~value; break;
+ case INTC_MODE_DUAL_SET: value = *valuep & ~value; break;
+ case INTC_MODE_DUAL_CLR: value |= *valuep; break;
default: assert(0);
}
for (k = 0; k <= first; k++) {
- mask = ((1 << width) - 1) << ((first - k) * width);
+ int priority = -1;
+ unsigned shift = ((first - k) * width);
+ mask = ((1 << width) - 1) << shift;
if ((*valuep & mask) == (value & mask))
continue;
@@ -274,7 +307,18 @@ 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);
+ priority = (value & mask) >> shift;
+ if (width == 8)
+ priority &= 0x1f;
+ else if (width == 4)
+ priority <<= 1;
+ }
+
+ sh_intc_toggle_mask(desc, enum_ids[k], value & mask, priority, 0);
+
}
*valuep = value;
@@ -316,24 +360,24 @@ static void sh_intc_register(struct intc_desc *desc,
}
static void sh_intc_register_source(struct intc_desc *desc,
- intc_enum source,
+ intc_enum source_id,
struct intc_group *groups,
int nr_groups)
{
unsigned int i, k;
- struct intc_source *s;
+ struct intc_source *source = sh_intc_source(desc, source_id);
+ assert(source);
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 < INTC_ARRAY(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++;
+ if (mr->enum_ids[k] == source_id)
+ {
+ source->enable_max++;
+ break;
+ }
}
}
}
@@ -343,31 +387,14 @@ static void sh_intc_register_source(struct intc_desc *desc,
struct intc_prio_reg *pr = desc->prio_regs + i;
for (k = 0; k < INTC_ARRAY(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 (pr->enum_ids[k] == source_id)
+ {
+ source->enable_max++;
+ break;
+ }
}
}
}
-
- if (groups) {
- for (i = 0; i < nr_groups; i++) {
- struct intc_group *gr = groups + i;
-
- for (k = 0; k < INTC_ARRAY(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++;
- }
- }
- }
-
}
void sh_intc_register_sources(struct intc_desc *desc,
@@ -377,7 +404,7 @@ void sh_intc_register_sources(struct intc_desc *desc,
int nr_groups)
{
unsigned int i, k;
- struct intc_source *s;
+ struct intc_source *s, *s2;
for (i = 0; i < nr_vectors; i++) {
struct intc_vect *vect = vectors + i;
@@ -394,10 +421,34 @@ 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;
s = sh_intc_source(desc, gr->enum_id);
+
+ /* Propagate group's enable_max to children. */
+ for (k = 0; k < INTC_ARRAY(gr->enum_ids); k++) {
+ if (!gr->enum_ids[k])
+ continue;
+
+ s2 = sh_intc_source(desc, gr->enum_ids[k]);
+ s2->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. */
+
+ assert(s->next_enum_id == 0);
s->next_enum_id = gr->enum_ids[0];
for (k = 1; k < INTC_ARRAY(gr->enum_ids); k++) {
@@ -405,6 +456,7 @@ void sh_intc_register_sources(struct intc_desc *desc,
continue;
s = sh_intc_source(desc, gr->enum_ids[k - 1]);
+ assert(s->next_enum_id == 0);
s->next_enum_id = gr->enum_ids[k];
}
@@ -417,6 +469,7 @@ void sh_intc_register_sources(struct intc_desc *desc,
}
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,
@@ -431,6 +484,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 = base;
i = sizeof(struct intc_source) * nr_sources;
desc->sources = malloc(i);
@@ -448,6 +502,10 @@ int sh_intc_init(struct intc_desc *desc,
desc->iomemtype = cpu_register_io_memory(0, sh_intc_readfn,
sh_intc_writefn, desc);
+
+ cpu_register_physical_memory_offset(A7ADDR(base), 4, desc->iomemtype, INTC_A7(base));
+ cpu_register_physical_memory_offset(P4ADDR(base), 4, desc->iomemtype, INTC_A7(base));
+
if (desc->mask_regs) {
for (i = 0; i < desc->nr_mask_regs; i++) {
struct intc_mask_reg *mr = desc->mask_regs + i;
@@ -483,3 +541,19 @@ void sh_intc_set_irl(void *opaque, int n, int level)
sh_intc_toggle_source(s, 0, -1);
}
}
+
+void sh_intc_set_irl_priorities(struct intc_desc *desc,
+ int low_priority_source,
+ int high_priority_source)
+{
+ int priority = 15;
+ int i;
+
+ assert (low_priority_source - high_priority_source + 1 == 15);
+ for (i = high_priority_source; i <= low_priority_source; ++i, --priority)
+ {
+ struct intc_source *sources = desc->sources + i;
+ sources->priority = priority;
+ }
+ assert(priority == 0);
+}
diff --git a/hw/sh_intc.h b/hw/sh_intc.h
index 4e36f00..d35f8dc 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,
@@ -76,5 +80,14 @@ int sh_intc_init(struct intc_desc *desc,
int nr_prio_regs);
void sh_intc_set_irl(void *opaque, int n, int level);
+
+/* Assign priorities for IRL interrupt sources. The
+ low_priority_source gets priority of 1, and
+ high_priority_source gets priority of 15. Sources in
+ between get priorities in between. The number of
+ sources in the range should be 15. */
+void sh_intc_set_irl_priorities(struct intc_desc *desc,
+ int low_priority_source,
+ int high_priority_source);
#endif /* __SH_INTC_H__ */
diff --git a/target-sh4/cpu.h b/target-sh4/cpu.h
index eed3b1b..fe02a24 100644
--- a/target-sh4/cpu.h
+++ b/target-sh4/cpu.h
@@ -140,6 +140,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 61b668b..10bcf84 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,14 @@ 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;
next reply other threads:[~2008-12-11 19:52 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2008-12-11 19:52 Vladimir Prus [this message]
2008-12-11 21:16 ` [Qemu-devel] SH: Improve the interrupt controller Jean-Christophe PLAGNIOL-VILLARD
2008-12-12 5:48 ` [Qemu-devel] " Vladimir Prus
2008-12-14 16:46 ` [Qemu-devel] " takasi-y
2008-12-14 16:57 ` Vladimir Prus
2009-01-26 13:32 ` Vladimir Prus
2009-01-26 23:51 ` Shin-ichiro KAWASAKI
2009-02-04 16:40 ` takasi-y
2009-02-08 18:57 ` takasi-y
2009-02-12 11:05 ` Vladimir Prus
2009-02-17 18:32 ` takasi-y
2009-02-19 19:57 ` Vladimir Prus
2009-03-13 17:50 ` takasi-y
2009-03-13 18:32 ` Vladimir Prus
2009-03-19 17:17 ` takasi-y
2009-03-19 17:31 ` Vladimir Prus
2009-02-20 3:06 ` Jean-Christophe PLAGNIOL-VILLARD
2009-02-20 5:16 ` Vladimir Prus
2009-02-20 5:32 ` Jean-Christophe PLAGNIOL-VILLARD
2009-02-20 5:58 ` Vladimir Prus
2009-02-20 5:59 ` Jean-Christophe PLAGNIOL-VILLARD
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=200812112252.17620.vladimir@codesourcery.com \
--to=vladimir@codesourcery.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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).