/* Test exception return integrity checks. */ #include #include "armv7m.h" #include "testme.h" static unsigned testseq; static uint32_t saved_real_lr; #define SEQ() __atomic_add_fetch(&testseq, 1, __ATOMIC_RELAXED) #define CHECK_SEQ(N) testEqI(N, SEQ(), "SEQ " #N) static inline void test_equal(const char *m, uint32_t expect, uint32_t actual) { testEqI(expect, actual, "%s", m); } static void hard(void) { testDiag("Unexpected HardFault"); abort(); } /* Assembly language function body which wraps a call to the named * function() and uses the return value as the LR to use for return * from exception. We need this so we can be sure we've not got stray * extra junk on the stack when we attempt to do a return with a * particular magic LR value. * We also pass the function a pointer to the stack frame so it can * manipulate it if necessary. */ #define MAKE_WRAPPER(FN) \ extern void FN##_wrap(void); \ __asm__( \ ".thumb_func\n" \ #FN "_wrap:\n" \ "mov r0, lr\n" \ "mov r1, sp\n" \ "blx " #FN "\n" \ "mov lr, r0\n" \ "bx lr\n") uint32_t usage(uint32_t old_lr, uint32_t *sframe) { int test = SEQ(); uint32_t cfsr = in32(SCB(0xd28)); out32(SCB(0xd28), 0xff00); /* W1C */ switch (test) { case 2: testDiag("In UsageFault handler, checking we got INVPC fault"); test_equal("CFSR", 0x040000, cfsr); break; case 5: testDiag("In UsageFault handler, checking we got INVPC fault"); test_equal("CFSR", 0x040000, cfsr); testDiag("Ignoring bogus LR %"PRIx32", doing return to handler", old_lr); return 0xfffffff1; break; case 9: testDiag("In UsageFault handler, checking we got INVPC fault"); test_equal("CFSR", 0x040000, cfsr); testDiag("Undoing the change to xPSR"); sframe[7] &= ~0x1ff; break; case 0xd: testDiag("In UsageFault handler, checking we got INVPC fault"); test_equal("CFSR", 0x040000, cfsr); testDiag("Undoing the change to xPSR"); sframe[7] |= 0x10; /* IRQ0 active */ break; case 0x11: testDiag("In UsageFault handler, checking we got INVPC fault"); test_equal("CFSR", 0x040000, cfsr); testDiag("Ignoring bogus LR %"PRIx32", doing return to thread", old_lr); return 0xfffffff9; break; default: testFail("Fail: unexpected UsageFault (seq %x)", test); abort(); } return old_lr; } uint32_t svc(uint32_t old_lr, uint32_t *sframe) { int test = SEQ(); switch (test) { case 1: { /* Mark SVC as not active via the SHCSR */ uint32_t old_shcsr = in32(SCB(0xd24)); testDiag("In SVC handler, clearing SHCSR.SVCALLACT"); out32(SCB(0xd24), old_shcsr & ~0x80); break; } case 4: testDiag("In SVC handler, attempting return to thread mode"); return 0xfffffff9; case 8: testDiag("In SVC handler, writing non-0 to xPSR.exception in sframe"); sframe[7] |= 0x3; testDiag("New xPSR %"PRIx32, sframe[7]); break; case 0xc: testDiag("In SVC handler, writing 0 to xPSR.exception in sframe"); testDiag("old xPSR %"PRIx32, sframe[7]); sframe[7] &= ~0x1ff; break; case 0x10: testDiag("In SVC handler, attempting return with reserved value"); return 0xfffffff0; case 0x13: testDiag("In SVC handler (direct call from main with LR %"PRIx32")", old_lr); saved_real_lr = old_lr; return 0xfffffff9; default: testFail("Fail: unexpected SVC (seq %x)", test); abort(); } return old_lr; } static void irq0(void) { unsigned test = SEQ(); switch (test) { case 3: testDiag("In IRQ0 handler, triggering SVC"); SVC(42); CHECK_SEQ(6); testDiag("Back in IRQ0 handler"); break; case 0xb: testDiag("In IRQ0 handler, triggering SVC"); SVC(42); CHECK_SEQ(0xe); testDiag("Back in IRQ0 handler"); break; default: testFail("Fail: unexpected IRQ0 (seq %x)", test); abort(); } } uint32_t mem(uint32_t old_lr, uint32_t *sframe) { unsigned test = SEQ(); switch (test) { case 0x14: testDiag("In MemManage handler"); /* Fix up the return address in the stack frame (remembering * to clear its low bit since the stack frame doesn't want * an interworking address) */ sframe[6] = saved_real_lr & ~1; break; default: testFail("Fail: unexpected MemManage (seq %x)", test); abort(); } return old_lr; } MAKE_WRAPPER(svc); MAKE_WRAPPER(usage); MAKE_WRAPPER(mem); void main(void) { run_table.hard = hard; run_table.mem = mem_wrap; run_table.svc = svc_wrap; run_table.usage = usage_wrap; run_table.irq[0] = irq0; testInit(12); out32(SCB(0xd24), 0x70000); /* Enable Bus, Mem, and Usage Faults */ out32(SCB(0x100), 1); /* Enable IRQ0 */ out32(SCB(0x400), PRIO(2,0)); /* Set IRQ0 to priority 2 (below SVC) */ testDiag("Test: return from exception which is not active"); SVC(42); testDiag("Returned to main"); testDiag("Test: return to thread mode from nested exception"); out32(SCB(0x200), 1); /* pend IRQ0 */ testDiag("Returned to main"); CHECK_SEQ(7); testDiag("Test: return to thread mode with non-zero IPSR Exception field"); SVC(42); testDiag("Returned to main"); CHECK_SEQ(10); testDiag("Test: return to handler mode with zero IPSR exception field"); out32(SCB(0x200), 1); /* pend IRQ0 */ testDiag("Returned to main"); CHECK_SEQ(0xf); testDiag("Test: return from exception with reserved EXC_RETURN value"); SVC(42); testDiag("Returned to main"); CHECK_SEQ(0x12); #if 0 /* Attempt a jump to the magic address for an exception return. * This is not supposed to have magic behaviour in thread mode, * and on real hardware the result is an attempt to execute at * address 0xfffffff9 which is always NX (even if no MPU present) * and thus an IACCVIOL MemManage fault (which may escalate to * HardFault if MemManage is disabled in the SHCSR). * QEMU doesn't currently do this -- you will get the "tried to * execute outside RAM or ROM codepath" because we don't enforce * that NX handling. * The MemManage fault handler code path has been tested with a * hacked QEMU so it should work correctly. */ testDiag("Attempt exception return when in thread mode"); svc_wrap(); testDiag("Returned to main"); #else /* Write the SEQs that we would otherwise take in the SVC and MemManage * handlers. */ SEQ(); SEQ(); #endif CHECK_SEQ(0x15); testDiag("Done."); }