Cortex-M3 vector_catch testing support
authorDavid Brownell <dbrownell@users.sourceforge.net>
Wed, 20 Jan 2010 19:07:42 +0000 (11:07 -0800)
committerDavid Brownell <dbrownell@users.sourceforge.net>
Wed, 20 Jan 2010 19:07:42 +0000 (11:07 -0800)
The "cm3-ftest.cfg" can be used to verify that OpenOCD handles
certain faults correctly:

 - Test #1: it ignores faults that it wasn't told to catch
 - Test #2: if vector_catch is told to catch, it catches

The "fault.c" generates ASM code to trigger faults, while the
config script loads and runs pre-compiled code.

This covers most, but not all, of the vector_catch options.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
testing/examples/cortex/cm3-ftest.cfg [new file with mode: 0644]
testing/examples/cortex/fault.c [new file with mode: 0644]

diff --git a/testing/examples/cortex/cm3-ftest.cfg b/testing/examples/cortex/cm3-ftest.cfg
new file mode 100644 (file)
index 0000000..2dae249
--- /dev/null
@@ -0,0 +1,143 @@
+#
+# For each named Cortex-M3 vector_catch flag VECTOR ...
+#              bus_err         state_err
+#              chk_err         nocp_err
+#              mm_err          reset
+#
+# BUT NYET hard_err, int_err (their test cases don't yet work) ...
+#
+# Do the following:
+#
+#  - Test #1:  verify that OpenOCD ignores exceptions by default
+#     + l_VECTOR (loads testcase to RAM)
+#     + fault triggers loop-to-self exception "handler"
+#     + "halt"
+#     + observe fault "handling" -- loop-to-self from load_and_run (below)
+#
+#  - Test #2:  verify that "vector_catch" makes OpenOCD stops ignoring them
+#     + cortex_m3 vector_catch none
+#     + cortex_m3 vector_catch VECTOR
+#     + l_VECTOR (loads testcase to RAM)
+#     + fault triggers vector catch hardware
+#     + observe OpenOCD entering debug state with no assistance
+#
+# NOTE "reset" includes the NVIC, so that test case gets its reset vector
+# from the flash, not from the vector table set up here.  Which means that
+# for that vector_catch option, the Test #1 (above) "observe" step won't
+# use the SRAM address.
+#
+
+# we can fully automate test #2
+proc vector_test {tag} {
+       halt
+       # REVISIT -- annoying, we'd like to scrap vector_catch output
+       cortex_m3 vector_catch none
+       cortex_m3 vector_catch $tag
+       eval "l_$tag"
+}
+
+#
+# Load and start one vector_catch test case.
+#
+# name -- tag for the vector_catch flag being tested
+# halfwords -- array of instructions (some wide, some narrow)
+# n_instr -- how many instructions are in $halfwords
+#
+proc load_and_run { name halfwords n_instr } {
+       reset halt
+
+       # Load code at beginning of SRAM.
+       echo "# code to trigger $name vector"
+       set addr 0x20000000
+
+       # ocd_array2mem should be faster, though we'd need to
+       # compute the resulting $addr ourselves
+       foreach opcode $halfwords {
+               mwh $addr $opcode
+               incr addr 2
+       }
+
+       # create default loop-to-self at $addr ... it serves as
+       # (a) "main loop" on error
+       # (b) handler for all exceptions that get triggered
+       mwh $addr 0xe7fe
+
+       # disassemble, as sanity check and what's-happening trace
+       cortex_m3 disassemble 0x20000000 [expr 1 + $n_instr ]
+
+       # Assume that block of code is at most 16 halfwords long.
+       # Create a basic table of loop-to-self exception handlers.
+       mww 0x20000020 $addr 16
+       # Store its address in VTOR
+       mww 0xe000ed08 0x20000020
+       # Use SHCSR to ensure nothing escalates to a HardFault
+       mww 0xe000ed24 0x00070000
+
+       # now start, trigering the $name vector catch logic
+       resume 0x20000000
+}
+
+#proc l_hard_err {} {
+#      IMPLEMENT ME
+#      FORCED -- escalate something to HardFault
+#}
+
+#proc l_int_err {} {
+#      IMPLEMENT ME
+#      STKERR -- exception stack BusFault
+#}
+
+# BusFault, escalates to HardFault
+proc l_bus_err {} {
+       # PRECISERR -- assume less than 512 MBytes of SRAM
+       load_and_run bus_err {
+               0xf06f 0x4040
+               0x7800
+       } 2
+}
+
+# UsageFault, escalates to HardFault
+proc l_state_err {} {
+       # UNDEFINSTR -- issue architecturally undefined instruction
+       load_and_run state_err {
+               0xde00
+       } 1
+}
+
+# UsageFault, escalates to HardFault
+proc l_chk_err {} {
+       # UNALIGNED -- LDM through unaligned pointer
+       load_and_run chk_err {
+               0xf04f 0x0001
+               0xe890 0x0006
+       } 2
+}
+
+# UsageFault, escalates to HardFault
+proc l_nocp_err {} {
+       # NOCP -- issue cp14 DCC instruction
+       load_and_run nocp_err {
+               0xee10 0x0e15
+       } 1
+}
+
+# MemManage, escalates to HardFault
+proc l_mm_err {} {
+       # IACCVIOL -- instruction fetch from an XN region
+       load_and_run mm_err {
+               0xf04f 0x4060
+               0x4687
+       } 2
+}
+
+proc l_reset {} {
+       # issue SYSRESETREQ via AIRCR
+       load_and_run reset {
+               0xf04f 0x0104
+               0xf2c0 0x51fa
+               0xf44f 0x406d
+               0xf100 0x000c
+               0xf2ce 0x0000
+               0x6001
+       } 6
+}
diff --git a/testing/examples/cortex/fault.c b/testing/examples/cortex/fault.c
new file mode 100644 (file)
index 0000000..9a5fe19
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * COMPILE:  arm-none-eabi-gcc -mthumb -march=armv7-m ...
+ *     ... plus, provide at least a default exception vector table.
+ *
+ * RUN:  this is best run from SRAM.  It starts at main() then triggers
+ * a fault before more than a handful of instructions have executed.
+ * Run each test case in two modes:
+ *
+ * (1) Faults caught on the Cortex-M3.  Default handlers are usually
+ *     loop-to-self NOPs, so a debugger won't notice faults until they
+ *     halt the core and examine xSPR and other registers.
+ *
+ *     To verify the fault triggered, issue "halt" from OpenOCD; you
+ *     should be told about the fault and (some of) its details.
+ *     Then it's time to run the next test.
+ *
+ *     NOTE however that "reset" will restart everything; verify that
+ *     case by observing your reset handler doing its normal work.
+ *
+ * (2) Faults intercepted by OpenOCD "vector_catch ..." commands.
+ *
+ *     OpenOCD should tell you about the fault, and show the same
+ *     details, without your "halt" command.
+ *
+ * Someday, a fancy version of this code could provide a vector table and
+ * fault handlers which use semihosting (when that works on Cortex-M3) to
+ * report what happened, again without needing a "halt" command.
+ */
+
+
+/* These symbols match the OpenOCD "cortex_m3 vector_catch" bit names. */
+enum vc_case {
+       hard_err,
+       int_err,
+       bus_err,
+       state_err,
+       chk_err,
+       nocp_err,
+       mm_err,
+       reset,
+};
+
+/* REVISIT come up with a way to avoid recompiling, maybe:
+ *  - write it in RAM before starting
+ *  - compiled-in BKPT, manual patch of r0, then resume
+ *  - ...
+ */
+
+#ifndef VC_ID
+#warning "no VC_ID ... using reset"
+#define VC_ID reset
+#endif
+
+int main(void) __attribute__ ((externally_visible, noreturn));
+
+/*
+ * Trigger various Cortex-M3 faults to verify that OpenOCD behaves OK
+ * in terms of its vector_catch handling.
+ *
+ * Fault handling should be left entirely up to the application code
+ * UNLESS a "vector_catch" command tells OpenOCD to intercept a fault.
+ *
+ * See ARMv7-M architecure spec table B1-9 for the list of faults and
+ * their mappings to the vector catch bits.
+ */
+int main(void)
+{
+       /* One test case for each vector catch bit.  We're not doing
+        * hardware testing; so it doesn't matter when some DEMCR bits
+        * could apply in multiple ways.
+        */
+       switch (VC_ID) {
+
+       /* "cortex_m3 vector_catch hard_err" */
+       case hard_err:
+               /* FORCED - Fault escalation */
+
+               /* FIXME code this */
+               break;
+
+       /* "cortex_m3 vector_catch int_err" */
+       case int_err:
+               /* STKERR -- Exception stack BusFault */
+
+               /* FIXME code this */
+               break;
+
+       /* "cortex_m3 vector_catch bus_err" */
+       case bus_err:
+               /* PRECISERR -- precise data bus read
+                * Here we assume a Cortex-M3 with 512 MBytes SRAM is very
+                * unlikely, so the last SRAM byte isn't a valid address.
+                */
+               __asm__ volatile(
+                       "mov r0, #0x3fffffff\n"
+                       "ldrb r0, [r0]\n"
+                       );
+               break;
+
+       /* "cortex_m3 vector_catch state_err" */
+       case state_err:
+               /* UNDEFINSTR -- architectural undefined instruction */
+               __asm__ volatile(".hword 0xde00");
+               break;
+
+       /* "cortex_m3 vector_catch chk_err" */
+       case chk_err:
+               /* UNALIGNED ldm */
+               __asm__ volatile(
+                       "mov r0, #1\n"
+                       "ldm r0, {r1, r2}\n"
+                       );
+               break;
+
+       /* "cortex_m3 vector_catch nocp_err" */
+       case nocp_err:
+               /* NOCP ... Cortex-M3 has no coprocessors (like CP14 DCC),
+                * but these instructions are allowed by ARMv7-M.
+                */
+               __asm__ volatile("mrc p14, 0, r0, c0, c5, 0");
+               break;
+
+       /* "cortex_m3 vector_catch mm_err" */
+       case mm_err:
+               /* IACCVIOL -- instruction fetch from an XN region */
+               __asm__ volatile(
+                       "mov r0, #0xe0000000\n"
+                       "mov pc, r0\n"
+                       );
+               break;
+
+       /* "cortex_m3 vector_catch reset" */
+       case reset:
+               __asm__ volatile(
+                       /* r1 = SYSRESETREQ */
+                       "mov r1, #0x0004\n"
+                       /* r1 |= VECTKEY */
+                       "movt r1, #0x05fa\n"
+                       /* r0 = &AIRCR */
+                       "mov r0, #0xed00\n"
+                       "add r0, #0xc\n"
+                       "movt r0, #0xe000\n"
+                       /* AIRCR = ... */
+                       "str r1, [r0, #0]\n"
+                       );
+               break;
+       }
+
+       /* don't return */
+       while (1)
+               continue;
+}