diff options
Diffstat (limited to 'rsp')
| -rw-r--r-- | rsp/rsp2elf.s | 47 | ||||
| -rw-r--r-- | rsp/rspboot.h | 29 | ||||
| -rw-r--r-- | rsp/rspboot.s | 127 | ||||
| -rw-r--r-- | rsp/rspboot_ap.s | 153 |
4 files changed, 356 insertions, 0 deletions
diff --git a/rsp/rsp2elf.s b/rsp/rsp2elf.s new file mode 100644 index 000000000..db3618854 --- /dev/null +++ b/rsp/rsp2elf.s @@ -0,0 +1,47 @@ +/** + * @file rsp2elf.s + * + * This file converts binaries output from armips into a suitable elf file to be linked into the project. + * It requires the following preprocessor definitions to be provided when assembled: + * UC_NAME : The microcode name, used to generate symbol names for text and data + * UC_TEXT_SECTION : The program section to place the microcode text into, usually .text + * UC_DATA_SECTION : The program section to place the microcode data into, usually .rodata + * UC_TEXT_BIN_PATH : The path to the microcode text binary + * UC_DATA_BIN_PATH : The path to the microcode data binary + */ + +/* Preprocessor macros */ + +#define GLUE(a,b) a##b +#define SYM_NAME(a,b) GLUE(a,b) + +#define STR(x) #x +#define BIN_PATH(x) STR(x) + +/* Microcode text */ + +.section UC_TEXT_SECTION + +.global SYM_NAME(UC_NAME,TextStart) +SYM_NAME(UC_NAME,TextStart): + + .incbin BIN_PATH(UC_TEXT_BIN_PATH) + +.global SYM_NAME(UC_NAME,TextEnd) +SYM_NAME(UC_NAME,TextEnd): + +.size SYM_NAME(UC_NAME,TextStart), SYM_NAME(UC_NAME,TextEnd) - SYM_NAME(UC_NAME,TextStart) + +/* Microcode data */ + +.section UC_DATA_SECTION + +.global SYM_NAME(UC_NAME,DataStart) +SYM_NAME(UC_NAME,DataStart): + + .incbin BIN_PATH(UC_DATA_BIN_PATH) + +.global SYM_NAME(UC_NAME,DataEnd) +SYM_NAME(UC_NAME,DataEnd): + +.size SYM_NAME(UC_NAME,DataStart), SYM_NAME(UC_NAME,DataEnd) - SYM_NAME(UC_NAME,DataStart) diff --git a/rsp/rspboot.h b/rsp/rspboot.h new file mode 100644 index 000000000..16213ad68 --- /dev/null +++ b/rsp/rspboot.h @@ -0,0 +1,29 @@ +#ifndef RSPBOOT_H +#define RSPBOOT_H + +/* Memory addressing */ +#define DMEM_START (0x0000) +#define DMEM_SIZE (0x1000) +#define DMEM_END (DMEM_START + DMEM_SIZE) +#define IMEM_START (DMEM_END) +#define IMEM_SIZE (0x1000) +#define IMEM_END (IMEM_START + IMEM_SIZE) + +#define DMEM_START_VA (0x04000000 | DMEM_START) +#define IMEM_START_VA (0x04000000 | IMEM_START) +#define DMEM_END_VA (0x04000000 | DMEM_END) +#define IMEM_END_VA (0x04000000 | IMEM_END) + +/* The first bytes of IMEM are reserved so that rspboot has space for + * code that will load the new IMEM contents for the target ucode. + */ +#define RSPBOOT_RESERVED_IMEM 0x80 + +/* Entrypoint for ucodes loaded with rspboot */ +#define RSPBOOT_ENTRYPOINT (IMEM_START + RSPBOOT_RESERVED_IMEM) +#define RSPBOOT_ENTRYPOINT_VA (0x04000000 | RSPBOOT_ENTRYPOINT) + +/* OSTask structure */ +#define OSTASK_ADDR (DMEM_END - OS_TASK_SIZE) + +#endif diff --git a/rsp/rspboot.s b/rsp/rspboot.s new file mode 100644 index 000000000..b3dc968d4 --- /dev/null +++ b/rsp/rspboot.s @@ -0,0 +1,127 @@ +/** + * @file rspboot.s + * + * This RSP microcode program acts as a loader for other microcodes. This ucode: + * - (Optionally) Waits for the RDP to go idle + * - Loads a ucode's .data section to the start of DMEM + * - Loads a ucode's .text section to RSPBOOT_ENTRYPOINT + * - Jumps to the loaded ucode + * + * Along the way it also checks whether the task scheduler running on the CPU has + * requested a yield and, if so, halts the RSP. + */ +.rsp +#include "rcp.h" +#include "sptask.h" +#include "rspboot.h" + +// $1 is expected to contain the OSTask pointer by many ucodes +OSTask_reg equ $1 + +.create CODE_FILE, IMEM_START_VA + +entry: + j start + addi OSTask_reg, $zero, OSTASK_ADDR + +load_ucode_text_and_enter: + // Read the ucode .text DRAM address from the OSTask + lw $2, OS_TASK_OFF_UCODE(OSTask_reg) + // Always DMA as much as will fit in IMEM to RSPBOOT_ENTRYPOINT + addi $3, $zero, (IMEM_SIZE - (RSPBOOT_ENTRYPOINT - IMEM_START)) - 1 + addi $7, $zero, RSPBOOT_ENTRYPOINT + // Start the DMA, assuming it's not busy having waited for it while + // loading the ucode .data + mtc0 $7, SP_MEM_ADDR + mtc0 $2, SP_DRAM_ADDR + mtc0 $3, SP_RD_LEN + // Wait for .text to load +@@while_dma_busy: + mfc0 $4, SP_DMA_BUSY + bnez $4, @@while_dma_busy + nop + // Check yield one more time + jal check_yield + nop + // Release the semaphore just in case a prior task or the CPU failed to release it + // and jump to the ucode that was loaded + jr $7 + mtc0 $zero, SP_SEMAPHORE + +check_yield: + // Check the YIELD bit in SP_STATUS: if it is set enter yield_break, + // otherwise return + mfc0 $8, SP_STATUS + andi $8, $8, SP_STATUS_YIELD + bnez $8, yield_break + nop + // No need to yield, return to caller + jr $ra +yield_break: + // Release the semaphore (note this happens even if there is no yield as it + // is in the delay slot of the above instruction) + mtc0 $zero, SP_SEMAPHORE + // Update the RSP status to signal task complete and yielded, clear the + // yield request + li $8, (SP_SET_TASKDONE | SP_SET_YIELDED | SP_CLR_YIELD) + mtc0 $8, SP_STATUS + // Halt the RSP + break + nop + +// Everything up to here needs to end before RSPBOOT_ENTRYPOINT so it's not overwritten by the .text loader +.if . > RSPBOOT_ENTRYPOINT_VA + .error "Not enough reserved IMEM for .text bootstrapper" +.endif + +start: + // Check the DP_WAIT flag, if not set branch straight to loading ucode .data + lw $2, OS_TASK_OFF_FLAGS(OSTask_reg) + andi $2, $2, OS_TASK_DP_WAIT + beqz $2, load_ucode_data + nop + // If the flag is set, we need to wait for the RDP to become idle just in case + // it's still reading commands from DMEM in XBUS mode. First check if we need + // to yield to another task. + jal check_yield + nop + // Now poll RDP DMA busy + mfc0 $2, DPC_STATUS + andi $2, $2, DPC_STATUS_DMA_BUSY + // If the RDP is still busy, check yield again. This will return to the same + // return address as the above jal since $ra has not been clobbered since then. + bgtz $2, check_yield + nop +load_ucode_data: + // Load the DRAM address and size of the ucode .data from the OSTask + lw $2, OS_TASK_OFF_UDATA(OSTask_reg) + lw $3, OS_TASK_OFF_UDATA_SZ(OSTask_reg) + addi $3, $3, -1 + // Wait for a DMA slot to free +@@while_dma_full: + mfc0 $30, SP_DMA_FULL + bnez $30, @@while_dma_full + nop + // Submit the DMA transfer for the ucode .data, targeting the start of DMEM + mtc0 $zero, SP_MEM_ADDR + mtc0 $2, SP_DRAM_ADDR + mtc0 $3, SP_RD_LEN + // Wait for the transfer to complete +@@while_dma_busy: + mfc0 $4, SP_DMA_BUSY + bnez $4, @@while_dma_busy + nop + // Check whether we need to yield, does not return if we do + jal check_yield + nop + // Jump to ucode .text loader + j load_ucode_text_and_enter + nop + +.align 0x10 + +.if . > IMEM_END_VA + .error "Not enough room in IMEM" +.endif + +.close diff --git a/rsp/rspboot_ap.s b/rsp/rspboot_ap.s new file mode 100644 index 000000000..d3708c2fa --- /dev/null +++ b/rsp/rspboot_ap.s @@ -0,0 +1,153 @@ +/** + * @file rspboot_ap.s + * + * This RSP microcode program acts as a loader for other microcodes. This ucode: + * - (Optionally) Waits for the RDP to go idle + * - Loads a ucode's .data section to the start of DMEM + * - Loads a ucode's .text section to RSPBOOT_ENTRYPOINT + * - Jumps to the loaded ucode + * + * Along the way it also checks whether the task scheduler running on the CPU has + * requested a yield and, if so, halts the RSP. + * + * This is a special variant of rspboot that is designed to work alongside IPL3 X105 + * and the cic6105 microcode to attempt to detect whether the running game is a pirated + * copy. If it is so determined (in the antipiracy_test routine) cic6105 will not set + * signal 7 in SP_STATUS, causing subsequent runs of rspboot to corrupt random regions + * of memory (see the dpclock_corrupt routine) + */ +.rsp +#include "rcp.h" +#include "sptask.h" +#include "rspboot.h" + +#define OSTASK_FIELD(field) (-0x40 + OS_TASK_OFF_##field)($zero) + +.create CODE_FILE, IMEM_START + +entry: + // This jump is overwritten once rspboot runs for the first time (see set_status_and_patch below) + j antipiracy_test + lw $17, OSTASK_FIELD(FLAGS) + +load_ucode_text_and_enter: + li $ra, RSPBOOT_ENTRYPOINT + li $1, (IMEM_SIZE - (RSPBOOT_ENTRYPOINT - IMEM_START))-1 + mtc0 $16, SP_DRAM_ADDR + mtc0 $ra, SP_MEM_ADDR + mtc0 $1, SP_RD_LEN +@@while_dma_busy: + mfc0 $1, SP_DMA_BUSY + bnez $1, @@while_dma_busy +check_yield: + mfc0 $1, SP_STATUS + andi $1, $1, SP_STATUS_YIELD + bnez $1, yield_break + li $1, OSTASK_ADDR // $1 is expected to contain the OSTask pointer by many ucodes + jr $ra + +yield_break: + li $17, (SP_SET_SIG2 | SP_SET_SIG1 | SP_CLR_SIG0) + mtc0 $17, SP_STATUS + break + +antipiracy_test: + // Checks that $4, $5, $6 and $11 all have expected register values set by IPL3 X105 + lui $1, (SP_CLR_SIG7 >> 16) + li $3, 0x3D8 + bne $11, $3, set_status_and_patch + addi $16, $zero, entry_failure + lui $3, 0x3A0 + ori $3, $3, 0x4820 + bne $4, $3, set_status_and_patch + lui $3, 0x2529 + ori $3, $3, 0x4 + bne $6, $3, set_status_and_patch + srl $3, $5, 12 + addi $2, $3, -76 + bltz $2, set_status_and_patch + addi $2, $3, -79 + bgtz $2, set_status_and_patch + vxor $v13, $v13, $v13[1q] + vaddc $v13, $v13, $v13[2h] + vnxor $v13, $v13, $v13[4] + mfc2 $3, $v13[0] + xori $3, $3, 0x1234 // $3 is calculated and then never used before it is repurposed + nop + lui $1, (SP_SET_SIG7 >> 16) + addi $16, $zero, entry_success +set_status_and_patch: + mtc0 $1, SP_STATUS + lw $1, OSTASK_FIELD(UBOOT) + // fallthrough to dma write 8 bytes, patches the first 8 bytes of the ucode in dram + // with a new jump target that avoids the DPC_CLOCK code if SP_STATUS_SIG7 is set + +dma_write_8: +@@while_dma_full: + mfc0 $3, SP_DMA_FULL + bne $3, $3, @@while_dma_full + nop + mtc0 $16, SP_MEM_ADDR + mtc0 $1, SP_DRAM_ADDR + j dp_wait + mtc0 $zero, SP_WR_LEN // 0 = 8-byte write to RDRAM + +.align 8 +entry_success: + // antipiracy check passed, rspboot is patched to check signal 7 instead of performing the check again + j check_sig7 + lw $17, OSTASK_FIELD(FLAGS) + +.align 8 +entry_failure: + // antipiracy check failed, rspboot is patched to run the failure codepath until reset + j dpclock_corrupt + lw $17, OSTASK_FIELD(FLAGS) + +check_sig7: // if signal 7 is set, skip DPC_CLOCK sampling + corruption + mfc0 $16, SP_STATUS + andi $16, $16, SP_STATUS_SIG7 + bnez $16, dp_wait +dpclock_corrupt: + mfc0 $16, DPC_CLOCK + andi $1, $16, 0x1FFF + bnez $1, @@stepover // if the 13 low-order bits of the sampled clock are 0, set DPC_END to DPC_CLOCK + andi $1, $16, 0x7F + mtc0 $16, DPC_END +@@stepover: + beqz $1, dma_write_8 // if the 7 low-order bits of the sampled clock are 0, corrupt 8 random bytes of DRAM + srl $1, $16, 2 +dp_wait: + sll $17, $17, (32 - OS_TASK_DP_WAIT) + bgezal $17, load_ucode_data + lw $16, OSTASK_FIELD(UDATA_SZ) + mfc0 $1, DPC_STATUS + andi $1, $1, DPC_STATUS_DMA_BUSY + bnez $1, check_yield // yield check while waiting for RDP Command FIFO to become idle + +load_ucode_data: +@@while_dma_full: + mfc0 $1, SP_DMA_FULL + bnez $1, @@while_dma_full + lw $1, OSTASK_FIELD(UDATA) + addi $16, $16, -1 + mtc0 $zero, SP_MEM_ADDR + mtc0 $1, SP_DRAM_ADDR + mtc0 $16, SP_RD_LEN +@@while_dma_busy: + mfc0 $1, SP_DMA_BUSY + bnez $1, @@while_dma_busy + li $ra, load_ucode_text_and_enter + mtc0 $zero, SP_SEMAPHORE // Release the semaphore just in case a prior task or the CPU failed to release it + vxor $v16, $v16, $v16 // set $v16 to 0, some F3DZEX versions rely on this being set to function properly + sw $4, OSTASK_FIELD(UBOOT) + j check_yield + lw $16, OSTASK_FIELD(UCODE) + +.align 0x10 + +.if . > IMEM_END + .error "Not enough room in IMEM" +.endif + +.close |
