aboutsummaryrefslogtreecommitdiff
path: root/test7.c
blob: fdbd46c0d60dd5c53bab79d855ff3578cbd781c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
/* Test exception return integrity checks.
 */
#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 %x, 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 %x, 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 %x", sframe[7]);
        break;
    case 0xc:
        testDiag("In SVC handler, writing 0 to xPSR.exception in sframe");
        testDiag("old xPSR %x", 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 %x)", 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.");
}