1/*
2 * Copyright (c) 2009-2013 ETH Zurich.
3 * All rights reserved.
4 *
5 * This file is distributed under the terms in the attached LICENSE file.
6 * If you do not find this file, copies can be found by writing to:
7 * ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
8 */
9
10#include <kernel.h>
11#include <dispatch.h>
12#include <arm.h>
13#include <platform.h>
14/* XXX - not AArch64-compatible. */
15#include <cp15.h>
16#include <exceptions.h>
17#include <exec.h>
18#include <kputchar.h>
19#include <misc.h>
20#include <stdio.h>
21#include <wakeup.h>
22#include <irq.h>
23#include <gic.h>
24#include <systime.h>
25
26void handle_user_page_fault(lvaddr_t fault_address,
27                            arch_registers_state_t* save_area,
28                            struct dispatcher_shared_arm *disp)
29{
30    // XXX
31    // Set dcb_current->disabled correctly.  This should really be
32    // done in exceptions.S
33    // XXX
34    assert(dcb_current != NULL);
35    assert((struct dispatcher_shared_arm *)(dcb_current->disp) == disp);
36    if (dispatcher_is_disabled_ip((dispatcher_handle_t)disp, save_area->named.pc)) {
37        assert(save_area == dispatcher_get_trap_save_area((dispatcher_handle_t)disp));
38        dcb_current->disabled = true;
39    } else {
40        assert(save_area == dispatcher_get_enabled_save_area((dispatcher_handle_t)disp));
41        dcb_current->disabled = false;
42    }
43
44    lvaddr_t handler;
45    uintptr_t saved_pc = save_area->named.pc;
46
47    assert(dcb_current->disp_cte.cap.type == ObjType_Frame);
48
49    printk(LOG_WARN, "user page fault%s in '%.*s': addr %"PRIxLVADDR
50                      " IP %"PRIxPTR"\n",
51           dcb_current->disabled ? " WHILE DISABLED" : "", DISP_NAME_LEN,
52           disp->d.name, fault_address, saved_pc);
53
54    if (dcb_current->disabled) {
55        handler = disp->d.dispatcher_pagefault_disabled;
56        dcb_current->faults_taken++;
57    } else {
58        handler = disp->d.dispatcher_pagefault;
59    }
60
61    if (dcb_current->faults_taken > 2) {
62        printk(LOG_WARN, "handle_user_page_fault: too many faults, "
63               "making domain unrunnable\n");
64        dcb_current->faults_taken = 0; // just in case it gets restarted
65        scheduler_remove(dcb_current);
66        dispatch(schedule());
67    } else {
68        //
69        // Upcall to dispatcher
70        //
71        // NB System might be cleaner with a prototype
72        // dispatch context that has R0-R3 to be overwritten
73        // plus initial stack, thread, and gic registers. Could do
74        // a faster resume_for_upcall().
75        //
76        union registers_arm resume_area;
77
78        resume_area.named.cpsr = CPSR_F_MASK | ARM_MODE_USR;
79        resume_area.named.pc   = handler;
80        resume_area.named.r0   = disp->d.udisp;
81        resume_area.named.r1   = fault_address;
82        resume_area.named.r2   = 0;
83        resume_area.named.r3   = saved_pc;
84        resume_area.named.r9   = disp->got_base;
85
86        // SP is set by handler routine.
87
88        // Upcall user to save area
89        disp->d.disabled = true;
90        resume(&resume_area);
91    }
92}
93
94void handle_user_undef(lvaddr_t fault_address,
95                       arch_registers_state_t* save_area,
96                       struct dispatcher_shared_arm *disp)
97{
98    // XXX
99    // Set dcb_current->disabled correctly.  This should really be
100    // done in exceptions.S
101    // XXX
102    assert(dcb_current != NULL);
103    assert((struct dispatcher_shared_arm *)(dcb_current->disp) == disp);
104    if (dispatcher_is_disabled_ip((dispatcher_handle_t)disp, save_area->named.pc)) {
105        assert(save_area == dispatcher_get_trap_save_area((dispatcher_handle_t)disp));
106        dcb_current->disabled = true;
107    } else {
108        assert(save_area == dispatcher_get_enabled_save_area((dispatcher_handle_t)disp));
109        dcb_current->disabled = false;
110    }
111
112    union registers_arm resume_area;
113
114    assert(dcb_current->disp_cte.cap.type == ObjType_Frame);
115    printk(LOG_WARN, "user undef fault%s in '%.*s': IP %p\n",
116           dcb_current->disabled ? " WHILE DISABLED" : "", DISP_NAME_LEN,
117           disp->d.name, fault_address);
118
119    resume_area.named.cpsr = CPSR_F_MASK | ARM_MODE_USR;
120    resume_area.named.pc   = disp->d.dispatcher_trap;
121    resume_area.named.r0   = disp->d.udisp;
122    resume_area.named.r1   = ARM_EVECTOR_UNDEF;
123    resume_area.named.r2   = 0;
124    resume_area.named.r3   = fault_address;
125    resume_area.named.r9   = disp->got_base;
126
127    // Upcall user to save area
128    disp->d.disabled = true;
129    resume(&resume_area);
130}
131
132/* XXX - not 64-bit clean, not AArch64-compatible. */
133static int32_t bkpt_decode(lvaddr_t fault_address)
134{
135    int32_t bkpt_id = -1;
136    if ((fault_address & 3) == 0 && fault_address >= KERNEL_OFFSET) {
137        const uint32_t bkpt_mask = 0xfff000f0;
138        const uint32_t bkpt_isn  = 0xe1200070;
139
140        uintptr_t isn = *((uintptr_t*)fault_address);
141        if ((isn & bkpt_mask) == bkpt_isn) {
142            bkpt_id = (int32_t)((isn & 0xf) | ((isn & 0xfff00) >> 4));
143        }
144    }
145    return bkpt_id;
146}
147
148/* XXX - not 64-bit clean, not AArch64-compatible. */
149void fatal_kernel_fault(uint32_t evector, lvaddr_t address,
150                        arch_registers_state_t* save_area)
151{
152    int i;
153
154    /* Force the print spinlock release.  We're panicking now anyway, and if
155     * the kernel fault was *inside* kprintf, then we'll spin forever here,
156     * and never actually report the panic. */
157    /* XXX - implement lock_do_i_hold(), so we only do this if we're the
158     * holder. */
159    kprintf_end();
160
161    printk(LOG_PANIC, "\n");
162    printk(LOG_PANIC, "Kernel fault at %08"PRIxLVADDR
163                      " vector %08"PRIx32"\n\n", address, evector);
164    printk(LOG_PANIC, "Processor save_area at: %p\n", save_area);
165
166    for (i = 0; i < 16; i++) {
167        const char *extrainfo = "";
168
169        switch(i) {
170        case 13:
171            extrainfo = "\t(sp)";
172            break;
173
174        case 14:
175            extrainfo = "\t(lr)";
176            break;
177
178        case 15:
179            {
180                char str[128];
181                snprintf(str, 128, "\t(pc)\t%08"PRIx32,
182                         save_area->regs[R0_REG + i] -
183                         (uint32_t)&kernel_first_byte +
184                         0x100000);
185                extrainfo = str;
186            }
187            break;
188        }
189
190        printk(LOG_PANIC, "r%d\t%08"PRIx32"%s\n", i, save_area->regs[R0_REG + i], extrainfo);
191    }
192    printk(LOG_PANIC, "cpsr\t%08"PRIx32"\n", save_area->regs[CPSR_REG]);
193    printk(LOG_PANIC, "called from: #%08"PRIx32"\n",
194            (lvaddr_t)__builtin_return_address(0) -
195            (lvaddr_t)&kernel_first_byte + 0x100000);
196
197    switch (evector) {
198        case ARM_EVECTOR_UNDEF:
199            panic("Undefined instruction.\n");
200            break;
201
202        case ARM_EVECTOR_PABT: {
203            int ifsr = cp15_read_ifsr();
204            if (ifsr == 0) {
205                int bkpt = bkpt_decode(address);
206                if (bkpt >= 0) {
207                    panic("Breakpoint: %4x\n", bkpt);
208                }
209            }
210            panic("Prefetch abort: ifsr %08x\n", ifsr);
211        }
212
213        case ARM_EVECTOR_DABT: {
214            uint32_t dfsr = cp15_read_dfsr();
215
216            printf("\n");
217
218            printf("Data abort ");
219            if((dfsr >> 11) & 1) {
220                printf("on write\n");
221            } else {
222                printf("on read\n");
223            }
224
225            switch((dfsr & 0xf) | (dfsr & 0x400)) {
226            case 1:
227                printf("Alignment fault\n");
228                break;
229
230            case 4:
231                printf("Instruction cache-maintenance fault\n");
232                break;
233
234            case 5:
235                printf("Translation fault on section\n");
236                break;
237
238            case 6:
239                printf("Translation fault on page\n");
240                break;
241
242            case 8:
243                printf("Synchronous external abort\n");
244                break;
245
246            default:
247                printf("Unknown fault\n");
248                break;
249            }
250
251            panic("Data abort: dfsr %08"PRIx32"\n", dfsr);
252        }
253
254        case ARM_EVECTOR_IRQ: {
255            uint32_t irq = gic_get_active_irq();
256            panic("IRQ %"PRIu32" in the kernel", irq);
257        }
258
259        default:
260          panic("Caused by evector: %02"PRIx32, evector);
261          break;
262    }
263}
264
265void handle_fiq_kernel(arch_registers_state_t* save_area, uintptr_t fault_pc)
266{
267    panic("FIQ Interrupt in the kernel");
268}
269
270void handle_fiq(arch_registers_state_t* save_area,
271                uintptr_t fault_pc,
272                struct dispatcher_shared_generic *disp)
273{
274    panic("FIQ interrupt from user mode");
275}
276
277void handle_irq_kernel(arch_registers_state_t* save_area,
278                       uintptr_t fault_pc)
279{
280    /* In-kernel interrupts are bugs, except if we'd gone to sleep in
281     * wait_for_interrupt(), in which case there is no current dispatcher. */
282    if(waiting_for_interrupt) {
283        waiting_for_interrupt= 0;
284    }
285    else {
286        fatal_kernel_fault(ARM_EVECTOR_IRQ, fault_pc, save_area);
287    }
288
289    handle_irq(save_area, fault_pc, NULL);
290}
291
292void handle_irq(arch_registers_state_t* save_area,
293                uintptr_t fault_pc,
294                struct dispatcher_shared_arm *disp)
295{
296    // XXX
297    // Set dcb_current->disabled correctly.  This should really be
298    // done in exceptions.S
299    // XXX
300    if(dcb_current != NULL) {
301        assert((struct dispatcher_shared_arm *)(dcb_current->disp) == disp);
302        if (dispatcher_is_disabled_ip((dispatcher_handle_t)disp, fault_pc)) {
303            assert(save_area ==
304                   dispatcher_get_disabled_save_area(
305                       (dispatcher_handle_t)disp));
306            dcb_current->disabled = true;
307        } else {
308            assert(save_area ==
309                   dispatcher_get_enabled_save_area(
310                       (dispatcher_handle_t)disp));
311            dcb_current->disabled = false;
312        }
313    }
314
315    // Retrieve the current IRQ number
316    uint32_t irq = 0;
317    irq = gic_get_active_irq();
318    debug(SUBSYS_DISPATCH, "IRQ %"PRIu32" while %s\n", irq,
319          dcb_current->disabled ? "disabled": "enabled" );
320
321    // Offer it to the timer
322    if (timer_interrupt(irq)) {
323        // Timer interrupt, timer_interrupt() acks it at the timer.
324        wakeup_check(systime_now());
325        dispatch(schedule());
326    }
327    // this is the (still) unacknowledged startup interrupt sent by the BSP
328    // we just acknowledge it here
329    else if(irq == 1)
330    {
331        gic_ack_irq(irq);
332        dispatch(schedule());
333    }
334    else {
335        gic_ack_irq(irq);
336        send_user_interrupt(irq);
337        panic("Unhandled IRQ %"PRIu32"\n", irq);
338    }
339}
340