@@ -390,6 +390,30 @@ struct GTY(()) mips_frame_info {
HOST_WIDE_INT hard_frame_pointer_offset;
};
+/* Enumeration for masked vectored (VI) and non-masked (EIC) interrupts. */
+enum mips_int_mask
+{
+ INT_MASK_EIC = -1,
+ INT_MASK_SW0 = 0,
+ INT_MASK_SW1 = 1,
+ INT_MASK_HW0 = 2,
+ INT_MASK_HW1 = 3,
+ INT_MASK_HW2 = 4,
+ INT_MASK_HW3 = 5,
+ INT_MASK_HW4 = 6,
+ INT_MASK_HW5 = 7
+};
+
+/* Enumeration to mark the existence of the shadow register set.
+ SHADOW_SET_INTSTACK indicates a shadow register set with a valid stack
+ pointer. */
+enum mips_shadow_set
+{
+ SHADOW_SET_NO,
+ SHADOW_SET_YES,
+ SHADOW_SET_INTSTACK
+};
+
struct GTY(()) machine_function {
/* The next floating-point condition-code register to allocate
for ISA_HAS_8CC targets, relative to ST_REG_FIRST. */
@@ -442,8 +466,12 @@ struct GTY(()) machine_function {
/* True if this is an interrupt handler. */
bool interrupt_handler_p;
- /* True if this is an interrupt handler that uses shadow registers. */
- bool use_shadow_register_set_p;
+ /* Records the way in which interrupts should be masked. Only used if
+ interrupts are not kept masked. */
+ enum mips_int_mask int_mask;
+
+ /* Records if this is an interrupt handler that uses shadow registers. */
+ enum mips_shadow_set use_shadow_register_set;
/* True if this is an interrupt handler that should keep interrupts
masked. */
@@ -725,6 +753,10 @@ const enum reg_class mips_regno_to_class[FIRST_PSEUDO_REGISTER] = {
ALL_REGS, ALL_REGS, ALL_REGS, ALL_REGS
};
+static tree mips_handle_interrupt_attr (tree *, tree, tree, int, bool *);
+static tree mips_handle_use_shadow_register_set_attr (tree *, tree, tree, int,
+ bool *);
+
/* The value of TARGET_ATTRIBUTE_TABLE. */
static const struct attribute_spec mips_attribute_table[] = {
/* { name, min_len, max_len, decl_req, type_req, fn_type_req, handler,
@@ -742,8 +774,10 @@ static const struct attribute_spec mips_attribute_table[] = {
{ "nomicromips", 0, 0, true, false, false, NULL, false },
{ "nocompression", 0, 0, true, false, false, NULL, false },
/* Allow functions to be specified as interrupt handlers */
- { "interrupt", 0, 0, false, true, true, NULL, false },
- { "use_shadow_register_set", 0, 0, false, true, true, NULL, false },
+ { "interrupt", 0, 1, false, true, true, mips_handle_interrupt_attr,
+ false },
+ { "use_shadow_register_set", 0, 1, false, true, true,
+ mips_handle_use_shadow_register_set_attr, false },
{ "keep_interrupts_masked", 0, 0, false, true, true, NULL, false },
{ "use_debug_exception_return", 0, 0, false, true, true, NULL, false },
{ NULL, 0, 0, false, false, false, NULL, false }
@@ -1325,13 +1359,62 @@ mips_interrupt_type_p (tree type)
return lookup_attribute ("interrupt", TYPE_ATTRIBUTES (type)) != NULL;
}
+static enum mips_int_mask
+mips_interrupt_mask (tree type)
+{
+ tree attr = lookup_attribute ("interrupt", TYPE_ATTRIBUTES (type));
+ tree args, cst;
+ const char *str;
+
+ /* For missing attributes or no arguments then return 'eic' as a safe
+ fallback. */
+ if (attr == NULL)
+ return INT_MASK_EIC;
+
+ args = TREE_VALUE (attr);
+
+ if (args == NULL)
+ return INT_MASK_EIC;
+
+ cst = TREE_VALUE (args);
+
+ if (strcmp (TREE_STRING_POINTER (cst), "eic") == 0)
+ return INT_MASK_EIC;
+
+ /* The validation code in mips_handle_interrupt_attr guarantees that the
+ argument is now in the form:
+ vector=(sw0|sw1|hw0|hw1|hw2|hw3|hw4|hw5). */
+ str = TREE_STRING_POINTER (cst);
+
+ gcc_assert (strlen (str) == strlen ("vector=sw0"));
+
+ if (str[7] == 's')
+ return (enum mips_int_mask) (INT_MASK_SW0 + (str[9] - '0'));
+
+ return (enum mips_int_mask) (INT_MASK_HW0 + (str[9] - '0'));
+}
+
/* Check if the attribute to use shadow register set is set for a function. */
-static bool
-mips_use_shadow_register_set_p (tree type)
+static enum mips_shadow_set
+mips_use_shadow_register_set (tree type)
{
- return lookup_attribute ("use_shadow_register_set",
- TYPE_ATTRIBUTES (type)) != NULL;
+ tree attr = lookup_attribute ("use_shadow_register_set",
+ TYPE_ATTRIBUTES (type));
+ tree args, cst;
+
+ /* The validation code in mips_handle_use_shadow_register_set_attr guarantees
+ that if an argument is present then it means: Assume the shadow register
+ set has a valid stack pointer in it. */
+ if (attr == NULL)
+ return SHADOW_SET_NO;
+
+ args = TREE_VALUE (attr);
+
+ if (args == NULL)
+ return SHADOW_SET_YES;
+
+ return SHADOW_SET_INTSTACK;
}
/* Check if the attribute to keep interrupts masked is set for a function. */
@@ -1537,6 +1620,87 @@ mips_can_inline_p (tree caller, tree callee)
return false;
return default_target_can_inline_p (caller, callee);
}
+
+static tree
+mips_handle_interrupt_attr (tree *node, tree name, tree args,
+ int flags ATTRIBUTE_UNUSED, bool *no_add_attrs)
+{
+ /* Check for an argument. */
+ if (is_attribute_p ("interrupt", name) && args != NULL)
+ {
+ tree cst;
+
+ cst = TREE_VALUE (args);
+ if (TREE_CODE (cst) != STRING_CST)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute requires a string argument",
+ name);
+ *no_add_attrs = true;
+ }
+ else if (strcmp (TREE_STRING_POINTER (cst), "eic") != 0
+ && strncmp (TREE_STRING_POINTER (cst), "vector=", 7) != 0)
+ {
+ warning (OPT_Wattributes,
+ "argument to %qE attribute is neither eic, nor "
+ "vector=<line>", name);
+ *no_add_attrs = true;
+ }
+ else if (strncmp (TREE_STRING_POINTER (cst), "vector=", 7) == 0)
+ {
+ const char *arg = TREE_STRING_POINTER (cst) + 7;
+
+ /* Acceptable names are: sw0,sw1,hw0,hw1,hw2,hw3,hw4,hw5. */
+ if (strlen (arg) != 3
+ || (arg[0] != 's' && arg[0] != 'h')
+ || arg[1] != 'w'
+ || (arg[0] == 's' && arg[2] != '0' && arg[2] != '1')
+ || (arg[0] == 'h' && (arg[2] < '0' || arg[2] > '5')))
+ {
+ warning (OPT_Wattributes,
+ "interrupt vector to %qE attribute is not "
+ "vector=(sw0|sw1|hw0|hw1|hw2|hw3|hw4|hw5)",
+ name);
+ *no_add_attrs = true;
+ }
+ }
+
+ return NULL_TREE;
+ }
+
+ return NULL_TREE;
+}
+
+static tree
+mips_handle_use_shadow_register_set_attr (tree *node, tree name, tree args,
+ int flags ATTRIBUTE_UNUSED,
+ bool *no_add_attrs)
+{
+ /* Check for an argument. */
+ if (is_attribute_p ("use_shadow_register_set", name) && args != NULL)
+ {
+ tree cst;
+
+ cst = TREE_VALUE (args);
+ if (TREE_CODE (cst) != STRING_CST)
+ {
+ warning (OPT_Wattributes,
+ "%qE attribute requires a string argument",
+ name);
+ *no_add_attrs = true;
+ }
+ else if (strcmp (TREE_STRING_POINTER (cst), "intstack") != 0)
+ {
+ warning (OPT_Wattributes,
+ "argument to %qE attribute is not intstack", name);
+ *no_add_attrs = true;
+ }
+
+ return NULL_TREE;
+ }
+
+ return NULL_TREE;
+}
/* If X is a PLUS of a CONST_INT, return the two terms in *BASE_PTR
and *OFFSET_PTR. Return X in *BASE_PTR and 0 in *OFFSET_PTR otherwise. */
@@ -10052,7 +10216,8 @@ mips_interrupt_extra_call_saved_reg_p (unsigned int regno)
if (TARGET_DSP && DSP_ACC_REG_P (regno))
return true;
- if (GP_REG_P (regno) && !cfun->machine->use_shadow_register_set_p)
+ if (GP_REG_P (regno)
+ && cfun->machine->use_shadow_register_set == SHADOW_SET_NO)
{
/* $0 is hard-wired. */
if (regno == GP_REG_FIRST)
@@ -10266,8 +10431,10 @@ mips_compute_frame_info (void)
else
{
cfun->machine->interrupt_handler_p = true;
- cfun->machine->use_shadow_register_set_p =
- mips_use_shadow_register_set_p (TREE_TYPE (current_function_decl));
+ cfun->machine->int_mask =
+ mips_interrupt_mask (TREE_TYPE (current_function_decl));
+ cfun->machine->use_shadow_register_set =
+ mips_use_shadow_register_set (TREE_TYPE (current_function_decl));
cfun->machine->keep_interrupts_masked_p =
mips_keep_interrupts_masked_p (TREE_TYPE (current_function_decl));
cfun->machine->use_debug_exception_return_p =
@@ -10382,9 +10549,9 @@ mips_compute_frame_info (void)
/* All interrupt context functions need space to preserve STATUS. */
frame->num_cop0_regs++;
- /* If we don't keep interrupts masked, we need to save EPC. */
- if (!cfun->machine->keep_interrupts_masked_p)
- frame->num_cop0_regs++;
+ /* We need to save EPC regardless of whether interrupts remain masked
+ as exceptions will corrupt EPC. */
+ frame->num_cop0_regs++;
}
/* Move above the accumulator save area. */
@@ -11425,21 +11592,21 @@ mips_expand_prologue (void)
/* If this interrupt is using a shadow register set, we need to
get the stack pointer from the previous register set. */
- if (cfun->machine->use_shadow_register_set_p)
- emit_insn (gen_mips_rdpgpr (stack_pointer_rtx,
- stack_pointer_rtx));
+ if (cfun->machine->use_shadow_register_set == SHADOW_SET_YES)
+ emit_insn (PMODE_INSN (gen_mips_rdpgpr, (stack_pointer_rtx,
+ stack_pointer_rtx)));
if (!cfun->machine->keep_interrupts_masked_p)
{
- /* Move from COP0 Cause to K0. */
- emit_insn (gen_cop0_move (gen_rtx_REG (SImode, K0_REG_NUM),
- gen_rtx_REG (SImode,
- COP0_CAUSE_REG_NUM)));
- /* Move from COP0 EPC to K1. */
- emit_insn (gen_cop0_move (gen_rtx_REG (SImode, K1_REG_NUM),
- gen_rtx_REG (SImode,
- COP0_EPC_REG_NUM)));
+ if (cfun->machine->int_mask == INT_MASK_EIC)
+ /* Move from COP0 Cause to K0. */
+ emit_insn (gen_cop0_move (gen_rtx_REG (SImode, K0_REG_NUM),
+ gen_rtx_REG (SImode, COP0_CAUSE_REG_NUM)));
}
+ /* Move from COP0 EPC to K1. */
+ emit_insn (gen_cop0_move (gen_rtx_REG (SImode, K1_REG_NUM),
+ gen_rtx_REG (SImode,
+ COP0_EPC_REG_NUM)));
/* Allocate the first part of the frame. */
rtx insn = gen_add3_insn (stack_pointer_rtx, stack_pointer_rtx,
@@ -11450,15 +11617,13 @@ mips_expand_prologue (void)
/* Start at the uppermost location for saving. */
offset = frame->cop0_sp_offset - size;
- if (!cfun->machine->keep_interrupts_masked_p)
- {
- /* Push EPC into its stack slot. */
- mem = gen_frame_mem (word_mode,
- plus_constant (Pmode, stack_pointer_rtx,
- offset));
- mips_emit_move (mem, gen_rtx_REG (word_mode, K1_REG_NUM));
- offset -= UNITS_PER_WORD;
- }
+
+ /* Push EPC into its stack slot. */
+ mem = gen_frame_mem (word_mode,
+ plus_constant (Pmode, stack_pointer_rtx,
+ offset));
+ mips_emit_move (mem, gen_rtx_REG (word_mode, K1_REG_NUM));
+ offset -= UNITS_PER_WORD;
/* Move from COP0 Status to K1. */
emit_insn (gen_cop0_move (gen_rtx_REG (SImode, K1_REG_NUM),
@@ -11466,7 +11631,8 @@ mips_expand_prologue (void)
COP0_STATUS_REG_NUM)));
/* Right justify the RIPL in k0. */
- if (!cfun->machine->keep_interrupts_masked_p)
+ if (!cfun->machine->keep_interrupts_masked_p
+ && cfun->machine->int_mask == INT_MASK_EIC)
emit_insn (gen_lshrsi3 (gen_rtx_REG (SImode, K0_REG_NUM),
gen_rtx_REG (SImode, K0_REG_NUM),
GEN_INT (CAUSE_IPL)));
@@ -11479,12 +11645,22 @@ mips_expand_prologue (void)
offset -= UNITS_PER_WORD;
/* Insert the RIPL into our copy of SR (k1) as the new IPL. */
- if (!cfun->machine->keep_interrupts_masked_p)
+ if (!cfun->machine->keep_interrupts_masked_p
+ && cfun->machine->int_mask == INT_MASK_EIC)
emit_insn (gen_insvsi (gen_rtx_REG (SImode, K1_REG_NUM),
GEN_INT (6),
GEN_INT (SR_IPL),
gen_rtx_REG (SImode, K0_REG_NUM)));
+ /* Clear all interrupt mask bits up to and including the
+ handler's interrupt line. */
+ if (!cfun->machine->keep_interrupts_masked_p
+ && cfun->machine->int_mask != INT_MASK_EIC)
+ emit_insn (gen_insvsi (gen_rtx_REG (SImode, K1_REG_NUM),
+ GEN_INT (cfun->machine->int_mask + 1),
+ GEN_INT (SR_IM0),
+ gen_rtx_REG (SImode, GP_REG_FIRST)));
+
if (!cfun->machine->keep_interrupts_masked_p)
/* Enable interrupts by clearing the KSU ERL and EXL bits.
IE is already the correct value, so we don't have to do
@@ -11845,29 +12021,27 @@ mips_expand_epilogue (bool sibcall_p)
rtx mem;
offset = frame->cop0_sp_offset - (frame->total_size - step2);
- if (!cfun->machine->keep_interrupts_masked_p)
- {
- /* Restore the original EPC. */
- mem = gen_frame_mem (word_mode,
- plus_constant (Pmode, stack_pointer_rtx,
- offset));
- mips_emit_move (gen_rtx_REG (word_mode, K0_REG_NUM), mem);
- offset -= UNITS_PER_WORD;
- /* Move to COP0 EPC. */
- emit_insn (gen_cop0_move (gen_rtx_REG (SImode, COP0_EPC_REG_NUM),
- gen_rtx_REG (SImode, K0_REG_NUM)));
- }
+ /* Restore the original EPC. */
+ mem = gen_frame_mem (word_mode,
+ plus_constant (Pmode, stack_pointer_rtx,
+ offset));
+ mips_emit_move (gen_rtx_REG (word_mode, K1_REG_NUM), mem);
+ offset -= UNITS_PER_WORD;
+
+ /* Move to COP0 EPC. */
+ emit_insn (gen_cop0_move (gen_rtx_REG (SImode, COP0_EPC_REG_NUM),
+ gen_rtx_REG (SImode, K1_REG_NUM)));
/* Restore the original Status. */
mem = gen_frame_mem (word_mode,
plus_constant (Pmode, stack_pointer_rtx,
offset));
- mips_emit_move (gen_rtx_REG (word_mode, K0_REG_NUM), mem);
+ mips_emit_move (gen_rtx_REG (word_mode, K1_REG_NUM), mem);
offset -= UNITS_PER_WORD;
/* If we don't use shadow register set, we need to update SP. */
- if (!cfun->machine->use_shadow_register_set_p)
+ if (cfun->machine->use_shadow_register_set == SHADOW_SET_NO)
mips_deallocate_stack (stack_pointer_rtx, GEN_INT (step2), 0);
else
/* The choice of position is somewhat arbitrary in this case. */
@@ -11875,7 +12049,7 @@ mips_expand_epilogue (bool sibcall_p)
/* Move to COP0 Status. */
emit_insn (gen_cop0_move (gen_rtx_REG (SImode, COP0_STATUS_REG_NUM),
- gen_rtx_REG (SImode, K0_REG_NUM)));
+ gen_rtx_REG (SImode, K1_REG_NUM)));
}
else if (TARGET_MICROMIPS
&& !crtl->calls_eh_return
@@ -1809,6 +1809,9 @@ FP_ASM_SPEC "\
#define CAUSE_IPL 10
/* Interrupt Priority Level is from bit 10 to bit 15 of the status register. */
#define SR_IPL 10
+/* Interrupt masks start with IM0 at bit 8 to IM7 at bit 15 of the status
+ register. */
+#define SR_IM0 8
/* Exception Level is at bit 1 of the status register. */
#define SR_EXL 1
/* Interrupt Enable is at bit 0 of the status register. */
@@ -6564,14 +6564,14 @@ (define_insn "mips_ehb"
(set_attr "mode" "none")])
;; Read GPR from previous shadow register set.
-(define_insn "mips_rdpgpr"
- [(set (match_operand:SI 0 "register_operand" "=d")
- (unspec_volatile:SI [(match_operand:SI 1 "register_operand" "d")]
- UNSPEC_RDPGPR))]
+(define_insn "mips_rdpgpr_<mode>"
+ [(set (match_operand:P 0 "register_operand" "=d")
+ (unspec_volatile:P [(match_operand:P 1 "register_operand" "d")]
+ UNSPEC_RDPGPR))]
""
"rdpgpr\t%0,%1"
[(set_attr "type" "move")
- (set_attr "mode" "SI")])
+ (set_attr "mode" "<MODE>")])
;; Move involving COP0 registers.
(define_insn "cop0_move"
@@ -4090,10 +4090,18 @@ These function attributes are supported by the MIPS back end:
@table @code
@item interrupt
@cindex @code{interrupt} function attribute, MIPS
-Use this attribute to indicate
-that the specified function is an interrupt handler. The compiler generates
-function entry and exit sequences suitable for use in an interrupt handler
-when this attribute is present.
+Use this attribute to indicate that the specified function is an interrupt
+handler. The compiler generates function entry and exit sequences suitable
+for use in an interrupt handler when this attribute is present.
+An optional argument is supported for the interrupt attribute which allows
+the interrupt mode to be described. By default GCC assumes the external
+interrupt controller (EIC) mode is in use, this can be explicitly set using
+@code{eic}. When interrupts are non-masked then the requested Interrupt
+Priority Level (IPL) is copied to the current IPL which has the effect of only
+enabling higher priority interrupts. To use vectored interrupt mode use
+the argument @code{vector=[sw0|sw1|hw0|hw1|hw2|hw3|hw4|hw5]}, this will change
+the behaviour of the non-masked interrupt support and GCC will arrange to mask
+all interrupts from sw0 up to and including the specified interrupt vector.
You can use the following attributes to modify the behavior
of an interrupt handler:
@@ -4101,7 +4109,9 @@ of an interrupt handler:
@item use_shadow_register_set
@cindex @code{use_shadow_register_set} function attribute, MIPS
Assume that the handler uses a shadow register set, instead of
-the main general-purpose registers.
+the main general-purpose registers. An optional argument @code{intstack} is
+supported to indicate that the shadow register set contains a valid stack
+pointer.
@item keep_interrupts_masked
@cindex @code{keep_interrupts_masked} function attribute, MIPS
@@ -4129,6 +4139,8 @@ void __attribute__ ((interrupt, keep_interrupts_masked,
void __attribute__ ((interrupt, use_shadow_register_set,
keep_interrupts_masked,
use_debug_exception_return)) v7 ();
+void __attribute__ ((interrupt("eic"))) v8 ();
+void __attribute__ ((interrupt("vector=hw3"))) v9 ();
@end smallexample
@item long_call
new file mode 100644
@@ -0,0 +1,31 @@
+/* Test optional argument for interrupt and use_shadow_register_set
+ attributes. */
+/* { dg-do compile } */
+/* { dg-options "isa_rev>=2" } */
+/* { dg-final { scan-assembler "e0:.*ins\t\\\$27,\\\$26,10,6.*\.end\te0" } } */
+/* { dg-final { scan-assembler-times "mfc0\t\\\$26,\\\$13" 3 } } */
+/* { dg-final { scan-assembler-times "mfc0\t\\\$27,\\\$14" 11 } } */
+/* { dg-final { scan-assembler "v0:.*ins\t\\\$27,\\\$0,8,1.*\.end\tv0" } } */
+/* { dg-final { scan-assembler "v1:.*ins\t\\\$27,\\\$0,8,2.*\.end\tv1" } } */
+/* { dg-final { scan-assembler "v2:.*ins\t\\\$27,\\\$0,8,3.*\.end\tv2" } } */
+/* { dg-final { scan-assembler "v3:.*ins\t\\\$27,\\\$0,8,4.*\.end\tv3" } } */
+/* { dg-final { scan-assembler "v4:.*ins\t\\\$27,\\\$0,8,5.*\.end\tv4" } } */
+/* { dg-final { scan-assembler "v5:.*ins\t\\\$27,\\\$0,8,6.*\.end\tv5" } } */
+/* { dg-final { scan-assembler "v6:.*ins\t\\\$27,\\\$0,8,7.*\.end\tv6" } } */
+/* { dg-final { scan-assembler "v7:.*ins\t\\\$27,\\\$0,8,8.*\.end\tv7" } } */
+
+/* { dg-final { scan-assembler-times "rdpgpr\t\\\$sp,\\\$sp" 1 } } */
+/* { dg-final { scan-assembler-not "s1:.*rdpgpr\t\\\$sp,\\\$sp.*\.end\ts1" } } */
+
+NOMIPS16 void __attribute__ ((interrupt("eic"))) e0 () { }
+NOMIPS16 void __attribute__ ((interrupt("vector=sw0"))) v0 () { }
+NOMIPS16 void __attribute__ ((interrupt("vector=sw1"))) v1 () { }
+NOMIPS16 void __attribute__ ((interrupt("vector=hw0"))) v2 () { }
+NOMIPS16 void __attribute__ ((interrupt("vector=hw1"))) v3 () { }
+NOMIPS16 void __attribute__ ((interrupt("vector=hw2"))) v4 () { }
+NOMIPS16 void __attribute__ ((interrupt("vector=hw3"))) v5 () { }
+NOMIPS16 void __attribute__ ((interrupt("vector=hw4"))) v6 () { }
+NOMIPS16 void __attribute__ ((interrupt("vector=hw5"))) v7 () { }
+
+NOMIPS16 void __attribute__ ((interrupt, use_shadow_register_set)) s0 () { }
+NOMIPS16 void __attribute__ ((interrupt, use_shadow_register_set("intstack"))) s1 () { }