| /* |
| * Copyright (C) 2004-2006 Atmel Corporation |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| /* |
| * This file contains the low-level entry-points into the kernel, that is, |
| * exception handlers, debug trap handlers, interrupt handlers and the |
| * system call handler. |
| */ |
| #include <linux/errno.h> |
| |
| #include <asm/asm.h> |
| #include <asm/hardirq.h> |
| #include <asm/irq.h> |
| #include <asm/ocd.h> |
| #include <asm/page.h> |
| #include <asm/pgtable.h> |
| #include <asm/ptrace.h> |
| #include <asm/sysreg.h> |
| #include <asm/thread_info.h> |
| #include <asm/unistd.h> |
| |
| #ifdef CONFIG_PREEMPT |
| # define preempt_stop mask_interrupts |
| #else |
| # define preempt_stop |
| # define fault_resume_kernel fault_restore_all |
| #endif |
| |
| #define __MASK(x) ((1 << (x)) - 1) |
| #define IRQ_MASK ((__MASK(SOFTIRQ_BITS) << SOFTIRQ_SHIFT) | \ |
| (__MASK(HARDIRQ_BITS) << HARDIRQ_SHIFT)) |
| |
| .section .ex.text,"ax",@progbits |
| .align 2 |
| exception_vectors: |
| bral handle_critical |
| .align 2 |
| bral handle_critical |
| .align 2 |
| bral do_bus_error_write |
| .align 2 |
| bral do_bus_error_read |
| .align 2 |
| bral do_nmi_ll |
| .align 2 |
| bral handle_address_fault |
| .align 2 |
| bral handle_protection_fault |
| .align 2 |
| bral handle_debug |
| .align 2 |
| bral do_illegal_opcode_ll |
| .align 2 |
| bral do_illegal_opcode_ll |
| .align 2 |
| bral do_illegal_opcode_ll |
| .align 2 |
| bral do_fpe_ll |
| .align 2 |
| bral do_illegal_opcode_ll |
| .align 2 |
| bral handle_address_fault |
| .align 2 |
| bral handle_address_fault |
| .align 2 |
| bral handle_protection_fault |
| .align 2 |
| bral handle_protection_fault |
| .align 2 |
| bral do_dtlb_modified |
| |
| #define tlbmiss_save pushm r0-r3 |
| #define tlbmiss_restore popm r0-r3 |
| |
| .org 0x50 |
| .global itlb_miss |
| itlb_miss: |
| tlbmiss_save |
| rjmp tlb_miss_common |
| |
| .org 0x60 |
| dtlb_miss_read: |
| tlbmiss_save |
| rjmp tlb_miss_common |
| |
| .org 0x70 |
| dtlb_miss_write: |
| tlbmiss_save |
| |
| .global tlb_miss_common |
| .align 2 |
| tlb_miss_common: |
| mfsr r0, SYSREG_TLBEAR |
| mfsr r1, SYSREG_PTBR |
| |
| /* |
| * First level lookup: The PGD contains virtual pointers to |
| * the second-level page tables, but they may be NULL if not |
| * present. |
| */ |
| pgtbl_lookup: |
| lsr r2, r0, PGDIR_SHIFT |
| ld.w r3, r1[r2 << 2] |
| bfextu r1, r0, PAGE_SHIFT, PGDIR_SHIFT - PAGE_SHIFT |
| cp.w r3, 0 |
| breq page_table_not_present |
| |
| /* Second level lookup */ |
| ld.w r2, r3[r1 << 2] |
| mfsr r0, SYSREG_TLBARLO |
| bld r2, _PAGE_BIT_PRESENT |
| brcc page_not_present |
| |
| /* Mark the page as accessed */ |
| sbr r2, _PAGE_BIT_ACCESSED |
| st.w r3[r1 << 2], r2 |
| |
| /* Drop software flags */ |
| andl r2, _PAGE_FLAGS_HARDWARE_MASK & 0xffff |
| mtsr SYSREG_TLBELO, r2 |
| |
| /* Figure out which entry we want to replace */ |
| mfsr r1, SYSREG_MMUCR |
| clz r2, r0 |
| brcc 1f |
| mov r3, -1 /* All entries have been accessed, */ |
| mov r2, 0 /* so start at 0 */ |
| mtsr SYSREG_TLBARLO, r3 /* and reset TLBAR */ |
| |
| 1: bfins r1, r2, SYSREG_DRP_OFFSET, SYSREG_DRP_SIZE |
| mtsr SYSREG_MMUCR, r1 |
| tlbw |
| |
| tlbmiss_restore |
| rete |
| |
| /* The slow path of the TLB miss handler */ |
| .align 2 |
| page_table_not_present: |
| /* Do we need to synchronize with swapper_pg_dir? */ |
| bld r0, 31 |
| brcs sync_with_swapper_pg_dir |
| |
| page_not_present: |
| tlbmiss_restore |
| sub sp, 4 |
| stmts --sp, r0-lr |
| call save_full_context_ex |
| mfsr r12, SYSREG_ECR |
| mov r11, sp |
| call do_page_fault |
| rjmp ret_from_exception |
| |
| .align 2 |
| sync_with_swapper_pg_dir: |
| /* |
| * If swapper_pg_dir contains a non-NULL second-level page |
| * table pointer, copy it into the current PGD. If not, we |
| * must handle it as a full-blown page fault. |
| * |
| * Jumping back to pgtbl_lookup causes an unnecessary lookup, |
| * but it is guaranteed to be a cache hit, it won't happen |
| * very often, and we absolutely do not want to sacrifice any |
| * performance in the fast path in order to improve this. |
| */ |
| mov r1, lo(swapper_pg_dir) |
| orh r1, hi(swapper_pg_dir) |
| ld.w r3, r1[r2 << 2] |
| cp.w r3, 0 |
| breq page_not_present |
| mfsr r1, SYSREG_PTBR |
| st.w r1[r2 << 2], r3 |
| rjmp pgtbl_lookup |
| |
| /* |
| * We currently have two bytes left at this point until we |
| * crash into the system call handler... |
| * |
| * Don't worry, the assembler will let us know. |
| */ |
| |
| |
| /* --- System Call --- */ |
| |
| .org 0x100 |
| system_call: |
| #ifdef CONFIG_PREEMPT |
| mask_interrupts |
| #endif |
| pushm r12 /* r12_orig */ |
| stmts --sp, r0-lr |
| |
| mfsr r0, SYSREG_RAR_SUP |
| mfsr r1, SYSREG_RSR_SUP |
| #ifdef CONFIG_PREEMPT |
| unmask_interrupts |
| #endif |
| zero_fp |
| stm --sp, r0-r1 |
| |
| /* check for syscall tracing */ |
| get_thread_info r0 |
| ld.w r1, r0[TI_flags] |
| bld r1, TIF_SYSCALL_TRACE |
| brcs syscall_trace_enter |
| |
| syscall_trace_cont: |
| cp.w r8, NR_syscalls |
| brhs syscall_badsys |
| |
| lddpc lr, syscall_table_addr |
| ld.w lr, lr[r8 << 2] |
| mov r8, r5 /* 5th argument (6th is pushed by stub) */ |
| icall lr |
| |
| .global syscall_return |
| syscall_return: |
| get_thread_info r0 |
| mask_interrupts /* make sure we don't miss an interrupt |
| setting need_resched or sigpending |
| between sampling and the rets */ |
| |
| /* Store the return value so that the correct value is loaded below */ |
| stdsp sp[REG_R12], r12 |
| |
| ld.w r1, r0[TI_flags] |
| andl r1, _TIF_ALLWORK_MASK, COH |
| brne syscall_exit_work |
| |
| syscall_exit_cont: |
| popm r8-r9 |
| mtsr SYSREG_RAR_SUP, r8 |
| mtsr SYSREG_RSR_SUP, r9 |
| ldmts sp++, r0-lr |
| sub sp, -4 /* r12_orig */ |
| rets |
| |
| .align 2 |
| syscall_table_addr: |
| .long sys_call_table |
| |
| syscall_badsys: |
| mov r12, -ENOSYS |
| rjmp syscall_return |
| |
| .global ret_from_fork |
| ret_from_fork: |
| call schedule_tail |
| mov r12, 0 |
| rjmp syscall_return |
| |
| .global ret_from_kernel_thread |
| ret_from_kernel_thread: |
| call schedule_tail |
| mov r12, r0 |
| mov lr, r2 /* syscall_return */ |
| mov pc, r1 |
| |
| syscall_trace_enter: |
| pushm r8-r12 |
| call syscall_trace |
| popm r8-r12 |
| rjmp syscall_trace_cont |
| |
| syscall_exit_work: |
| bld r1, TIF_SYSCALL_TRACE |
| brcc 1f |
| unmask_interrupts |
| call syscall_trace |
| mask_interrupts |
| ld.w r1, r0[TI_flags] |
| |
| 1: bld r1, TIF_NEED_RESCHED |
| brcc 2f |
| unmask_interrupts |
| call schedule |
| mask_interrupts |
| ld.w r1, r0[TI_flags] |
| rjmp 1b |
| |
| 2: mov r2, _TIF_SIGPENDING | _TIF_NOTIFY_RESUME |
| tst r1, r2 |
| breq 3f |
| unmask_interrupts |
| mov r12, sp |
| mov r11, r0 |
| call do_notify_resume |
| mask_interrupts |
| ld.w r1, r0[TI_flags] |
| rjmp 1b |
| |
| 3: bld r1, TIF_BREAKPOINT |
| brcc syscall_exit_cont |
| rjmp enter_monitor_mode |
| |
| /* This function expects to find offending PC in SYSREG_RAR_EX */ |
| .type save_full_context_ex, @function |
| .align 2 |
| save_full_context_ex: |
| mfsr r11, SYSREG_RAR_EX |
| sub r9, pc, . - debug_trampoline |
| mfsr r8, SYSREG_RSR_EX |
| cp.w r9, r11 |
| breq 3f |
| mov r12, r8 |
| andh r8, (MODE_MASK >> 16), COH |
| brne 2f |
| |
| 1: pushm r11, r12 /* PC and SR */ |
| unmask_exceptions |
| ret r12 |
| |
| 2: sub r10, sp, -(FRAME_SIZE_FULL - REG_LR) |
| stdsp sp[4], r10 /* replace saved SP */ |
| rjmp 1b |
| |
| /* |
| * The debug handler set up a trampoline to make us |
| * automatically enter monitor mode upon return, but since |
| * we're saving the full context, we must assume that the |
| * exception handler might want to alter the return address |
| * and/or status register. So we need to restore the original |
| * context and enter monitor mode manually after the exception |
| * has been handled. |
| */ |
| 3: get_thread_info r8 |
| ld.w r11, r8[TI_rar_saved] |
| ld.w r12, r8[TI_rsr_saved] |
| rjmp 1b |
| .size save_full_context_ex, . - save_full_context_ex |
| |
| /* Low-level exception handlers */ |
| handle_critical: |
| /* |
| * AT32AP700x errata: |
| * |
| * After a Java stack overflow or underflow trap, any CPU |
| * memory access may cause erratic behavior. This will happen |
| * when the four least significant bits of the JOSP system |
| * register contains any value between 9 and 15 (inclusive). |
| * |
| * Possible workarounds: |
| * - Don't use the Java Extension Module |
| * - Ensure that the stack overflow and underflow trap |
| * handlers do not do any memory access or trigger any |
| * exceptions before the overflow/underflow condition is |
| * cleared (by incrementing or decrementing the JOSP) |
| * - Make sure that JOSP does not contain any problematic |
| * value before doing any exception or interrupt |
| * processing. |
| * - Set up a critical exception handler which writes a |
| * known-to-be-safe value, e.g. 4, to JOSP before doing |
| * any further processing. |
| * |
| * We'll use the last workaround for now since we cannot |
| * guarantee that user space processes don't use Java mode. |
| * Non-well-behaving userland will be terminated with extreme |
| * prejudice. |
| */ |
| #ifdef CONFIG_CPU_AT32AP700X |
| /* |
| * There's a chance we can't touch memory, so temporarily |
| * borrow PTBR to save the stack pointer while we fix things |
| * up... |
| */ |
| mtsr SYSREG_PTBR, sp |
| mov sp, 4 |
| mtsr SYSREG_JOSP, sp |
| mfsr sp, SYSREG_PTBR |
| sub pc, -2 |
| |
| /* Push most of pt_regs on stack. We'll do the rest later */ |
| sub sp, 4 |
| pushm r0-r12 |
| |
| /* PTBR mirrors current_thread_info()->task->active_mm->pgd */ |
| get_thread_info r0 |
| ld.w r1, r0[TI_task] |
| ld.w r2, r1[TSK_active_mm] |
| ld.w r3, r2[MM_pgd] |
| mtsr SYSREG_PTBR, r3 |
| #else |
| sub sp, 4 |
| pushm r0-r12 |
| #endif |
| sub r0, sp, -(14 * 4) |
| mov r1, lr |
| mfsr r2, SYSREG_RAR_EX |
| mfsr r3, SYSREG_RSR_EX |
| pushm r0-r3 |
| |
| mfsr r12, SYSREG_ECR |
| mov r11, sp |
| call do_critical_exception |
| |
| /* We should never get here... */ |
| bad_return: |
| sub r12, pc, (. - 1f) |
| lddpc pc, 2f |
| .align 2 |
| 1: .asciz "Return from critical exception!" |
| 2: .long panic |
| |
| .align 1 |
| do_bus_error_write: |
| sub sp, 4 |
| stmts --sp, r0-lr |
| call save_full_context_ex |
| mov r11, 1 |
| rjmp 1f |
| |
| do_bus_error_read: |
| sub sp, 4 |
| stmts --sp, r0-lr |
| call save_full_context_ex |
| mov r11, 0 |
| 1: mfsr r12, SYSREG_BEAR |
| mov r10, sp |
| call do_bus_error |
| rjmp ret_from_exception |
| |
| .align 1 |
| do_nmi_ll: |
| sub sp, 4 |
| stmts --sp, r0-lr |
| mfsr r9, SYSREG_RSR_NMI |
| mfsr r8, SYSREG_RAR_NMI |
| bfextu r0, r9, MODE_SHIFT, 3 |
| brne 2f |
| |
| 1: pushm r8, r9 /* PC and SR */ |
| mfsr r12, SYSREG_ECR |
| mov r11, sp |
| call do_nmi |
| popm r8-r9 |
| mtsr SYSREG_RAR_NMI, r8 |
| tst r0, r0 |
| mtsr SYSREG_RSR_NMI, r9 |
| brne 3f |
| |
| ldmts sp++, r0-lr |
| sub sp, -4 /* skip r12_orig */ |
| rete |
| |
| 2: sub r10, sp, -(FRAME_SIZE_FULL - REG_LR) |
| stdsp sp[4], r10 /* replace saved SP */ |
| rjmp 1b |
| |
| 3: popm lr |
| sub sp, -4 /* skip sp */ |
| popm r0-r12 |
| sub sp, -4 /* skip r12_orig */ |
| rete |
| |
| handle_address_fault: |
| sub sp, 4 |
| stmts --sp, r0-lr |
| call save_full_context_ex |
| mfsr r12, SYSREG_ECR |
| mov r11, sp |
| call do_address_exception |
| rjmp ret_from_exception |
| |
| handle_protection_fault: |
| sub sp, 4 |
| stmts --sp, r0-lr |
| call save_full_context_ex |
| mfsr r12, SYSREG_ECR |
| mov r11, sp |
| call do_page_fault |
| rjmp ret_from_exception |
| |
| .align 1 |
| do_illegal_opcode_ll: |
| sub sp, 4 |
| stmts --sp, r0-lr |
| call save_full_context_ex |
| mfsr r12, SYSREG_ECR |
| mov r11, sp |
| call do_illegal_opcode |
| rjmp ret_from_exception |
| |
| do_dtlb_modified: |
| pushm r0-r3 |
| mfsr r1, SYSREG_TLBEAR |
| mfsr r0, SYSREG_PTBR |
| lsr r2, r1, PGDIR_SHIFT |
| ld.w r0, r0[r2 << 2] |
| lsl r1, (32 - PGDIR_SHIFT) |
| lsr r1, (32 - PGDIR_SHIFT) + PAGE_SHIFT |
| |
| /* Translate to virtual address in P1 */ |
| andl r0, 0xf000 |
| sbr r0, 31 |
| add r2, r0, r1 << 2 |
| ld.w r3, r2[0] |
| sbr r3, _PAGE_BIT_DIRTY |
| mov r0, r3 |
| st.w r2[0], r3 |
| |
| /* The page table is up-to-date. Update the TLB entry as well */ |
| andl r0, lo(_PAGE_FLAGS_HARDWARE_MASK) |
| mtsr SYSREG_TLBELO, r0 |
| |
| /* MMUCR[DRP] is updated automatically, so let's go... */ |
| tlbw |
| |
| popm r0-r3 |
| rete |
| |
| do_fpe_ll: |
| sub sp, 4 |
| stmts --sp, r0-lr |
| call save_full_context_ex |
| unmask_interrupts |
| mov r12, 26 |
| mov r11, sp |
| call do_fpe |
| rjmp ret_from_exception |
| |
| ret_from_exception: |
| mask_interrupts |
| lddsp r4, sp[REG_SR] |
| |
| andh r4, (MODE_MASK >> 16), COH |
| brne fault_resume_kernel |
| |
| get_thread_info r0 |
| ld.w r1, r0[TI_flags] |
| andl r1, _TIF_WORK_MASK, COH |
| brne fault_exit_work |
| |
| fault_resume_user: |
| popm r8-r9 |
| mask_exceptions |
| mtsr SYSREG_RAR_EX, r8 |
| mtsr SYSREG_RSR_EX, r9 |
| ldmts sp++, r0-lr |
| sub sp, -4 |
| rete |
| |
| fault_resume_kernel: |
| #ifdef CONFIG_PREEMPT |
| get_thread_info r0 |
| ld.w r2, r0[TI_preempt_count] |
| cp.w r2, 0 |
| brne 1f |
| ld.w r1, r0[TI_flags] |
| bld r1, TIF_NEED_RESCHED |
| brcc 1f |
| lddsp r4, sp[REG_SR] |
| bld r4, SYSREG_GM_OFFSET |
| brcs 1f |
| call preempt_schedule_irq |
| 1: |
| #endif |
| |
| popm r8-r9 |
| mask_exceptions |
| mfsr r1, SYSREG_SR |
| mtsr SYSREG_RAR_EX, r8 |
| mtsr SYSREG_RSR_EX, r9 |
| popm lr |
| sub sp, -4 /* ignore SP */ |
| popm r0-r12 |
| sub sp, -4 /* ignore r12_orig */ |
| rete |
| |
| irq_exit_work: |
| /* Switch to exception mode so that we can share the same code. */ |
| mfsr r8, SYSREG_SR |
| cbr r8, SYSREG_M0_OFFSET |
| orh r8, hi(SYSREG_BIT(M1) | SYSREG_BIT(M2)) |
| mtsr SYSREG_SR, r8 |
| sub pc, -2 |
| get_thread_info r0 |
| ld.w r1, r0[TI_flags] |
| |
| fault_exit_work: |
| bld r1, TIF_NEED_RESCHED |
| brcc 1f |
| unmask_interrupts |
| call schedule |
| mask_interrupts |
| ld.w r1, r0[TI_flags] |
| rjmp fault_exit_work |
| |
| 1: mov r2, _TIF_SIGPENDING | _TIF_NOTIFY_RESUME |
| tst r1, r2 |
| breq 2f |
| unmask_interrupts |
| mov r12, sp |
| mov r11, r0 |
| call do_notify_resume |
| mask_interrupts |
| ld.w r1, r0[TI_flags] |
| rjmp fault_exit_work |
| |
| 2: bld r1, TIF_BREAKPOINT |
| brcc fault_resume_user |
| rjmp enter_monitor_mode |
| |
| .section .kprobes.text, "ax", @progbits |
| .type handle_debug, @function |
| handle_debug: |
| sub sp, 4 /* r12_orig */ |
| stmts --sp, r0-lr |
| mfsr r8, SYSREG_RAR_DBG |
| mfsr r9, SYSREG_RSR_DBG |
| unmask_exceptions |
| pushm r8-r9 |
| bfextu r9, r9, SYSREG_MODE_OFFSET, SYSREG_MODE_SIZE |
| brne debug_fixup_regs |
| |
| .Ldebug_fixup_cont: |
| #ifdef CONFIG_TRACE_IRQFLAGS |
| call trace_hardirqs_off |
| #endif |
| mov r12, sp |
| call do_debug |
| mov sp, r12 |
| |
| lddsp r2, sp[REG_SR] |
| bfextu r3, r2, SYSREG_MODE_OFFSET, SYSREG_MODE_SIZE |
| brne debug_resume_kernel |
| |
| get_thread_info r0 |
| ld.w r1, r0[TI_flags] |
| mov r2, _TIF_DBGWORK_MASK |
| tst r1, r2 |
| brne debug_exit_work |
| |
| bld r1, TIF_SINGLE_STEP |
| brcc 1f |
| mfdr r4, OCD_DC |
| sbr r4, OCD_DC_SS_BIT |
| mtdr OCD_DC, r4 |
| |
| 1: popm r10,r11 |
| mask_exceptions |
| mtsr SYSREG_RSR_DBG, r11 |
| mtsr SYSREG_RAR_DBG, r10 |
| #ifdef CONFIG_TRACE_IRQFLAGS |
| call trace_hardirqs_on |
| 1: |
| #endif |
| ldmts sp++, r0-lr |
| sub sp, -4 |
| retd |
| .size handle_debug, . - handle_debug |
| |
| /* Mode of the trapped context is in r9 */ |
| .type debug_fixup_regs, @function |
| debug_fixup_regs: |
| mfsr r8, SYSREG_SR |
| mov r10, r8 |
| bfins r8, r9, SYSREG_MODE_OFFSET, SYSREG_MODE_SIZE |
| mtsr SYSREG_SR, r8 |
| sub pc, -2 |
| stdsp sp[REG_LR], lr |
| mtsr SYSREG_SR, r10 |
| sub pc, -2 |
| sub r8, sp, -FRAME_SIZE_FULL |
| stdsp sp[REG_SP], r8 |
| rjmp .Ldebug_fixup_cont |
| .size debug_fixup_regs, . - debug_fixup_regs |
| |
| .type debug_resume_kernel, @function |
| debug_resume_kernel: |
| mask_exceptions |
| popm r10, r11 |
| mtsr SYSREG_RAR_DBG, r10 |
| mtsr SYSREG_RSR_DBG, r11 |
| #ifdef CONFIG_TRACE_IRQFLAGS |
| bld r11, SYSREG_GM_OFFSET |
| brcc 1f |
| call trace_hardirqs_on |
| 1: |
| #endif |
| mfsr r2, SYSREG_SR |
| mov r1, r2 |
| bfins r2, r3, SYSREG_MODE_OFFSET, SYSREG_MODE_SIZE |
| mtsr SYSREG_SR, r2 |
| sub pc, -2 |
| popm lr |
| mtsr SYSREG_SR, r1 |
| sub pc, -2 |
| sub sp, -4 /* skip SP */ |
| popm r0-r12 |
| sub sp, -4 |
| retd |
| .size debug_resume_kernel, . - debug_resume_kernel |
| |
| .type debug_exit_work, @function |
| debug_exit_work: |
| /* |
| * We must return from Monitor Mode using a retd, and we must |
| * not schedule since that involves the D bit in SR getting |
| * cleared by something other than the debug hardware. This |
| * may cause undefined behaviour according to the Architecture |
| * manual. |
| * |
| * So we fix up the return address and status and return to a |
| * stub below in Exception mode. From there, we can follow the |
| * normal exception return path. |
| * |
| * The real return address and status registers are stored on |
| * the stack in the way the exception return path understands, |
| * so no need to fix anything up there. |
| */ |
| sub r8, pc, . - fault_exit_work |
| mtsr SYSREG_RAR_DBG, r8 |
| mov r9, 0 |
| orh r9, hi(SR_EM | SR_GM | MODE_EXCEPTION) |
| mtsr SYSREG_RSR_DBG, r9 |
| sub pc, -2 |
| retd |
| .size debug_exit_work, . - debug_exit_work |
| |
| .set rsr_int0, SYSREG_RSR_INT0 |
| .set rsr_int1, SYSREG_RSR_INT1 |
| .set rsr_int2, SYSREG_RSR_INT2 |
| .set rsr_int3, SYSREG_RSR_INT3 |
| .set rar_int0, SYSREG_RAR_INT0 |
| .set rar_int1, SYSREG_RAR_INT1 |
| .set rar_int2, SYSREG_RAR_INT2 |
| .set rar_int3, SYSREG_RAR_INT3 |
| |
| .macro IRQ_LEVEL level |
| .type irq_level\level, @function |
| irq_level\level: |
| sub sp, 4 /* r12_orig */ |
| stmts --sp,r0-lr |
| mfsr r8, rar_int\level |
| mfsr r9, rsr_int\level |
| |
| #ifdef CONFIG_PREEMPT |
| sub r11, pc, (. - system_call) |
| cp.w r11, r8 |
| breq 4f |
| #endif |
| |
| pushm r8-r9 |
| |
| mov r11, sp |
| mov r12, \level |
| |
| call do_IRQ |
| |
| lddsp r4, sp[REG_SR] |
| bfextu r4, r4, SYSREG_M0_OFFSET, 3 |
| cp.w r4, MODE_SUPERVISOR >> SYSREG_M0_OFFSET |
| breq 2f |
| cp.w r4, MODE_USER >> SYSREG_M0_OFFSET |
| #ifdef CONFIG_PREEMPT |
| brne 3f |
| #else |
| brne 1f |
| #endif |
| |
| get_thread_info r0 |
| ld.w r1, r0[TI_flags] |
| andl r1, _TIF_WORK_MASK, COH |
| brne irq_exit_work |
| |
| 1: |
| #ifdef CONFIG_TRACE_IRQFLAGS |
| call trace_hardirqs_on |
| #endif |
| popm r8-r9 |
| mtsr rar_int\level, r8 |
| mtsr rsr_int\level, r9 |
| ldmts sp++,r0-lr |
| sub sp, -4 /* ignore r12_orig */ |
| rete |
| |
| #ifdef CONFIG_PREEMPT |
| 4: mask_interrupts |
| mfsr r8, rsr_int\level |
| sbr r8, 16 |
| mtsr rsr_int\level, r8 |
| ldmts sp++, r0-lr |
| sub sp, -4 /* ignore r12_orig */ |
| rete |
| #endif |
| |
| 2: get_thread_info r0 |
| ld.w r1, r0[TI_flags] |
| bld r1, TIF_CPU_GOING_TO_SLEEP |
| #ifdef CONFIG_PREEMPT |
| brcc 3f |
| #else |
| brcc 1b |
| #endif |
| sub r1, pc, . - cpu_idle_skip_sleep |
| stdsp sp[REG_PC], r1 |
| #ifdef CONFIG_PREEMPT |
| 3: get_thread_info r0 |
| ld.w r2, r0[TI_preempt_count] |
| cp.w r2, 0 |
| brne 1b |
| ld.w r1, r0[TI_flags] |
| bld r1, TIF_NEED_RESCHED |
| brcc 1b |
| lddsp r4, sp[REG_SR] |
| bld r4, SYSREG_GM_OFFSET |
| brcs 1b |
| call preempt_schedule_irq |
| #endif |
| rjmp 1b |
| .endm |
| |
| .section .irq.text,"ax",@progbits |
| |
| .global irq_level0 |
| .global irq_level1 |
| .global irq_level2 |
| .global irq_level3 |
| IRQ_LEVEL 0 |
| IRQ_LEVEL 1 |
| IRQ_LEVEL 2 |
| IRQ_LEVEL 3 |
| |
| .section .kprobes.text, "ax", @progbits |
| .type enter_monitor_mode, @function |
| enter_monitor_mode: |
| /* |
| * We need to enter monitor mode to do a single step. The |
| * monitor code will alter the return address so that we |
| * return directly to the user instead of returning here. |
| */ |
| breakpoint |
| rjmp breakpoint_failed |
| |
| .size enter_monitor_mode, . - enter_monitor_mode |
| |
| .type debug_trampoline, @function |
| .global debug_trampoline |
| debug_trampoline: |
| /* |
| * Save the registers on the stack so that the monitor code |
| * can find them easily. |
| */ |
| sub sp, 4 /* r12_orig */ |
| stmts --sp, r0-lr |
| get_thread_info r0 |
| ld.w r8, r0[TI_rar_saved] |
| ld.w r9, r0[TI_rsr_saved] |
| pushm r8-r9 |
| |
| /* |
| * The monitor code will alter the return address so we don't |
| * return here. |
| */ |
| breakpoint |
| rjmp breakpoint_failed |
| .size debug_trampoline, . - debug_trampoline |
| |
| .type breakpoint_failed, @function |
| breakpoint_failed: |
| /* |
| * Something went wrong. Perhaps the debug hardware isn't |
| * enabled? |
| */ |
| lda.w r12, msg_breakpoint_failed |
| mov r11, sp |
| mov r10, 9 /* SIGKILL */ |
| call die |
| 1: rjmp 1b |
| |
| msg_breakpoint_failed: |
| .asciz "Failed to enter Debug Mode" |