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