# SPIM S20 MIPS simulator. # A reentrant traphandler for SPIM. # This uses interrupts and memory mapped I/O # to handle putc and getc syscalls. # Copyright (C) 1992 Scott Kempf, socttk@cs.wisc.edu # # SPIM is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 1, or (at your option) any # later version. # # SPIM is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. # # You should have received a copy of the GNU General Public License # along with GNU CC; see the file COPYING. # # Assign addresses to labels .eq KeyboardData 0xbfff0000 .eq KeyboardStatus 0xbfff0004 .eq DisplayData 0xbfff0008 .eq DisplayStatus 0xbfff000c .eq KeyboardData2 0xbfff0010 .eq KeyboardStatus2 0xbfff0014 .eq DisplayData2 0xbfff0018 .eq DisplayStatus2 0xbfff001c .eq ClockStatus 0xbfff0020 .kdata flag: .word 0 # flag is set to one when executing in the kernel # so that if the kernel is re-entered and the # exception return address is lost an error is printed. # This flag will help to notice problems. # It will not prevent them. .align 2 tmpr1: .word 0 # Temporary storage for register 1 tmpr2: .word 0 # Temporary storage for register 2 tmpr4: .word 0 # Temporary storage for register 4 tmpr5: .word 0 # Temporary storage for register 5 tmpr6: .word 0 # Temporary storage for register 6 tmpr31: .word 0 # Temporary storage for register 31 .ktext .space 0x80 # Skip space so kernel starts at 0x80000080 .set noat add $k1, $1, $0 # Save register 1 .set at lw $k0, flag # Test for illegal reentry. bnez $k0, BadReentry OK: li $k0, 1 # Set flag to (help) prevent illegal reentry. sw $k0, flag sw $k1, tmpr1 # Save registers sw $2, tmpr2 sw $4, tmpr4 sw $5, tmpr5 sw $6, tmpr6 sw $31, tmpr31 mfc0 $k0, $13 # Get the Cause register and $k0, $k0, 0x3c # Mask out the ExcCode bits in the from Cause lw $k0, JumpTable($k0) # Read address from jump table j $k0 # Jump to exception handler Return: lw $2, tmpr2 # Code to handle return to user lw $4, tmpr4 # Restore registers lw $5, tmpr5 lw $6, tmpr6 lw $31, tmpr31 li $k1, 0 # Clear reentery flag sw $k1, flag lw $k1, tmpr1 # Restore register 1 .set noat add $1, $k1, $0 .set at mfc0 $k0, $14 # Get the EPC register rfe # Return from exception j $k0 .kdata BadReMess: .asciiz "Bad re-entry into kernel\nHalting\n" .ktext BadReentry: la $4, BadReMess jal PrintString PCont: li $2, 0 # Clear re-entry warning sw $2, flag li $2, 13 # The quit_now syscall syscall # This is a blocking busy wait print routine PrintString: # It is used only to print errors. lb $6, ($4) beqz $6, PSCont PSLoop: lw $5, DisplayStatus bgez $5, PSLoop sw $6, DisplayData add $4, $4, 1 j PrintString PSCont: j $31 .kdata .align 2 JumpTable: # Jump table used to handle exception .word HandleInt # External Interrupt .word PrintMess # TLB modification exception .word PrintMess # TLB miss exception (load or fetch) .word PrintMess # TLB miss exception (store) .word PrintMess # Address error exception (load or fetch) .word PrintMess # Address error exception (store) .word PrintMess # Bus error exception (for a fetch) .word PrintMess # Bus error exception (for a load or store) .word HandleSys # Syscall exception .word PrintMess # Breakpoint exception .word PrintMess # Reserved Instruciton exception .word PrintMess # Coprocessor Unusable exception .word PrintMess # Arithmetic overflow exception .word PrintMess # Inexact floating point result .word PrintMess # Divide by 0 .word PrintMess # Floating point overflow .word PrintMess # Floating point underflow .word PrintMess, PrintMess, PrintMess, PrintMess .word PrintMess, PrintMess, PrintMess, PrintMess MessTable: # A table of messages for error exceptions .word NoMess # External Interrupt .word BadExp # TLB modification exception .word BadExp # TLB miss exception (load or fetch) .word BadExp # TLB miss exception (store) .word AdEMess # Address error exception (load or fetch) .word AdEMess # Address error exception (store) .word BEMess # Bus error exception (for a fetch) .word BEMess # Bus error exception (for a load or store) .word NoMess # Syscall exception .word BadExp # Breakpoint exception .word RIMess # Reserved Instruciton exception .word CpUMess # Coprocessor Unusable exception .word OvfMess # Arithmetic overflow exception .word FPIMess # Inexact floating point result .word DB0Mess # Divide by 0 .word FPOvfMess # Floating point overflow .word FPUnderMess # Floating point underflow .word BadExp, BadExp, BadExp, BadExp .word BadExp, BadExp, BadExp, BadExp .kdata NoMess: .asciiz "This should not be printed.\n" BadExp: .asciiz "Unexpected exception (a bug in spim?)\nHalting\n" AdEMess: .asciiz "Address error exception\n" BEMess: .asciiz "Bus error exception\n" RIMess: .asciiz "Reserved instrunction exception\n" CpUMess: .asciiz "Coprocessor Unusable exception\n" OvfMess: .asciiz "Integer Overflow exception\n" FPIMess: .asciiz "Inexact floating point result exception\n" DB0Mess: .asciiz "Divide by zero exception\n" FPOvfMess: .asciiz "Floating point overflow exception\n" FPUnderMess: .asciiz "Floating point underflow exception\n" .ktext PrintMess: mfc0 $k0, $13 # Get the Cause register and $k0, $k0, 0x3c # Mask out the ExcCode bits in the from Cause lw $4, MessTable($k0) # Read address for message jal PrintString j Return .ktext HandleInt: jal PutNextChar jal GetNextChar jal PutNextChar2 jal GetNextChar2 lw $0, ClockStatus # Clear the clock interrupt # Remove this line if clock iterrupts are used. j Return .kdata SysMess: .asciiz "Illegal syscall number\n" .ktext HandleSys: mfc0 $k1, $14 # Get the EPC register add $k1, $k1, 4 # Increament PC past syscall instruction mtc0 $k1, $14 # Set the EPC register lw $2, tmpr2 # Get the syscall number beq $2, 11, Putc # Is it putc? beq $2, 12, Getc # Is it getc? beq $2, 21, Putc2 # Is it putc2? beq $2, 22, Getc2 # Is it getc2? beq $2, 10, Exit # Is is exit? la $4, SysMess # Bad number, print message. jal PrintString j Return Exit: jal PutNextChar # Empty the output queue lw $5, hpqp lw $6, tpqp bne $5, $6, Exit E2: jal PutNextChar2 # Empty the output queue2 lw $5, hpqp2 lw $6, tpqp2 bne $5, $6, E2 li $2, 0 # Clear re-entry warning sw $2, flag li $2, 13 # The call the quit_now syscall syscall # Blocking method for putc: #Putc: lw $5, DisplayStatus # bgez $5, Putc # sw $4, DisplayData # Interupt driven for putc: .kdata rtn_addr: .word 0 .ktext Putc: lw $4, tmpr4 # Load the chacter to print into $4 jal PutR4 j Return PutR4: lw $6, hpqp # Get head pointer for point queue lw $5, tpqp # Get tail pointer for point queue add $5, $5, 1 # Advance tail and $5, $5, 0xff beq $5, $6, Full # Check if full sb $4, putqueue($5) # Add to queue sw $5, tpqp # Save new tail pointer mfc0 $5, $12 # Get the Status register ori $5, 0x00005000 # Turn on display interrupts mtc0 $5, $12 # Set the Status register sw $31, rtn_addr # Save return address jal PutNextChar # Try to print lw $31, rtn_addr # Restore return address j $31 Full: sw $31, rtn_addr jal start_reent FullLoop: lw $6, hpqp lw $5, tpqp add $5, $5, 1 and $5, $5, 0xff beq $5, $6, FullLoop jal end_reent lw $31, rtn_addr j PutR4 .ktext Putc2: lw $4, tmpr4 # Load the chacter to print into $4 jal PutR42 j Return PutR42: lw $6, hpqp2 # Get head pointer for point queue lw $5, tpqp2 # Get tail pointer for point queue add $5, $5, 1 # Advance tail and $5, $5, 0xff beq $5, $6, Full2 # Check if full sb $4, putqueue2($5) # Add to queue sw $5, tpqp2 # Save new tail pointer mfc0 $5, $12 # Get the Status register ori $5, 0x00005000 # Turn on display interrupts mtc0 $5, $12 # Set the Status register sw $31, rtn_addr # Save return address jal PutNextChar2 # Try to print lw $31, rtn_addr # Restore return address j $31 Full2: sw $31, rtn_addr jal start_reent FullLoop2: lw $6, hpqp2 lw $5, tpqp2 add $5, $5, 1 and $5, $5, 0xff beq $5, $6, FullLoop2 jal end_reent lw $31, rtn_addr j PutR4 # Blocking method for getc #Getc: lw $5, KeyboardStatus # bgez $5, Getc # lw $4, KeyboardData # sw $4, tmpr2 # Interrupt driven method for getc Getc: mfc0 $5, $12 # Get the Status register ori $5, 0x000000800 # Turn on keyboard interrupts mtc0 $5, $12 # Set the Status register lw $4, tgqp # Get the tail get queue pointer lw $5, hgqp # Get the head get queue pointer beq $4, $5, GotNone # See if queue is empty add $5, $5, 1 # Advance the head pointer and $5, $5, 0xff lb $2, getqueue($5) # Get the character sw $2, tmpr2 sw $5, hgqp j Return GotNone: jal start_reent None: lw $4, tgqp # We loop here, but in a multitasking lw $5, hgqp # system, we could run another process. beq $4, $5, None jal end_reent j Getc Getc2: mfc0 $5, $12 # Get the Status register ori $5, 0x000002000 # Turn on keyboard interrupts mtc0 $5, $12 # Set the Status register lw $4, tgqp2 # Get the tail get queue pointer lw $5, hgqp2 # Get the head get queue pointer beq $4, $5, GotNone2 # See if queue is empty add $5, $5, 1 # Advance the head pointer and $5, $5, 0xff lb $2, getqueue2($5) # Get the character sw $2, tmpr2 sw $5, hgqp2 j Return GotNone2: jal start_reent None2: lw $4, tgqp2 # We loop here, but in a multitasking lw $5, hgqp2 # system, we could run another process. beq $4, $5, None2 jal end_reent j Getc2 .kdata putqueue: .space 256 .align 2 tpqp: .word 0 hpqp: .word 0 .ktext PutNextChar: lw $4, DisplayStatus # Get display status word bgez $4, PutRet # Return if not ready lw $4, hpqp # Get head put queue pointer lw $5, tpqp # Get tail put queue pointer beq $4, $5, PutRet # Is empty add $4, $4, 1 # Remove character from queue and $4, $4, 0xff sw $4, hpqp # Update the head pointer lb $4, putqueue($4) # Get the charater to output sw $4, DisplayData # Output the characer PutRet: j $31 # Return to caller .kdata putqueue2: .space 256 .align 2 tpqp2: .word 0 hpqp2: .word 0 .ktext PutNextChar2: lw $4, DisplayStatus2 # Get display status word bgez $4, PutRet2 # Return if not ready lw $4, hpqp2 # Get head put queue pointer lw $5, tpqp2 # Get tail put queue pointer beq $4, $5, PutRet2 # Is empty add $4, $4, 1 # Remove character from queue and $4, $4, 0xff sw $4, hpqp2 # Update the head pointer lb $4, putqueue2($4) # Get the charater to output sw $4, DisplayData2 # Output the characer PutRet2:j $31 # Return to caller .kdata getqueue: .space 256 tgqp: .word 0 hgqp: .word 0 .ktext GetNextChar: lw $4, KeyboardStatus # Get keyboard status word bgez $4, GetRet # Return if not ready lw $4, tgqp # Get tail get queue pointer lw $5, hgqp # Get head get queue pointer add $4, $4, 1 # Increament tail and $4, $4, 0xff beq $4, $5, GetQueueFull # Branch if queue is full sw $4, tgqp # Update tail pointer lw $5, KeyboardData # Get keybroard data sb $5, getqueue($4) # Store it in the queue move $4, $5 # Move to $4 so it can be printed j PutR4 # PutR4 will do a "j $31" GetRet: j $31 # Return to caller GetQueueFull: # Can't really do anything. # Characters may be lost. mfc0 $5, $12 and $5, $5, 0xffffff7ff # Turn off keyboard interrupts mtc0 $5, $12 j $31 .kdata getqueue2: .space 256 tgqp2: .word 0 hgqp2: .word 0 .ktext GetNextChar2: lw $4, KeyboardStatus2 # Get keyboard status word bgez $4, GetRet2 # Return if not ready lw $4, tgqp2 # Get tail get queue pointer lw $5, hgqp2 # Get head get queue pointer add $4, $4, 1 # Increament tail and $4, $4, 0xff beq $4, $5, GetQueueFull2 # Branch if queue is full sw $4, tgqp2 # Update tail pointer lw $5, KeyboardData2 # Get keybroard data sb $5, getqueue2($4) # Store it in the queue move $4, $5 # Move to $4 so it can be printed j PutR42 # PutR4 will do a "j $31" GetRet2:j $31 # Return to caller GetQueueFull2: # Can't really do anything. # Characters may be lost. mfc0 $5, $12 and $5, $5, 0xfffffdfff # Turn off keyboard interrupts mtc0 $5, $12 j $31 .kdata oldtmpr1: .word oldtmpr2: .word oldtmpr4: .word oldtmpr5: .word oldtmpr6: .word oldtmpr31: .word oldrtn_addr: .word oldflag: .word 0 oldEPC: .word oldCause: .word CantReent: .asciiz "Kernel interrupt level too deep.\n" .ktext start_reent: lw $k0, oldflag # Check to see if we are already reentered. beqz $k0, reentOK la $4, CantReent jal PrintString sw $0, oldflag # Quit because of error sw $0, flag li $2, 13 syscall reentOK: lw $k0, tmpr1 # Copy used locations to safe places. sw $k0, oldtmpr1 lw $k0, tmpr2 sw $k0, oldtmpr2 lw $k0, tmpr4 sw $k0, oldtmpr4 lw $k0, tmpr5 sw $k0, oldtmpr5 lw $k0, tmpr6 sw $k0, oldtmpr6 lw $k0, tmpr31 sw $k0, oldtmpr31 lw $k0, rtn_addr sw $k0, oldrtn_addr lw $k0, flag sw $k0, oldflag mfc0 $k0, $13 # Save needed c0 registers sw $k0, oldCause mfc0 $k0, $14 sw $k0, oldEPC sw $0, flag # Enable interrupts! mfc0 $k0, $12 or $k0, $k0, 1 mtc0 $k0, $12 j $31 end_reent: mfc0 $k0, $12 # Disable interrupts! and $k0, $k0, 0xfffffffe mtc0 $k0, $12 lw $k0, oldflag sw $k0, flag sw $0, oldflag lw $k0, oldCause # Restore c0 registers mtc0 $k0, $13 lw $k0, oldEPC mtc0 $k0, $14 lw $k0, oldtmpr1 # Restore used locations sw $k0, tmpr1 lw $k0, oldtmpr2 sw $k0, tmpr2 lw $k0, oldtmpr4 sw $k0, tmpr4 lw $k0, oldtmpr5 sw $k0, tmpr5 lw $k0, oldtmpr6 sw $k0, tmpr6 lw $k0, oldtmpr31 sw $k0, tmpr31 lw $k0, oldrtn_addr sw $k0, rtn_addr j $31 .text .globl __start