# SPIM S20 MIPS simulator. # The default exception handler for spim. # # Copyright (c) 1990-2010, James R. Larus. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, # are permitted provided that the following conditions are met: # # Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation and/or # other materials provided with the distribution. # # Neither the name of the James R. Larus nor the names of its contributors may be # used to endorse or promote products derived from this software without specific # prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE # GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # Define the exception handling code. This must go first! .kdata __m1_: .asciiz " Exception " __m2_: .asciiz " occurred and ignored\n" __e0_: .asciiz " [Interrupt] " __e1_: .asciiz " [TLB]" __e2_: .asciiz " [TLB]" __e3_: .asciiz " [TLB]" __e4_: .asciiz " [Address error in inst/data fetch] " __e5_: .asciiz " [Address error in store] " __e6_: .asciiz " [Bad instruction address] " __e7_: .asciiz " [Bad data address] " __e8_: .asciiz " [Error in syscall] " __e9_: .asciiz " [Breakpoint] " __e10_: .asciiz " [Reserved instruction] " __e11_: .asciiz "" __e12_: .asciiz " [Arithmetic overflow] " __e13_: .asciiz " [Trap] " __e14_: .asciiz "" __e15_: .asciiz " [Floating point] " __e16_: .asciiz "" __e17_: .asciiz "" __e18_: .asciiz " [Coproc 2]" __e19_: .asciiz "" __e20_: .asciiz "" __e21_: .asciiz "" __e22_: .asciiz " [MDMX]" __e23_: .asciiz " [Watch]" __e24_: .asciiz " [Machine check]" __e25_: .asciiz "" __e26_: .asciiz "" __e27_: .asciiz "" __e28_: .asciiz "" __e29_: .asciiz "" __e30_: .asciiz " [Cache]" __e31_: .asciiz "" __excp: .word __e0_, __e1_, __e2_, __e3_, __e4_, __e5_, __e6_, __e7_, __e8_, __e9_ .word __e10_, __e11_, __e12_, __e13_, __e14_, __e15_, __e16_, __e17_, __e18_, .word __e19_, __e20_, __e21_, __e22_, __e23_, __e24_, __e25_, __e26_, __e27_, .word __e28_, __e29_, __e30_, __e31_ # Space in memory for saved registers. __v0: .word 0 __a0: .word 0 __at: .word 0 # The exception handler must reside in memory at addres 0x80000180. This # value is hardwired into the MIPS processor. .ktext 0x80000180 # This is the exception handler code that the processor runs when # an exception occurs. Right now, it just prints the exception number # and returns. # # Because we are running in the kernel, we can use $k0/$k1 without # saving their old values. # Normally, we shouldn't touch $at. This directive # tells the assembler not to complain if we do. .set noat # We have to save any registers we use, but we haven't # set up a kernel stack. Dump them into kernel memory. # # We need $at because we want to use pseudo-instructions. # The regular exception handling code uses $a0 and $v0 to # make system calls, so we need to save those too. # # If you want to use any registers other than those, allocate # space for them in the kernel data segment, and then save # them here. Don't forget to restore them before you # return to user space. sw $at, __at .set at sw $v0, __v0 sw $a0, __a0 # Get the exception number from cause[6:2] mfc0 $k0, $13 # Cause register is reg. 13 srl $a0, $k0, 2 # Shift exception number to bits $a0[4:0] andi $a0, $a0, 0x0000001f # Clear bits $a0[31:5] # If the exception number is zero, this is an interrupt. # Jump to the interrupt handler. Otherwise, it's a non-interrupt # exception. Let the handler deal with it. beq $a0, $zero, interrupt_handler # Print information about exception. # li $v0 4 # syscall 4 (print_str) la $a0 __m1_ syscall # Get the exception number from $k0 again, since the syscall # destroyed $a0. li $v0, 1 # syscall 1 (print_int) srl $a0, $k0, 2 andi $a0, $a0, 0x0000001f syscall li $v0, 4 # syscall 4 (print_str) andi $a0, $k0, 0x3c lw $a0, __excp($a0) nop syscall # If exception was a bad value in $pc (for example, not word # aligned) we need to do some special checks. 0x18 == exception 6: bus # error on instruction fetch. bne $k0, 0x18, ok_pc nop mfc0 $a0, $14 # Grab $epc - address of excepting instruction andi $a0, $a0, 0x3 # Is EPC word-aligned? beq $a0, 0, ok_pc li $v0, 10 # $pc is hopelessly fouled up. Whoever wrote the syscall # program we are executing is on crack or something. # Give up: syscall 10 = exit. ok_pc: li $v0, 4 # syscall 4 (print_str) la $a0, __m2_ syscall srl $a0, $k0, 2 # Extract ExcCode Field yet again andi $a0, $a0, 0x1f # At this point, we've basically done all we can # to service a non-interrupt exception. Clean up # and go to the instruction following the excepting one. bne $a0, 0, ret_from_non_interrupt_exception # 0 means exception was an interrupt # Interrupt-specific code goes here! # # Don't skip the instruction at EPC when returning from an interrupt. # If the exception was an interrupt, it was detected *before* the instruction # began to execute. interrupt_handler: # j ret_from_interrupt ret_from_non_interrupt_exception: # Return from (non-interrupt) exception. We have to skip the instruction # that caused the exception, to avoid getting into an infinite loop # of exceptions. # # However, if the exception was an interrupt, it was detected before # $pc was incremented. When we return, just go to $epc. mfc0 $k0, $14 # Grab address of excepting instruction from # co-processor reg. 14. addiu $k0, $k0, 4 # Increment it mtc0 $k0, $14 # Put incremented address back in reg. 14 # When we return from the exception handler, # the hardware will put this address in the $pc. # Restore registers, reset co-processor, return from exception. We perform # this code on every type of exception. ret_from_interrupt: lw $v0, __v0 # Restore registers lw $a0, __a0 .set noat lw $at, __at .set at mtc0 $0, $13 # Clear Cause register # When the exception/interrupt happened, the hardware # turned off interrupts in the status register. Re-enable them. mfc0 $k0, $12 ori $k0, 0x1 mtc0 $k0, $12 # Return from exception: $pc = $epc eret