mirror of
https://github.com/spicyjpeg/573in1.git
synced 2025-03-02 15:44:35 +01:00
291 lines
7.6 KiB
ArmAsm
291 lines
7.6 KiB
ArmAsm
# ps1-bare-metal - (C) 2023 spicyjpeg
|
|
#
|
|
# Permission to use, copy, modify, and/or distribute this software for any
|
|
# purpose with or without fee is hereby granted, provided that the above
|
|
# copyright notice and this permission notice appear in all copies.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
# PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
.set noreorder
|
|
.set noat
|
|
|
|
## Exception handler
|
|
|
|
.set BADV, $8
|
|
.set SR, $12
|
|
.set CAUSE, $13
|
|
.set EPC, $14
|
|
|
|
.set COP0_CAUSE_EXC_BITMASK, 31 << 2
|
|
.set COP0_CAUSE_EXC_SYS, 8 << 2
|
|
|
|
.section .text._exceptionVector, "ax", @progbits
|
|
.global _exceptionVector
|
|
.type _exceptionVector, @function
|
|
|
|
_exceptionVector:
|
|
# This 16-byte stub is going to be relocated to the address the CPU jumps to
|
|
# when an exception occurs (0x80000080) at runtime, overriding the default
|
|
# one installed by the BIOS. We're going to fetch a pointer to the current
|
|
# thread, grab the EPC (i.e. the address of the instruction that was being
|
|
# executed before the exception occurred) and jump to the exception handler.
|
|
# NOTE: we can't use any registers other than $k0 and $k1 here, as doing so
|
|
# would destroy their contents and corrupt the current thread's state.
|
|
lw $k0, %gprel(currentThread)($gp)
|
|
j _exceptionHandler
|
|
mfc0 $k1, EPC
|
|
nop
|
|
|
|
.section .text._exceptionHandler, "ax", @progbits
|
|
.global _exceptionHandler
|
|
.type _exceptionHandler, @function
|
|
|
|
_exceptionHandler:
|
|
# Save the full state of the thread in order to make sure the interrupt
|
|
# handler callback (invoked later on) can use any register. The state of
|
|
# $hi/$lo is saved after all other registers in order to let the multiplier
|
|
# finish any ongoing calculation.
|
|
sw $at, 0x04($k0)
|
|
sw $v0, 0x08($k0)
|
|
sw $v1, 0x0c($k0)
|
|
sw $a0, 0x10($k0)
|
|
sw $a1, 0x14($k0)
|
|
sw $a2, 0x18($k0)
|
|
sw $a3, 0x1c($k0)
|
|
sw $t0, 0x20($k0)
|
|
sw $t1, 0x24($k0)
|
|
sw $t2, 0x28($k0)
|
|
sw $t3, 0x2c($k0)
|
|
sw $t4, 0x30($k0)
|
|
sw $t5, 0x34($k0)
|
|
sw $t6, 0x38($k0)
|
|
sw $t7, 0x3c($k0)
|
|
sw $s0, 0x40($k0)
|
|
sw $s1, 0x44($k0)
|
|
sw $s2, 0x48($k0)
|
|
sw $s3, 0x4c($k0)
|
|
sw $s4, 0x50($k0)
|
|
sw $s5, 0x54($k0)
|
|
sw $s6, 0x58($k0)
|
|
sw $s7, 0x5c($k0)
|
|
sw $t8, 0x60($k0)
|
|
sw $t9, 0x64($k0)
|
|
sw $gp, 0x68($k0)
|
|
sw $sp, 0x6c($k0)
|
|
sw $fp, 0x70($k0)
|
|
sw $ra, 0x74($k0)
|
|
|
|
mfhi $v0
|
|
mflo $v1
|
|
sw $v0, 0x78($k0)
|
|
sw $v1, 0x7c($k0)
|
|
|
|
# Check bits 2-6 of the CAUSE register to determine what triggered the
|
|
# exception. If it was caused by a syscall, increment EPC to make sure
|
|
# returning to the thread won't trigger another syscall.
|
|
mfc0 $v0, CAUSE
|
|
lw $v1, %gprel(interruptHandler)($gp)
|
|
|
|
# int code = CAUSE & COP0_CAUSE_EXC_BITMASK;
|
|
andi $v0, COP0_CAUSE_EXC_BITMASK
|
|
beqz $v0, .LisInterrupt
|
|
li $at, COP0_CAUSE_EXC_SYS
|
|
|
|
beq $v0, $at, .LisSyscall
|
|
lw $a0, %gprel(interruptHandlerArg0)($gp)
|
|
|
|
.LisOtherException: # if ((code != INT) && (code != SYS)) {
|
|
# If the exception was not triggered by a syscall nor by an interrupt call
|
|
# _unhandledException(), which will then display information about the
|
|
# exception and lock up.
|
|
sw $k1, 0x00($k0)
|
|
|
|
mfc0 $a1, BADV # _unhandledException((CAUSE >> 2) & 0x1f, BADV)
|
|
srl $a0, $v0, 2
|
|
jal _unhandledException
|
|
addiu $sp, -8
|
|
|
|
lw $k0, %gprel(nextThread)($gp)
|
|
b .Lreturn
|
|
addiu $sp, 8
|
|
|
|
.LisInterrupt: # } else {
|
|
# If the exception was caused by an interrupt, check if the interrupted
|
|
# instruction was a GTE opcode and increment EPC to avoid executing it again
|
|
# if that is the case. This is a workaround for a hardware bug.
|
|
|
|
# if ((code == INT) && ((*EPC >> 25) == 0x25)) EPC += 4;
|
|
lw $v0, 0($k1)
|
|
li $at, 0x25
|
|
srl $v0, 25
|
|
bne $v0, $at, .LskipEPCIncrement
|
|
lw $a0, %gprel(interruptHandlerArg0)($gp)
|
|
|
|
.LisSyscall:
|
|
# if (code == SYS) EPC += 4;
|
|
addiu $k1, 4
|
|
|
|
.LskipEPCIncrement:
|
|
# Save the modified EPC and invoke the interrupt handler, which will
|
|
# temporarily use the current thread's stack.
|
|
sw $k1, 0x00($k0)
|
|
|
|
# interruptHandler(interruptHandlerArg0, interruptHandlerArg1);
|
|
lw $a1, %gprel(interruptHandlerArg1)($gp)
|
|
jalr $v1
|
|
addiu $sp, -8
|
|
|
|
lw $k0, %gprel(nextThread)($gp)
|
|
addiu $sp, 8
|
|
|
|
.Lreturn: # }
|
|
# Grab a pointer to the next thread to be executed and restore its state.
|
|
|
|
# currentThread = nextThread;
|
|
sw $k0, %gprel(currentThread)($gp)
|
|
|
|
lw $v0, 0x78($k0)
|
|
lw $v1, 0x7c($k0)
|
|
mthi $v0
|
|
mtlo $v1
|
|
|
|
lw $k1, 0x00($k0)
|
|
lw $at, 0x04($k0)
|
|
lw $v0, 0x08($k0)
|
|
lw $v1, 0x0c($k0)
|
|
lw $a0, 0x10($k0)
|
|
lw $a1, 0x14($k0)
|
|
lw $a2, 0x18($k0)
|
|
lw $a3, 0x1c($k0)
|
|
lw $t0, 0x20($k0)
|
|
lw $t1, 0x24($k0)
|
|
lw $t2, 0x28($k0)
|
|
lw $t3, 0x2c($k0)
|
|
lw $t4, 0x30($k0)
|
|
lw $t5, 0x34($k0)
|
|
lw $t6, 0x38($k0)
|
|
lw $t7, 0x3c($k0)
|
|
lw $s0, 0x40($k0)
|
|
lw $s1, 0x44($k0)
|
|
lw $s2, 0x48($k0)
|
|
lw $s3, 0x4c($k0)
|
|
lw $s4, 0x50($k0)
|
|
lw $s5, 0x54($k0)
|
|
lw $s6, 0x58($k0)
|
|
lw $s7, 0x5c($k0)
|
|
lw $t8, 0x60($k0)
|
|
lw $t9, 0x64($k0)
|
|
lw $gp, 0x68($k0)
|
|
lw $sp, 0x6c($k0)
|
|
lw $fp, 0x70($k0)
|
|
lw $ra, 0x74($k0)
|
|
|
|
jr $k1
|
|
rfe
|
|
|
|
## Delay functions
|
|
|
|
.set IO_BASE, 0xbf801000
|
|
|
|
.set TIMER2_VALUE, IO_BASE | 0x120
|
|
.set TIMER2_CTRL, IO_BASE | 0x124
|
|
.set TIMER2_RELOAD, IO_BASE | 0x128
|
|
|
|
.section .text.delayMicroseconds, "ax", @progbits
|
|
.global delayMicroseconds
|
|
.type delayMicroseconds, @function
|
|
|
|
delayMicroseconds:
|
|
# Calculate the approximate number of CPU cycles that need to be burned,
|
|
# assuming a 33.8688 MHz clock (1 us = 33.8688 = ~33.875 cycles).
|
|
|
|
# cycles = ((us * 271) + 4) / 8;
|
|
sll $a1, $a0, 8
|
|
sll $a2, $a0, 4
|
|
addu $a1, $a2
|
|
subu $a1, $a0
|
|
addiu $a1, 4
|
|
sra $a0, $a1, 3
|
|
|
|
# Compensate for the overhead of calculating the cycle count, entering the
|
|
# loop and returning.
|
|
addiu $a0, -(6 + 1 + 2 + 4 + 2)
|
|
|
|
# TIMER2_CTRL = 0;
|
|
lui $v1, %hi(IO_BASE)
|
|
sh $0, %lo(TIMER2_CTRL)($v1)
|
|
|
|
# Wait for up to 0xff00 cycles at a time (resetting the timer and waiting
|
|
# for it to count up each time), as the counter is only 16 bits wide. We
|
|
# have to wait 0xff00 cycles rather than 0x10000 since the counter wraps
|
|
# around rather than saturating on overflow.
|
|
li $a1, 0xff00
|
|
slt $v0, $a1, $a0
|
|
beqz $v0, .LshortDelay
|
|
li $a2, 0xff00 + 3
|
|
|
|
.LlongDelay: # for (; cycles > 0xff00; cycles -= (0xff00 + 3)) {
|
|
# TIMER2_VALUE = 0;
|
|
sh $0, %lo(TIMER2_VALUE)($v1)
|
|
li $v0, 0
|
|
|
|
.LlongDelayLoop:
|
|
# while (TIMER2_VALUE < 0xff00);
|
|
nop
|
|
slt $v0, $v0, $a1
|
|
bnez $v0, .LlongDelayLoop
|
|
lhu $v0, %lo(TIMER2_VALUE)($v1)
|
|
|
|
slt $v0, $a1, $a0
|
|
bnez $v0, .LlongDelay
|
|
subu $a0, $a2
|
|
|
|
.LshortDelay: # }
|
|
# Run the last busy loop once less than 0xff00 cycles are remaining.
|
|
|
|
# TIMER2_VALUE = 0;
|
|
sh $0, %lo(TIMER2_VALUE)($v1)
|
|
li $v0, 0
|
|
|
|
.LshortDelayLoop:
|
|
# while (TIMER2_VALUE < cycles);
|
|
nop
|
|
slt $v0, $v0, $a0
|
|
bnez $v0, .LshortDelayLoop
|
|
lhu $v0, %lo(TIMER2_VALUE)($v1)
|
|
|
|
# return;
|
|
jr $ra
|
|
nop
|
|
|
|
.section .text.delayMicrosecondsBusy, "ax", @progbits
|
|
.global delayMicrosecondsBusy
|
|
.type delayMicrosecondsBusy, @function
|
|
|
|
delayMicrosecondsBusy:
|
|
# cycles = ((us * 271) + 4) / 8:
|
|
sll $a1, $a0, 8
|
|
sll $a2, $a0, 4
|
|
addu $a1, $a2
|
|
subu $a1, $a0
|
|
addiu $a1, 4
|
|
sra $a0, $a1, 3
|
|
|
|
# Compensate for the overhead of calculating the cycle count and returning.
|
|
addiu $a0, -(6 + 1 + 2)
|
|
|
|
.Lloop:
|
|
# while (cycles > 0) cycles -= 2;
|
|
bgtz $a0, .Lloop
|
|
addiu $a0, -2
|
|
|
|
# return;
|
|
jr $ra
|
|
nop
|