libdragon
|
Low-level RSP hardware library. More...
Go to the source code of this file.
Data Structures | |
struct | rsp_snapshot_t |
Snapshot of the register status of the RSP. More... | |
struct | rsp_ucode_t |
RSP ucode definition. More... | |
Macros | |
#define | SP_DMEM ((volatile uint32_t*)0xA4000000) |
RSP DMEM: 4K of data memory. | |
#define | SP_IMEM ((volatile uint32_t*)0xA4001000) |
RSP IMEM: 4K of instruction memory. | |
#define | SP_PC ((volatile uint32_t*)0xA4080000) |
Current SP program counter. | |
#define | SP_DMA_SPADDR ((volatile uint32_t*)0xA4040000) |
SP DMA IMEM/DMEM address register. | |
#define | SP_DMA_RAMADDR ((volatile uint32_t*)0xA4040004) |
SP DMA RDRAM address register. | |
#define | SP_DMA_RDLEN ((volatile uint32_t*)0xA4040008) |
SP DMA from RDRAM to IMEM/DMEM register. | |
#define | SP_DMA_WRLEN ((volatile uint32_t*)0xA404000C) |
SP DMA from IMEM/DMEM to RDRAM register. | |
#define | SP_STATUS ((volatile uint32_t*)0xA4040010) |
SP status register. | |
#define | SP_DMA_FULL ((volatile uint32_t*)0xA4040014) |
SP DMA full register. | |
#define | SP_DMA_BUSY ((volatile uint32_t*)0xA4040018) |
SP DMA busy register. | |
#define | SP_SEMAPHORE ((volatile uint32_t*)0xA404001C) |
SP semaphore register. | |
#define | SP_STATUS_HALTED (1 << 0) |
SP halted. | |
#define | SP_STATUS_BROKE (1 << 1) |
SP executed a break instruction. | |
#define | SP_STATUS_DMA_BUSY (1 << 2) |
SP DMA busy. | |
#define | SP_STATUS_DMA_FULL (1 << 3) |
SP DMA full. | |
#define | SP_STATUS_IO_BUSY (1 << 4) |
SP IO busy. | |
#define | SP_STATUS_SSTEP (1 << 5) |
SP is in single step mode. | |
#define | SP_STATUS_INTERRUPT_ON_BREAK (1 << 6) |
SP generate interrupt when hit a break instruction. | |
#define | SP_STATUS_SIG0 (1 << 7) |
SP signal 0 is set. | |
#define | SP_STATUS_SIG1 (1 << 8) |
SP signal 1 is set. | |
#define | SP_STATUS_SIG2 (1 << 9) |
SP signal 2 is set. | |
#define | SP_STATUS_SIG3 (1 << 10) |
SP signal 3 is set. | |
#define | SP_STATUS_SIG4 (1 << 11) |
SP signal 4 is set. | |
#define | SP_STATUS_SIG5 (1 << 12) |
SP signal 5 is set. | |
#define | SP_STATUS_SIG6 (1 << 13) |
SP signal 6 is set. | |
#define | SP_STATUS_SIG7 (1 << 14) |
SP signal 7 is set. | |
#define | SP_WSTATUS_CLEAR_HALT 0x00001 |
SP_STATUS write mask: clear SP_STATUS_HALTED bit. | |
#define | SP_WSTATUS_SET_HALT 0x00002 |
SP_STATUS write mask: set SP_STATUS_HALTED bit. | |
#define | SP_WSTATUS_CLEAR_BROKE 0x00004 |
SP_STATUS write mask: clear BROKE bit. | |
#define | SP_WSTATUS_CLEAR_INTR 0x00008 |
SP_STATUS write mask: clear INTR bit. | |
#define | SP_WSTATUS_SET_INTR 0x00010 |
SP_STATUS write mask: set HALT bit. | |
#define | SP_WSTATUS_CLEAR_SSTEP 0x00020 |
SP_STATUS write mask: clear SSTEP bit. | |
#define | SP_WSTATUS_SET_SSTEP 0x00040 |
SP_STATUS write mask: set SSTEP bit. | |
#define | SP_WSTATUS_CLEAR_INTR_BREAK 0x00080 |
SP_STATUS write mask: clear SP_STATUS_INTERRUPT_ON_BREAK bit. | |
#define | SP_WSTATUS_SET_INTR_BREAK 0x00100 |
SP_STATUS write mask: set SP_STATUS_INTERRUPT_ON_BREAK bit. | |
#define | SP_WSTATUS_CLEAR_SIG0 0x00200 |
SP_STATUS write mask: clear SIG0 bit. | |
#define | SP_WSTATUS_SET_SIG0 0x00400 |
SP_STATUS write mask: set SIG0 bit. | |
#define | SP_WSTATUS_CLEAR_SIG1 0x00800 |
SP_STATUS write mask: clear SIG1 bit. | |
#define | SP_WSTATUS_SET_SIG1 0x01000 |
SP_STATUS write mask: set SIG1 bit. | |
#define | SP_WSTATUS_CLEAR_SIG2 0x02000 |
SP_STATUS write mask: clear SIG2 bit. | |
#define | SP_WSTATUS_SET_SIG2 0x04000 |
SP_STATUS write mask: set SIG2 bit. | |
#define | SP_WSTATUS_CLEAR_SIG3 0x08000 |
SP_STATUS write mask: clear SIG3 bit. | |
#define | SP_WSTATUS_SET_SIG3 0x10000 |
SP_STATUS write mask: set SIG3 bit. | |
#define | SP_WSTATUS_CLEAR_SIG4 0x20000 |
SP_STATUS write mask: clear SIG4 bit. | |
#define | SP_WSTATUS_SET_SIG4 0x40000 |
SP_STATUS write mask: set SIG4 bit. | |
#define | SP_WSTATUS_CLEAR_SIG5 0x80000 |
SP_STATUS write mask: clear SIG5 bit. | |
#define | SP_WSTATUS_SET_SIG5 0x100000 |
SP_STATUS write mask: set SIG5 bit. | |
#define | SP_WSTATUS_CLEAR_SIG6 0x200000 |
SP_STATUS write mask: clear SIG6 bit. | |
#define | SP_WSTATUS_SET_SIG6 0x400000 |
SP_STATUS write mask: set SIG6 bit. | |
#define | SP_WSTATUS_CLEAR_SIG7 0x800000 |
SP_STATUS write mask: clear SIG7 bit. | |
#define | SP_WSTATUS_SET_SIG7 0x1000000 |
SP_STATUS write mask: set SIG7 bit. | |
#define | DEFINE_RSP_UCODE(ucode_name, ...) |
Define one RSP ucode compiled via libdragon's build system (n64.mk). | |
#define | rsp_crash() |
Abort the program showing a RSP crash screen. | |
#define | rsp_crashf(msg, ...) |
Abort the program showing a RSP crash screen with a symptom message. | |
#define | RSP_WAIT_LOOP(timeout_ms) |
Create a loop that waits for some condition that is related to RSP, aborting with a RSP crash after a timeout. | |
Functions | |
void | rsp_init (void) |
Initialize the RSP subsytem. | |
void | rsp_load (rsp_ucode_t *ucode) |
Load a RSP ucode. | |
void | rsp_run (void) |
Run RSP ucode. | |
void | rsp_run_async (void) |
Run RSP async. | |
void | rsp_wait (void) |
Wait until RSP has finished processing. | |
void | rsp_load_code (void *code, unsigned long size, unsigned int imem_offset) |
Do a DMA transfer to load a piece of code into RSP IMEM. | |
void | rsp_load_data (void *data, unsigned long size, unsigned int dmem_offset) |
Do a DMA transfer to load a piece of data into RSP DMEM. | |
void | rsp_read_code (void *code, unsigned long size, unsigned int imem_offset) |
Do a DMA transfer to load a piece of code from RSP IMEM to RDRAM. | |
void | rsp_read_data (void *data, unsigned long size, unsigned int dmem_offset) |
Do a DMA transfer to load a piece of data from RSP DMEM to RDRAM. | |
Low-level RSP hardware library.
This library offers very low-level support for RSP programming. The goal is to provide access to the hardware by exposing macros for all hardware registers, provide a few simple helpers to load and run RSP ucode (without any constraint or limitation on how the ucode should be designed, how it should communicate with the CPU, etc.), and a few debugging helpers to aid during development.
This documentation is not a guide to become a RSP programmer. It assumes familiarity with RSP programming concepts and focus on explaining libdragon RSP support.
To define a RSP ucode, assuming you are using the n64.mk build system, it is sufficient to do the following:
.S
and whose name starts with rsp
. For instance rsp_math.S
.rsp_math.o
) in your Makefile in the list of dependencies for building your ROM, like all the other object files that come from C files.DEFINE_RSP_UCODE(rsp_math)
.At this point, you can load the ucode using rsp_load and run it using either rsp_run, or rsp_run_async (and later synchronize with rsp_wait). You can look at the ucodetest
example which is a very minimal program that does some RSP programming.
If you don't use n64.mk, you will have to come up with your own way to load the ucode text and data segment into the ROM, and then either define your own rsp_ucode_t structure, or manually call the lower level functions rsp_load_code and rsp_load_data.
To provide input and read output from the RSP, there are a few possible ways:
RSP does not have any concept of exception. So in general it is not possible to tell whether something went wrong while running the ucode.
We define "RSP crash" any situation in which the RSP is behaving in an unexpected way. For instance, it may have returned corrupted data, or have stopped responding (eg: it is in an infinite loop), or has interrupted its execution before providing a result (eg: a signal has not been set in the status register).
When the CPU notices that the RSP may have crashed, it can invoke the rsp_crash function. This function interrupts the program showing a crash screen that contains a full register dump, and then aborts execution, so it must be used in non-recoverable situations. A even more complete dump that includes also a full DMEM dump is sent via debugf on the debugging spew (see the debugging library to check how to hook up to it). A function rsp_crashf is also available in case the CPU wants to provide a message on the symptom that was used to detect the RSP crash (eg: rsp_crashf("computed data is corrupted")
).
To help detect RSP crashes that involve timeouts, a macro RSP_WAIT_LOOP is available that can be used to implement CPU busy loops where the CPU waits for the RSP to do something. The macro just simplifies the creation of a loop with a timeout that calls rsp_crash, the actual condition to wait for is left to the caller for the maximum programming flexibility. Notice that rsp_wait and rsp_run use RSP_WAIT_LOOP internally with a timeout of 500ms to wait for the RSP to finish execution of the ucode, so using those APIs is enough to detect infinite loops in ucode execution and trigger a RSP crash screen.
It may be useful to also dump ucode-specific information when the crash screen is triggered. For instance, a ucode might want to dump on the screen some important variable or buffers taken from DMEM, or even reconstruct some state by looking at the registers. To do so, it is possible to register a ucode-specific crash handler by filling the crash_handler
field in the rsp_ucode_t structure (it can be done either at runtime, or at compile-time when using the DEFINE_RSP_UCODE macro).
The crash handler will be called by the RSP crash screen and can either add information on the screen (via printf) or dump them to the debugging log (via debugf). It receives a rsp_snapshot_t, which is a full snapshot of the whole RSP state at the moment of crash, including all registers (scalars and vectors), all COP0/COP2 registers, and the full IMEM and DMEM contents.
Since RSP debugging is quite complex due to the limited available communication channels, it is advised to adopt defensive programming while writing RSP ucode. The header file rsp.inc provides a set of assert macros that can be used in the RSP ucode to check for assumptions and invariants, similar to the C assert. To use them, also include the file rsp_assert.inc in your text segment.
When the RSP hits an assert, it enters an infinite loop, that will be eventually detected by the CPU, triggering the RSP crash screen. Each assertion can define a numeric assert code that will be shown in the crash screen.
To further help with debugging, it is possible to register a custom assert manager in the ucode. Similar to the crash handler, the assert handler will be called when an assert is triggered and will be provided with a rsp_snapshot_t, and the assert code. The assert handler can be used to parse the assert code and display a proper assert message (about two lines of text). Since each assert is placed in a specific point in the code, the assert handler can inspect the register contents at the point of assertion to provide further information on the crash. Notice that the crash handler will still be called also for asserts and remains the best place where display a dump of the main internal data structures of the overlay.
The RSP assert macros are compiled away when NDEBUG is defined (just like the C assert), so that it is possible to remove them from the final build in case of memory constraints.
struct rsp_snapshot_t |
Snapshot of the register status of the RSP.
This structure is used in the crash handler.
#define DEFINE_RSP_UCODE | ( | ucode_name, | |
... | |||
) |
Define one RSP ucode compiled via libdragon's build system (n64.mk).
If you're using libdragon's build system (n64.mk), use DEFINE_RSP_UCODE() to define one ucode coming from a rsp_*.S file. For instance, if you wrote and compiled a ucode called rsp_math.S, you can use DEFINE_RSP_UCODE(rsp_math) to define it at the global level. You can then use rsp_load(&rsp_math) to load it.
To statically define attributes of the ucode, you can use the C designated initializer syntax.
#define rsp_crash | ( | ) |
Abort the program showing a RSP crash screen.
This function aborts the execution of the program, and shows an exception screen which contains the RSP status.
This function (and its sibling rsp_crashf) should be invoked whenever the CPU realizes that the RSP is severely misbehaving, as it provides useful information on the RSP status that can help tracking down the bug. It is invoked automatically by this library (and others RSP libraries that build upon) whenever internal consistency checks fail. It is also invoked as part of RSP_WAIT_LOOP
when the timeout is reached, which is the most common way of detecting RSP misbehavior.
If the RSP has hit an assert, the crash screen will display the assert- specific information (like assert code and assert message).
To display ucode-specific information (like structural decoding of DMEM data), this function will call the function crash_handler in the current rsp_ucode_t, if it is defined.
#define rsp_crashf | ( | msg, | |
... | |||
) |
Abort the program showing a RSP crash screen with a symptom message.
This function is similar to rsp_crash, but also allows to specify a message that will be displayed in the crash screen. Since the CPU is normally unaware of the exact reason why the RSP has crashed, the message is possibly just a symptom as observed by the CPU (eg: "timeout reached", "signal was not set"), and is in fact referred as "symptom" in the RSP crash screen.
See rsp_crash for more information on when to call this function and how it can be useful.
#define RSP_WAIT_LOOP | ( | timeout_ms | ) |
Create a loop that waits for some condition that is related to RSP, aborting with a RSP crash after a timeout.
This macro simplifies the creation of a loop that busy-waits for operations performed by the RSP. If the condition is not reached within a timeout, it is assumed that the RSP has crashed or otherwise stalled and rsp_crash is invoked to abort the program showing a debugging screen.
[in] | timeout_ms | Allowed timeout in milliseconds. Normally a value like 150 is good enough because it is unlikely that the application should wait for such a long time. |
void rsp_load | ( | rsp_ucode_t * | ucode | ) |
Load a RSP ucode.
This function allows to load a RSP ucode into the RSP internal memory. The function executes the transfer right away, so it is responsibility of the caller making sure that it's a good time to do it.
The function internally keeps a pointer to the last loaded ucode. If the ucode passed is the same, it does nothing. This makes it easier to write code that optimistically switches between different ucodes, but without forcing transfers every time.
[in] | ucode | Ucode to load into RSP |
void rsp_run | ( | void | ) |
Run RSP ucode.
This function starts running the RSP, and wait until the ucode is finished.
|
inline |
Run RSP async.
This function starts running the RSP in background. Use rsp_wait() to synchronize later.
void rsp_wait | ( | void | ) |
Wait until RSP has finished processing.
This function will wait until the RSP is halted. It contains a fixed timeout of 500 ms, after which rsp_crash is invoked to abort the program.
void rsp_load_code | ( | void * | code, |
unsigned long | size, | ||
unsigned int | imem_offset | ||
) |
Do a DMA transfer to load a piece of code into RSP IMEM.
This is a lower-level function that actually executes a DMA transfer from RDRAM to IMEM. Prefer using rsp_load instead.
[in] | code | Pointer to buffer in RDRAM containing code. Must be aligned to 8 bytes. |
[in] | size | Size of the code to load. Must be a multiple of 8. |
[in] | imem_offset | Byte offset in IMEM where to load the code. Must be a multiple of 8. |
void rsp_load_data | ( | void * | data, |
unsigned long | size, | ||
unsigned int | dmem_offset | ||
) |
Do a DMA transfer to load a piece of data into RSP DMEM.
This is a lower-level function that actually executes a DMA transfer from RDRAM to DMEM. Prefer using rsp_load instead.
[in] | data | Pointer to buffer in RDRAM containing data. Must be aligned to 8 bytes. |
[in] | size | Size of the data to load. Must be a multiple of 8. |
[in] | dmem_offset | Offset in DMEM where to load the code. Must be a multiple of 8. |
void rsp_read_code | ( | void * | code, |
unsigned long | size, | ||
unsigned int | imem_offset | ||
) |
Do a DMA transfer to load a piece of code from RSP IMEM to RDRAM.
This is a lower-level function that actually executes a DMA transfer from IMEM to RDRAM.
[in] | code | Pointer to buffer in RDRAM where to write code. Must be aligned to 8 bytes. |
[in] | size | Size of the code to load. Must be a multiple of 8. |
[in] | imem_offset | Byte offset in IMEM where where the code will be loaded from. Must be a multiple of 8. |
void rsp_read_data | ( | void * | data, |
unsigned long | size, | ||
unsigned int | dmem_offset | ||
) |
Do a DMA transfer to load a piece of data from RSP DMEM to RDRAM.
This is a lower-level function that actually executes a DMA transfer from DMEM to RDRAM.
[in] | data | Pointer to buffer in RDRAM where to write data. Must be aligned to 8 bytes. |
[in] | size | Size of the data to load. Must be a multiple of 8. |
[in] | dmem_offset | Byte offset in IMEM where where the data will be loaded from. Must be a multiple of 8. |