/**
 * @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
