diff options
| author | Derek Hensley <hensley.derek58@gmail.com> | 2023-09-11 17:38:31 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-09-12 10:38:31 +1000 |
| commit | 190b78549e0fa1a801786e73d914185e1fbca2a6 (patch) | |
| tree | bc62310b90bbd59005ef01873ca4a007d8c8ef31 /src/boot | |
| parent | 39523baf8c52d59b8ca52832c22e70eadf518f8a (diff) | |
Non libultra Boot Cleanup (#1370)
* reorganize
* math64
* rcp_utils
* osSyncPrintfUnused
* comment spacing
Diffstat (limited to 'src/boot')
32 files changed, 4235 insertions, 0 deletions
diff --git a/src/boot/CIC6105.c b/src/boot/CIC6105.c new file mode 100644 index 000000000..e18223b03 --- /dev/null +++ b/src/boot/CIC6105.c @@ -0,0 +1,32 @@ +#include "prevent_bss_reordering.h" +#include "global.h" +#include "fault.h" + +UNK_TYPE4 D_8009BE30; +UNK_TYPE4 D_8009BE34; +FaultClient romInfoFaultClient; + +void CIC6105_Nop80081820(void) { +} + +void CIC6105_Nop80081828(void) { +} + +void CIC6105_PrintRomInfo(void) { + FaultDrawer_DrawText(80, 200, "SP_STATUS %08x", HW_REG(SP_STATUS_REG, u32)); + FaultDrawer_DrawText(40, 184, "ROM_F [Creator:%s]", gBuildTeam); + FaultDrawer_DrawText(56, 192, "[Date:%s]", gBuildDate); +} + +void CIC6105_AddRomInfoFaultPage(void) { + Fault_AddClient(&romInfoFaultClient, (void*)CIC6105_PrintRomInfo, NULL, NULL); +} + +void CIC6105_RemoveRomInfoFaultPage(void) { + Fault_RemoveClient(&romInfoFaultClient); +} + +void func_800818F4(void) { + D_8009BE30 = *(u32*)0xA02FB1F4; + D_8009BE34 = *(u32*)0xA02FE1C0; +} diff --git a/src/boot/O2/__osMalloc.c b/src/boot/O2/__osMalloc.c new file mode 100644 index 000000000..71a5a5ba3 --- /dev/null +++ b/src/boot/O2/__osMalloc.c @@ -0,0 +1,458 @@ +#include "os_malloc.h" +#include "libc/stdbool.h" +#include "libc/stdint.h" +#include "macros.h" +#include "functions.h" + +#define FILL_ALLOCBLOCK (1 << 0) +#define FILL_FREEBLOCK (1 << 1) +#define CHECK_FREE_BLOCK (1 << 2) + +#define NODE_MAGIC (0x7373) + +#define BLOCK_UNINIT_MAGIC (0xAB) +#define BLOCK_UNINIT_MAGIC_32 (0xABABABAB) +#define BLOCK_ALLOC_MAGIC (0xCD) +#define BLOCK_ALLOC_MAGIC_32 (0xCDCDCDCD) +#define BLOCK_FREE_MAGIC (0xEF) +#define BLOCK_FREE_MAGIC_32 (0xEFEFEFEF) + +OSMesg sArenaLockMsg[1]; + +void __osMallocAddHeap(Arena* arena, void* heap, size_t size); + +void ArenaImpl_LockInit(Arena* arena) { + osCreateMesgQueue(&arena->lock, sArenaLockMsg, ARRAY_COUNT(sArenaLockMsg)); +} + +void ArenaImpl_Lock(Arena* arena) { + osSendMesg(&arena->lock, NULL, OS_MESG_BLOCK); +} + +void ArenaImpl_Unlock(Arena* arena) { + osRecvMesg(&arena->lock, NULL, OS_MESG_BLOCK); +} + +ArenaNode* ArenaImpl_GetLastBlock(Arena* arena) { + ArenaNode* last; + ArenaNode* iter; + + last = arena->head; + + if (last != NULL) { + iter = last->next; + while (iter != NULL) { + last = iter; + iter = iter->next; + } + } + return last; +} + +/** + * Initializes \p arena to manage the memory region \p heap. + * + * @param arena The Arena to initialize. + * @param heap The memory region to use as heap space. + * @param size The size of the heap. + */ +void __osMallocInit(Arena* arena, void* heap, size_t size) { + bzero(arena, sizeof(Arena)); + + ArenaImpl_LockInit(arena); + + __osMallocAddHeap(arena, heap, size); + arena->isInit = true; +} + +// Original name: __osMallocAddBlock +void __osMallocAddHeap(Arena* arena, void* heap, size_t size) { + ptrdiff_t diff; + s32 alignedSize; + ArenaNode* firstNode; + ArenaNode* lastNode; + + if (heap == NULL) { + return; + } + + firstNode = (ArenaNode*)ALIGN16((uintptr_t)heap); + diff = (uintptr_t)firstNode - (uintptr_t)heap; + alignedSize = ((s32)size - diff) & ~0xF; + + // If the size of the heap is smaller than sizeof(ArenaNode), then the initialization will silently fail + if (alignedSize > (s32)sizeof(ArenaNode)) { + firstNode->next = NULL; + firstNode->prev = NULL; + firstNode->size = alignedSize - sizeof(ArenaNode); + firstNode->isFree = true; + firstNode->magic = NODE_MAGIC; + + ArenaImpl_Lock(arena); + + lastNode = ArenaImpl_GetLastBlock(arena); + + // Checks if there's already a block + if (lastNode == NULL) { + arena->head = firstNode; + arena->start = heap; + } else { + // Chain the existing block with the new one + firstNode->prev = lastNode; + lastNode->next = firstNode; + } + + ArenaImpl_Unlock(arena); + } +} + +/** + * Clears the whole \p arena, invalidating every allocated pointer to it. + * + * @param arena The Arena to clear. + */ +void __osMallocCleanup(Arena* arena) { + bzero(arena, sizeof(Arena)); +} + +/** + * Returns whether or not the \p arena has been initialized. + * + * @param arena The Arena to check. + * @return u8 `true` if the \p arena has been initialized. `false` otherwise. + */ +u8 __osMallocIsInitalized(Arena* arena) { + return arena->isInit; +} + +/** + * Allocates at least \p size bytes of memory using the given \p arena. + * The block of memory will be allocated at the start of the first sufficiently large free block. + * + * - If there's not enough space in the given \p arena, this function will fail, returning `NULL`. + * - If \p size is zero, then an empty region of memory is returned. + * + * To avoid memory leaks, the returned pointer should be eventually deallocated using either `__osFree` or + * `__osRealloc`. + * + * @param[in, out] arena The specific Arena to be used for the allocation. + * @param[in] size The size in bytes that will be allocated. + * @return void* On success, the allocated area of the \p arena memory. Otherwise, `NULL`. + */ +void* __osMalloc(Arena* arena, size_t size) { + ArenaNode* iter; + ArenaNode* newNode; + void* alloc = NULL; + + size = ALIGN16(size); + + ArenaImpl_Lock(arena); + + // Start iterating from the head of the arena. + iter = arena->head; + + // Iterate over the arena looking for a big enough space of memory. + while (iter != NULL) { + if (iter->isFree && iter->size >= size) { + size_t blockSize = ALIGN16(size) + sizeof(ArenaNode); + + // If the block is larger than the requested size, then split it and just use the required size of the + // current block. + if (blockSize < iter->size) { + ArenaNode* next; + + newNode = (ArenaNode*)((uintptr_t)iter + blockSize); + newNode->next = iter->next; + newNode->prev = iter; + newNode->size = iter->size - blockSize; + newNode->isFree = true; + newNode->magic = NODE_MAGIC; + + iter->next = newNode; + iter->size = size; + + next = newNode->next; + if (next != NULL) { + next->prev = newNode; + } + } + + iter->isFree = false; + alloc = (void*)((uintptr_t)iter + sizeof(ArenaNode)); + break; + } + + iter = iter->next; + } + + ArenaImpl_Unlock(arena); + + return alloc; +} + +/** + * Allocates at least \p size bytes of memory using the given \p arena. + * Unlike __osMalloc, the block of memory will be allocated from the end of the \p arena. + * + * - If there's not enough space in the given \p arena, this function will fail, returning `NULL`. + * - If \p size is zero, then an empty region of memory is returned. + * + * To avoid memory leaks, the returned pointer should be eventually deallocated using `__osFree` or `__osRealloc`. + * + * @param[in, out] arena The specific Arena to be used for the allocation. + * @param[in] size The size in bytes that will be allocated. + * @return void* On success, the allocated area of the \p arena memory. Otherwise, `NULL`. + */ +void* __osMallocR(Arena* arena, size_t size) { + ArenaNode* iter; + ArenaNode* newNode; + size_t blockSize; + void* alloc = NULL; + + size = ALIGN16(size); + + ArenaImpl_Lock(arena); + + // Start iterating from the last block of the arena. + iter = ArenaImpl_GetLastBlock(arena); + + // Iterate in reverse the arena looking for a big enough space of memory. + while (iter != NULL) { + if (iter->isFree && iter->size >= size) { + blockSize = ALIGN16(size) + sizeof(ArenaNode); + + // If the block is larger than the requested size, then split it and just use the required size of the + // current block. + if (blockSize < iter->size) { + ArenaNode* next; + + newNode = (ArenaNode*)((uintptr_t)iter + (iter->size - size)); + newNode->next = iter->next; + newNode->prev = iter; + newNode->size = size; + newNode->magic = NODE_MAGIC; + + iter->next = newNode; + iter->size -= blockSize; + + next = newNode->next; + if (next != NULL) { + next->prev = newNode; + } + iter = newNode; + } + + iter->isFree = false; + alloc = (void*)((uintptr_t)iter + sizeof(ArenaNode)); + break; + } + iter = iter->prev; + } + + ArenaImpl_Unlock(arena); + + return alloc; +} + +/** + * Deallocates the pointer \p ptr previously allocated by `__osMalloc`, `__osMallocR` or `__osRealloc`. + * If \p ptr is `NULL` or it has been already been freed, then this function does nothing. + * + * - The behaviour is undefined if \p ptr is not a memory region returned by one of the cited allocating + * functions. + * - The behaviour is undefined if \p ptr doesn't correspond to the given \p arena. + * - Any access to the freed pointer is undefined behaviour. + * + * @param[in, out] arena The specific Arena to be used for the allocation. + * @param[in, out] ptr The allocated memory block to deallocate. + */ +void __osFree(Arena* arena, void* ptr) { + ArenaNode* node; + ArenaNode* next; + ArenaNode* prev; + + ArenaImpl_Lock(arena); + + node = (ArenaNode*)((uintptr_t)ptr - sizeof(ArenaNode)); + + if ((ptr != NULL) && (node->magic == NODE_MAGIC) && !node->isFree) { + next = node->next; + prev = node->prev; + node->isFree = true; + + // Checks if the next node is contiguous to the current node and if it isn't currently allocated. Then merge the + // two nodes into one. + if ((uintptr_t)next == (uintptr_t)node + sizeof(ArenaNode) + node->size && next->isFree) { + ArenaNode* newNext = next->next; + + if (newNext != NULL) { + newNext->prev = node; + } + + node->size += next->size + sizeof(ArenaNode); + + node->next = newNext; + next = newNext; + } + + // Checks if the previous node is contiguous to the current node and if it isn't currently allocated. Then merge + // the two nodes into one. + if ((prev != NULL) && prev->isFree && ((uintptr_t)node == (uintptr_t)prev + sizeof(ArenaNode) + prev->size)) { + if (next != NULL) { + next->prev = prev; + } + + prev->next = next; + prev->size += node->size + sizeof(ArenaNode); + } + } + + ArenaImpl_Unlock(arena); +} + +/** + * Reallocates the pointer \p ptr. + * \p ptr must be either a pointer previously allocated by `__osMalloc`, `__osMallocR` or `__osRealloc` and + * not freed yet, or a `NULL` pointer. + * + * - If \p ptr is `NULL` a new pointer is allocated. See `__osMalloc` for more details. + * - If \p newSize is 0, then the given pointer is freed and `NULL` is returned. See `__osFree` for more details. + * - If \p newSize is bigger than the currently allocated allocated pointer, then the area of memory is expanded to a + * size big enough to fit the requested size. + * + * - The behaviour is undefined if \p ptr is not a memory region returned by one of the cited allocating + * functions. + * - The behaviour is undefined if \p ptr doesn't correspond to the given \p arena. + * - If the pointer is freed, then any access to the original freed pointer is undefined behaviour. + * + * @param[in, out] arena The specific Arena to be used for the allocation. + * @param[in, out] ptr The allocated memory block to deallocate. + * @param[in] newSize The new requested size. + * @return void* On success, the pointer to the reallocated area of memory. On failure, `NULL` is returned, + * and the original parameter \p ptr remains valid. + */ +void* __osRealloc(Arena* arena, void* ptr, size_t newSize) { + ArenaImpl_Lock(arena); + + (void)"__osRealloc(%08x, %d)\n"; + + if (ptr == NULL) { + // if the `ptr` is NULL, then allocate a new pointer with the specified size + // if newSize is 0, then __osMalloc would return a NULL pointer + ptr = __osMalloc(arena, newSize); + } else if (newSize == 0) { + // if the requested size is zero, then free the pointer + __osFree(arena, ptr); + ptr = NULL; + } else { + size_t diff; + void* newPtr; + // Gets the start of the ArenaNode pointer embedded + ArenaNode* node = (void*)((uintptr_t)ptr - sizeof(ArenaNode)); + + newSize = ALIGN16(newSize); + + // Only reallocate the memory if the new size isn't smaller than the actual node size + if ((newSize != node->size) && (node->size < newSize)) { + ArenaNode* next = node->next; + + diff = newSize - node->size; + // Checks if the next node is contiguous to the current allocated node and it has enough space to fit the + // new requested size + if (((uintptr_t)next == (uintptr_t)node + node->size + sizeof(ArenaNode)) && (next->isFree) && + (next->size >= diff)) { + ArenaNode* next2 = next->next; + + next->size = (next->size - diff); + if (next2 != NULL) { + // Update the previous element of the linked list + next2->prev = (void*)((uintptr_t)next + diff); + } + + next2 = (void*)((uintptr_t)next + diff); + node->next = next2; + node->size = newSize; + __osMemcpy(next2, next, sizeof(ArenaNode)); + } else { + // Create a new pointer and manually copy the data from the old pointer to the new one + newPtr = __osMalloc(arena, newSize); + if (newPtr != NULL) { + bcopy(newPtr, ptr, node->size); + __osFree(arena, ptr); + } + ptr = newPtr; + } + } + } + + ArenaImpl_Unlock(arena); + + return ptr; +} + +/** + * Gets the size of the largest free block, the total free space and the total allocated space. + * + * @param[in, out] arena The Arena which will be used to get the values from. + * @param[out] outMaxFree The size of the largest free block. + * @param[out] outFree The total free space. + * @param[out] outAlloc The total allocated space. + */ +void __osGetSizes(Arena* arena, size_t* outMaxFree, size_t* outFree, size_t* outAlloc) { + ArenaNode* iter; + + ArenaImpl_Lock(arena); + + *outMaxFree = 0; + *outFree = 0; + *outAlloc = 0; + + iter = arena->head; + while (iter != NULL) { + if (iter->isFree) { + *outFree += iter->size; + if (*outMaxFree < iter->size) { + *outMaxFree = iter->size; + } + } else { + *outAlloc += iter->size; + } + + iter = iter->next; + } + + ArenaImpl_Unlock(arena); +} + +/** + * Checks the validity of every node of the \p arena. + * + * @param arena The Arena to check. + * @return s32 0 if every pointer is valid. 1 otherwise. + */ +s32 __osCheckArena(Arena* arena) { + ArenaNode* iter; + s32 err = 0; + + ArenaImpl_Lock(arena); + + // "Checking the contents of the arena..." + (void)"アリーナの内容をチェックしています... (%08x)\n"; + + for (iter = arena->head; iter != NULL; iter = iter->next) { + if (iter->magic != NODE_MAGIC) { + // "Oops!!" + (void)"おおっと!! (%08x %08x)\n"; + + err = 1; + break; + } + } + + // "The arena still looks good" + (void)"アリーナはまだ、いけそうです\n"; + + ArenaImpl_Unlock(arena); + + return err; +} diff --git a/src/boot/O2/__osMemcpy.c b/src/boot/O2/__osMemcpy.c new file mode 100644 index 000000000..2e25b23f0 --- /dev/null +++ b/src/boot/O2/__osMemcpy.c @@ -0,0 +1,23 @@ +#include "global.h" + +void* __osMemcpy(void* dst, void* src, size_t size) { + u8* _dst = dst; + u8* _src = src; + register s32 rem; + + if (_dst == _src) { + return dst; + } + if (_dst < _src) { + for (rem = size--; rem != 0; rem = size--) { + *_dst++ = *_src++; + } + } else { + _dst += size - 1; + _src += size - 1; + for (rem = size--; rem != 0; rem = size--) { + *_dst-- = *_src--; + } + } + return dst; +} diff --git a/src/boot/O2/__osMemset.c b/src/boot/O2/__osMemset.c new file mode 100644 index 000000000..0c4172e9f --- /dev/null +++ b/src/boot/O2/__osMemset.c @@ -0,0 +1,11 @@ +#include "global.h" + +void* __osMemset(void* ptr, s32 val, size_t size) { + u8* dst = ptr; + register s32 rem; + + for (rem = size--; rem != 0; rem = size--) { + *dst++ = val; + } + return ptr; +} diff --git a/src/boot/O2/__osStrcmp.c b/src/boot/O2/__osStrcmp.c new file mode 100644 index 000000000..10bd2fa92 --- /dev/null +++ b/src/boot/O2/__osStrcmp.c @@ -0,0 +1,16 @@ +#include "global.h" + +s32 __osStrcmp(const char* str1, const char* str2) { + char c1; + char c2; + + do { + c1 = *str1++; + c2 = *str2++; + if (c1 != c2) { + return c1 - c2; + } + } while (c1); + + return 0; +} diff --git a/src/boot/O2/__osStrcpy.c b/src/boot/O2/__osStrcpy.c new file mode 100644 index 000000000..86a98a804 --- /dev/null +++ b/src/boot/O2/__osStrcpy.c @@ -0,0 +1,12 @@ +#include "global.h" + +char* __osStrcpy(char* dst, const char* src) { + char* _dst = dst; + + while (*src != '\0') { + *_dst++ = *src++; + } + *_dst = '\0'; + + return dst; +} diff --git a/src/boot/O2/debug.c b/src/boot/O2/debug.c new file mode 100644 index 000000000..3933b585e --- /dev/null +++ b/src/boot/O2/debug.c @@ -0,0 +1,11 @@ +#include "global.h" +#include "fault.h" + +void _dbg_hungup(const char* file, int lineNum) { + osGetThreadId(NULL); + Fault_AddHungupAndCrash(file, lineNum); +} + +void Reset(void) { + Fault_AddHungupAndCrash("Reset", 0); +} diff --git a/src/boot/O2/fmodf.c b/src/boot/O2/fmodf.c new file mode 100644 index 000000000..2f8aa74bf --- /dev/null +++ b/src/boot/O2/fmodf.c @@ -0,0 +1,12 @@ +#include "global.h" + +f32 fmodf(f32 dividend, f32 divisor) { + s32 quotient; + + if (divisor == 0.0f) { + return 0.0f; + } + quotient = dividend / divisor; + + return dividend - quotient * divisor; +} diff --git a/src/boot/O2/gfxprint.c b/src/boot/O2/gfxprint.c new file mode 100644 index 000000000..b52675829 --- /dev/null +++ b/src/boot/O2/gfxprint.c @@ -0,0 +1,236 @@ +#include "global.h" + +#define GFXP_FLAG_HIRAGANA (1 << 0) +#define GFXP_FLAG_RAINBOW (1 << 1) +#define GFXP_FLAG_SHADOW (1 << 2) +#define GFXP_FLAG_UPDATE (1 << 3) +#define GFXP_FLAG_ENLARGE (1 << 6) +#define GFXP_FLAG_OPEN (1 << 7) + +//! TODO: Need to extract +extern u16 sGfxPrintFontTLUT[64]; +extern u16 sGfxPrintRainbowTLUT[16]; +extern u8 sGfxPrintRainbowData[8]; +extern u8 sGfxPrintFontData[2048]; + +void GfxPrint_Setup(GfxPrint* this) { + s32 width = 16; + s32 height = 256; + s32 i; + + gDPPipeSync(this->dList++); + gDPSetOtherMode(this->dList++, + G_AD_DISABLE | G_CD_DISABLE | G_CK_NONE | G_TC_FILT | G_TF_BILERP | G_TT_IA16 | G_TL_TILE | + G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE, + G_AC_NONE | G_ZS_PRIM | G_RM_XLU_SURF | G_RM_XLU_SURF2); + gDPSetCombineMode(this->dList++, G_CC_DECALRGBA, G_CC_DECALRGBA); + gDPLoadTextureBlock_4b(this->dList++, sGfxPrintFontData, G_IM_FMT_CI, width, height, 0, G_TX_NOMIRROR | G_TX_WRAP, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); + gDPLoadTLUT(this->dList++, 64, 256, sGfxPrintFontTLUT); + + for (i = 1; i < 4; i++) { + gDPSetTile(this->dList++, G_IM_FMT_CI, G_IM_SIZ_4b, 1, 0, i * 2, i, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, + G_TX_NOLOD, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOLOD); + gDPSetTileSize(this->dList++, i * 2, 0, 0, 60, 1020); + } + + gDPSetColor(this->dList++, G_SETPRIMCOLOR, this->color.rgba); + + gDPLoadMultiTile_4b(this->dList++, sGfxPrintRainbowData, 0, 1, G_IM_FMT_CI, 2, 8, 0, 0, 1, 7, 4, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 1, 3, G_TX_NOLOD, G_TX_NOLOD); + + gDPLoadTLUT(this->dList++, 16, 320, sGfxPrintRainbowTLUT); + + for (i = 1; i < 4; i++) { + gDPSetTile(this->dList++, G_IM_FMT_CI, G_IM_SIZ_4b, 1, 0, i * 2 + 1, 4, G_TX_NOMIRROR | G_TX_WRAP, 3, + G_TX_NOLOD, G_TX_NOMIRROR | G_TX_WRAP, 1, G_TX_NOLOD); + gDPSetTileSize(this->dList++, i * 2 + 1, 0, 0, 4, 28); + } +} + +void GfxPrint_SetColor(GfxPrint* this, u32 r, u32 g, u32 b, u32 a) { + this->color.r = r; + this->color.g = g; + this->color.b = b; + this->color.a = a; + gDPPipeSync(this->dList++); + gDPSetColor(this->dList++, G_SETPRIMCOLOR, this->color.rgba); +} + +void GfxPrint_SetPosPx(GfxPrint* this, s32 x, s32 y) { + this->posX = this->baseX + (x << 2); + this->posY = this->baseY + (y << 2); +} + +void GfxPrint_SetPos(GfxPrint* this, s32 x, s32 y) { + GfxPrint_SetPosPx(this, x << 3, y << 3); +} + +void GfxPrint_SetBasePosPx(GfxPrint* this, s32 x, s32 y) { + this->baseX = x << 2; + this->baseY = y << 2; +} + +void GfxPrint_PrintCharImpl(GfxPrint* this, u8 c) { + u32 tile = (c & 0xFF) * 2; + u16 s = c & 4; + u16 t = c >> 3; + + if (this->flags & GFXP_FLAG_UPDATE) { + this->flags &= ~GFXP_FLAG_UPDATE; + + gDPPipeSync(this->dList++); + if (this->flags & GFXP_FLAG_RAINBOW) { + gDPSetTextureLUT(this->dList++, G_TT_RGBA16); + gDPSetCycleType(this->dList++, G_CYC_2CYCLE); + gDPSetRenderMode(this->dList++, G_RM_PASS, G_RM_XLU_SURF2); + gDPSetCombineMode(this->dList++, G_CC_INTERFERENCE, G_CC_PASS2); + } else { + gDPSetTextureLUT(this->dList++, G_TT_IA16); + gDPSetCycleType(this->dList++, G_CYC_1CYCLE); + gDPSetRenderMode(this->dList++, G_RM_XLU_SURF, G_RM_XLU_SURF2); + gDPSetCombineMode(this->dList++, G_CC_MODULATEIDECALA_PRIM, G_CC_MODULATEIDECALA_PRIM); + } + } + + if (this->flags & GFXP_FLAG_SHADOW) { + gDPSetColor(this->dList++, G_SETPRIMCOLOR, 0); + + gSPTextureRectangle(this->dList++, this->posX + 4, this->posY + 4, this->posX + 4 + 32, this->posY + 4 + 32, + tile, s << 6, t << 8, 1 << 10, 1 << 10); + + gDPSetColor(this->dList++, G_SETPRIMCOLOR, this->color.rgba); + } + + gSPTextureRectangle(this->dList++, this->posX, this->posY, this->posX + 32, this->posY + 32, tile, s << 6, t << 8, + 1 << 10, 1 << 10); + + this->posX += 32; +} + +void GfxPrint_PrintChar(GfxPrint* this, u8 c) { + if (c == ' ') { + this->posX += 32; + } else if (c > ' ' && c < 0x7F) { + GfxPrint_PrintCharImpl(this, c); + } else if (c >= 0xA0 && c < 0xE0) { + if (this->flags & GFXP_FLAG_HIRAGANA) { + if (c < 0xC0) { + c -= 0x20; + } else { + c += 0x20; + } + } + GfxPrint_PrintCharImpl(this, c); + } else { + switch (c) { + case '\0': + break; + case '\n': + this->posY += 32; + case '\r': + this->posX = this->baseX; + break; + case '\t': + do { + GfxPrint_PrintCharImpl(this, ' '); + } while ((this->posX - this->baseX) % 256); + break; + case GFXP_HIRAGANA_CHAR: + this->flags |= GFXP_FLAG_HIRAGANA; + break; + case GFXP_KATAKANA_CHAR: + this->flags &= ~GFXP_FLAG_HIRAGANA; + break; + case GFXP_RAINBOW_ON_CHAR: + this->flags |= GFXP_FLAG_RAINBOW; + this->flags |= GFXP_FLAG_UPDATE; + break; + case GFXP_RAINBOW_OFF_CHAR: + this->flags &= ~GFXP_FLAG_RAINBOW; + this->flags |= GFXP_FLAG_UPDATE; + break; + case GFXP_UNUSED_CHAR: + default: + break; + } + } +} + +void GfxPrint_PrintStringWithSize(GfxPrint* this, const void* buffer, size_t charSize, size_t charCount) { + const char* str = (const char*)buffer; + size_t count = charSize * charCount; + + while (count != 0) { + GfxPrint_PrintChar(this, *str++); + count--; + } +} + +void GfxPrint_PrintString(GfxPrint* this, const char* str) { + while (*str != '\0') { + GfxPrint_PrintChar(this, *str++); + } +} + +void* GfxPrint_Callback(void* arg, const char* str, size_t size) { + GfxPrint* this = arg; + + GfxPrint_PrintStringWithSize(this, str, sizeof(char), size); + + return this; +} + +void GfxPrint_Init(GfxPrint* this) { + this->flags &= ~GFXP_FLAG_OPEN; + + this->callback = GfxPrint_Callback; + this->dList = NULL; + this->posX = 0; + this->posY = 0; + this->baseX = 0; + this->baseY = 0; + this->color.rgba = 0; + + this->flags &= ~GFXP_FLAG_HIRAGANA; + this->flags &= ~GFXP_FLAG_RAINBOW; + this->flags |= GFXP_FLAG_SHADOW; + this->flags |= GFXP_FLAG_UPDATE; +} + +void GfxPrint_Destroy(GfxPrint* this) { +} + +void GfxPrint_Open(GfxPrint* this, Gfx* dList) { + if (!(this->flags & GFXP_FLAG_OPEN)) { + this->flags |= GFXP_FLAG_OPEN; + this->dList = dList; + GfxPrint_Setup(this); + } +} + +Gfx* GfxPrint_Close(GfxPrint* this) { + Gfx* ret; + + this->flags &= ~GFXP_FLAG_OPEN; + ret = this->dList; + this->dList = NULL; + + return ret; +} + +s32 GfxPrint_VPrintf(GfxPrint* this, const char* fmt, va_list args) { + return PrintUtils_VPrintf(&this->callback, fmt, args); +} + +s32 GfxPrint_Printf(GfxPrint* this, const char* fmt, ...) { + s32 ret; + va_list args; + va_start(args, fmt); + + ret = GfxPrint_VPrintf(this, fmt, args); + + va_end(args); + + return ret; +} diff --git a/src/boot/O2/loadfragment.c b/src/boot/O2/loadfragment.c new file mode 100644 index 000000000..b22b1a692 --- /dev/null +++ b/src/boot/O2/loadfragment.c @@ -0,0 +1,231 @@ +/** + * @file loadfragment.c + * + * Functions used to process and relocate dynamically loadable code segments (overlays). + * + * @note: + * These are completly unused in favor of the fragment overlay functions in `loadfragment2.c`. + * + * The main difference between them seems to be the lack of vramEnd arguments here. + * Instead they are calculated on the fly. + */ + +#include "global.h" +#include "system_malloc.h" +#include "loadfragment.h" + +s32 gLoadLogSeverity = 2; + +// Extract MIPS register rs from an instruction word +#define MIPS_REG_RS(insn) (((insn) >> 0x15) & 0x1F) + +// Extract MIPS register rt from an instruction word +#define MIPS_REG_RT(insn) (((insn) >> 0x10) & 0x1F) + +// Extract MIPS jump target from an instruction word +#define MIPS_JUMP_TARGET(insn) (((insn)&0x03FFFFFF) << 2) + +/** + * Performs runtime relocation of overlay files, loadable code segments. + * + * Overlays are expected to be loadable anywhere in direct-mapped cached (KSEG0) memory, with some appropriate + * alignment requirements; memory addresses in such code must be updated once loaded to execute properly. + * When compiled, overlays are given 'fake' KSEG0 RAM addresses larger than the total possible available main memory + * (>= 0x80800000), such addresses are referred to as Virtual RAM (VRAM) to distinguish them. When loading the overlay, + * the relocation table produced at compile time is consulted to determine where and how to update these VRAM addresses + * to correct RAM addresses based on the location the overlay was loaded at, enabling the code to execute at this + * address as if it were compiled to run at this address. + * + * Each relocation is represented by a packed 32-bit value, formatted in the following way: + * - [31:30] 2-bit section id, taking values from the `RelocSectionId` enum. + * - [29:24] 6-bit relocation type describing which relocation operation should be performed. Same as ELF32 MIPS. + * - [23: 0] 24-bit section-relative offset indicating where in the section to apply this relocation. + * + * @param allocatedRamAddr Memory address the binary was loaded at. + * @param ovlRelocs Overlay relocation section containing overlay section layout and runtime relocations. + * @param vramStart Virtual RAM address that the overlay was compiled at. + */ +void Fragment_Relocate(void* allocatedRamAddr, OverlayRelocationSection* ovlRelocs, uintptr_t vramStart) { + u32 sections[RELOC_SECTION_MAX]; + u32* relocDataP; + u32 reloc; + uintptr_t relocatedAddress; + u32 i; + u32* luiInstRef; + uintptr_t allocu32 = (uintptr_t)allocatedRamAddr; + u32* regValP; + //! MIPS ELF relocation does not generally require tracking register values, so at first glance it appears this + //! register tracking was an unnecessary complication. However there is a bug in the IDO compiler that can cause + //! relocations to be emitted in the wrong order under rare circumstances when the compiler attempts to reuse a + //! previous HI16 relocation for a different LO16 relocation as an optimization. This register tracking is likely + //! a workaround to prevent improper matching of unrelated HI16 and LO16 relocations that would otherwise arise + //! due to the incorrect ordering. + u32* luiRefs[32]; + u32 luiVals[32]; + u32 isLoNeg; + + if (gLoadLogSeverity >= 3) {} + + sections[RELOC_SECTION_NULL] = 0; + sections[RELOC_SECTION_TEXT] = allocu32; + sections[RELOC_SECTION_DATA] = allocu32 + ovlRelocs->textSize; + sections[RELOC_SECTION_RODATA] = sections[RELOC_SECTION_DATA] + ovlRelocs->dataSize; + + for (i = 0; i < ovlRelocs->numRelocations; i++) { + // This will always resolve to a 32-bit aligned address as each section + // containing code or pointers must be aligned to at least 4 bytes and the + // MIPS ABI defines the offset of both 16-bit and 32-bit relocations to be + // the start of the 32-bit word containing the target. + reloc = ovlRelocs->relocations[i]; + relocDataP = (u32*)(sections[RELOC_SECTION(reloc)] + RELOC_OFFSET(reloc)); + + switch (RELOC_TYPE_MASK(reloc)) { + case R_MIPS_32 << RELOC_TYPE_SHIFT: + // Handles 32-bit address relocation, used for things such as jump tables and pointers in data. + // Just relocate the full address + + // Check address is valid for relocation + if ((*relocDataP & 0x0F000000) == 0) { + *relocDataP = *relocDataP - vramStart + allocu32; + } else if (gLoadLogSeverity >= 3) { + } + break; + + case R_MIPS_26 << RELOC_TYPE_SHIFT: + // Handles 26-bit address relocation, used for jumps and jals. + // Extract the address from the target field of the J-type MIPS instruction. + // Relocate the address and update the instruction. + + if (1) { + *relocDataP = + (*relocDataP & 0xFC000000) | + (((PHYS_TO_K0(MIPS_JUMP_TARGET(*relocDataP)) - vramStart + allocu32) & 0x0FFFFFFF) >> 2); + } + break; + + case R_MIPS_HI16 << RELOC_TYPE_SHIFT: + // Handles relocation for a hi/lo pair, part 1. + // Store the reference to the LUI instruction (hi) using the `rt` register of the instruction. + // This will be updated later in the `R_MIPS_LO16` section. + + luiRefs[(*relocDataP >> 0x10) & 0x1F] = relocDataP; + luiVals[(*relocDataP >> 0x10) & 0x1F] = *relocDataP; + break; + + case R_MIPS_LO16 << RELOC_TYPE_SHIFT: + // Handles relocation for a hi/lo pair, part 2. + // Grab the stored LUI (hi) from the `R_MIPS_HI16` section using the `rs` register of the instruction. + // The full address is calculated, relocated, and then used to update both the LUI and lo instructions. + // If the lo part is negative, add 1 to the LUI value. + // Note: The lo instruction is assumed to have a signed immediate. + + luiInstRef = luiRefs[(*relocDataP >> 0x15) & 0x1F]; + regValP = &luiVals[(*relocDataP >> 0x15) & 0x1F]; + + // Check address is valid for relocation + if ((((*luiInstRef << 0x10) + (s16)*relocDataP) & 0x0F000000) == 0) { + relocatedAddress = ((*regValP << 0x10) + (s16)*relocDataP) - vramStart + allocu32; + isLoNeg = (relocatedAddress & 0x8000) ? 1 : 0; + *luiInstRef = (*luiInstRef & 0xFFFF0000) | (((relocatedAddress >> 0x10) & 0xFFFF) + isLoNeg); + *relocDataP = (*relocDataP & 0xFFFF0000) | (relocatedAddress & 0xFFFF); + } else if (gLoadLogSeverity >= 3) { + } + break; + } + } +} + +size_t Fragment_Load(uintptr_t vromStart, uintptr_t vromEnd, uintptr_t vramStart, void* allocatedRamAddr, + size_t allocatedBytes) { + size_t size = vromEnd - vromStart; + void* end; + s32 pad; + OverlayRelocationSection* ovlRelocs; + + if (gLoadLogSeverity >= 3) {} + if (gLoadLogSeverity >= 3) {} + + end = (uintptr_t)allocatedRamAddr + size; + DmaMgr_SendRequest0(allocatedRamAddr, vromStart, size); + + ovlRelocs = (OverlayRelocationSection*)((uintptr_t)end - ((s32*)end)[-1]); + + if (gLoadLogSeverity >= 3) {} + + if (allocatedBytes < ovlRelocs->bssSize + size) { + if (gLoadLogSeverity >= 3) {} + return 0; + } + + allocatedBytes = ovlRelocs->bssSize + size; + + if (gLoadLogSeverity >= 3) {} + + Fragment_Relocate(allocatedRamAddr, ovlRelocs, vramStart); + + if (ovlRelocs->bssSize != 0) { + if (gLoadLogSeverity >= 3) {} + bzero(end, ovlRelocs->bssSize); + } + + osWritebackDCache(allocatedRamAddr, allocatedBytes); + osInvalICache(allocatedRamAddr, allocatedBytes); + + if (gLoadLogSeverity >= 3) {} + + return allocatedBytes; +} + +void* Fragment_AllocateAndLoad(uintptr_t vromStart, uintptr_t vromEnd, uintptr_t vramStart) { + size_t size = vromEnd - vromStart; + void* end; + void* allocatedRamAddr; + uintptr_t ovlOffset; + OverlayRelocationSection* ovlRelocs; + size_t allocatedBytes; + + if (gLoadLogSeverity >= 3) {} + + allocatedRamAddr = SystemArena_MallocR(size); + end = (uintptr_t)allocatedRamAddr + size; + + if (gLoadLogSeverity >= 3) {} + + DmaMgr_SendRequest0(allocatedRamAddr, vromStart, size); + + if (gLoadLogSeverity >= 3) {} + + ovlOffset = (uintptr_t)end - 4; + ovlRelocs = (OverlayRelocationSection*)((uintptr_t)end - ((s32*)end)[-1]); + + if (1) {} + + allocatedBytes = ovlRelocs->bssSize + size; + + allocatedRamAddr = SystemArena_Realloc(allocatedRamAddr, allocatedBytes); + + if (gLoadLogSeverity >= 3) {} + + if (allocatedRamAddr == NULL) { + if (gLoadLogSeverity >= 3) {} + return allocatedRamAddr; + } + + end = (uintptr_t)allocatedRamAddr + size; + ovlRelocs = (OverlayRelocationSection*)((uintptr_t)end - *(uintptr_t*)ovlOffset); + + if (gLoadLogSeverity >= 3) {} + + Fragment_Relocate(allocatedRamAddr, ovlRelocs, vramStart); + + if (ovlRelocs->bssSize != 0) { + if (gLoadLogSeverity >= 3) {} + bzero(end, ovlRelocs->bssSize); + } + + osInvalICache(allocatedRamAddr, allocatedBytes); + + if (gLoadLogSeverity >= 3) {} + + return allocatedRamAddr; +} diff --git a/src/boot/O2/loadfragment2.c b/src/boot/O2/loadfragment2.c new file mode 100644 index 000000000..6925aaaac --- /dev/null +++ b/src/boot/O2/loadfragment2.c @@ -0,0 +1,177 @@ +/** + * @file loadfragment2.c + * + * Functions used to process and relocate dynamically loadable code segments (overlays). + * + * @note: + * These are for specific fragment overlays with the .ovl file extension + */ +#include "global.h" +#include "system_malloc.h" +#include "loadfragment.h" + +s32 gOverlayLogSeverity = 2; + +// Extract MIPS register rs from an instruction word +#define MIPS_REG_RS(insn) (((insn) >> 0x15) & 0x1F) + +// Extract MIPS register rt from an instruction word +#define MIPS_REG_RT(insn) (((insn) >> 0x10) & 0x1F) + +// Extract MIPS jump target from an instruction word +#define MIPS_JUMP_TARGET(insn) (((insn)&0x03FFFFFF) << 2) + +/** + * Performs runtime relocation of overlay files, loadable code segments. + * + * Overlays are expected to be loadable anywhere in direct-mapped cached (KSEG0) memory, with some appropriate + * alignment requirements; memory addresses in such code must be updated once loaded to execute properly. + * When compiled, overlays are given 'fake' KSEG0 RAM addresses larger than the total possible available main memory + * (>= 0x80800000), such addresses are referred to as Virtual RAM (VRAM) to distinguish them. When loading the overlay, + * the relocation table produced at compile time is consulted to determine where and how to update these VRAM addresses + * to correct RAM addresses based on the location the overlay was loaded at, enabling the code to execute at this + * address as if it were compiled to run at this address. + * + * Each relocation is represented by a packed 32-bit value, formatted in the following way: + * - [31:30] 2-bit section id, taking values from the `RelocSectionId` enum. + * - [29:24] 6-bit relocation type describing which relocation operation should be performed. Same as ELF32 MIPS. + * - [23: 0] 24-bit section-relative offset indicating where in the section to apply this relocation. + * + * @param allocatedRamAddress Memory address the binary was loaded at. + * @param ovlRelocs Overlay relocation section containing overlay section layout and runtime relocations. + * @param vramStart Virtual RAM address that the overlay was compiled at. + */ +void Overlay_Relocate(void* allocatedRamAddr, OverlayRelocationSection* ovlRelocs, uintptr_t vramStart) { + u32 sections[RELOC_SECTION_MAX]; + u32* relocDataP; + u32 reloc; + uintptr_t relocatedAddress; + u32 i; + u32* luiInstRef; + uintptr_t allocu32 = (uintptr_t)allocatedRamAddr; + u32* regValP; + //! MIPS ELF relocation does not generally require tracking register values, so at first glance it appears this + //! register tracking was an unnecessary complication. However there is a bug in the IDO compiler that can cause + //! relocations to be emitted in the wrong order under rare circumstances when the compiler attempts to reuse a + //! previous HI16 relocation for a different LO16 relocation as an optimization. This register tracking is likely + //! a workaround to prevent improper matching of unrelated HI16 and LO16 relocations that would otherwise arise + //! due to the incorrect ordering. + u32* luiRefs[32]; + u32 luiVals[32]; + u32 isLoNeg; + + if (gOverlayLogSeverity >= 3) {} + + sections[RELOC_SECTION_NULL] = 0; + sections[RELOC_SECTION_TEXT] = allocu32; + sections[RELOC_SECTION_DATA] = allocu32 + ovlRelocs->textSize; + sections[RELOC_SECTION_RODATA] = sections[RELOC_SECTION_DATA] + ovlRelocs->dataSize; + + for (i = 0; i < ovlRelocs->numRelocations; i++) { + // This will always resolve to a 32-bit aligned address as each section + // containing code or pointers must be aligned to at least 4 bytes and the + // MIPS ABI defines the offset of both 16-bit and 32-bit relocations to be + // the start of the 32-bit word containing the target. + reloc = ovlRelocs->relocations[i]; + relocDataP = (u32*)(sections[RELOC_SECTION(reloc)] + RELOC_OFFSET(reloc)); + + switch (RELOC_TYPE_MASK(reloc)) { + case R_MIPS_32 << RELOC_TYPE_SHIFT: + // Handles 32-bit address relocation, used for things such as jump tables and pointers in data. + // Just relocate the full address + + // Check address is valid for relocation + if ((*relocDataP & 0x0F000000) == 0) { + *relocDataP = *relocDataP - vramStart + allocu32; + } else if (gOverlayLogSeverity >= 3) { + } + break; + + case R_MIPS_26 << RELOC_TYPE_SHIFT: + // Handles 26-bit address relocation, used for jumps and jals. + // Extract the address from the target field of the J-type MIPS instruction. + // Relocate the address and update the instruction. + + if (1) { + *relocDataP = + (*relocDataP & 0xFC000000) | + (((PHYS_TO_K0(MIPS_JUMP_TARGET(*relocDataP)) - vramStart + allocu32) & 0x0FFFFFFF) >> 2); + } + break; + + case R_MIPS_HI16 << RELOC_TYPE_SHIFT: + // Handles relocation for a hi/lo pair, part 1. + // Store the reference to the LUI instruction (hi) using the `rt` register of the instruction. + // This will be updated later in the `R_MIPS_LO16` section. + + luiRefs[(*relocDataP >> 0x10) & 0x1F] = relocDataP; + luiVals[(*relocDataP >> 0x10) & 0x1F] = *relocDataP; + break; + + case R_MIPS_LO16 << RELOC_TYPE_SHIFT: + // Handles relocation for a hi/lo pair, part 2. + // Grab the stored LUI (hi) from the `R_MIPS_HI16` section using the `rs` register of the instruction. + // The full address is calculated, relocated, and then used to update both the LUI and lo instructions. + // If the lo part is negative, add 1 to the LUI value. + // Note: The lo instruction is assumed to have a signed immediate. + + luiInstRef = luiRefs[(*relocDataP >> 0x15) & 0x1F]; + regValP = &luiVals[(*relocDataP >> 0x15) & 0x1F]; + + // Check address is valid for relocation + if ((((*luiInstRef << 0x10) + (s16)*relocDataP) & 0x0F000000) == 0) { + relocatedAddress = ((*regValP << 0x10) + (s16)*relocDataP) - vramStart + allocu32; + isLoNeg = (relocatedAddress & 0x8000) ? 1 : 0; + *luiInstRef = (*luiInstRef & 0xFFFF0000) | (((relocatedAddress >> 0x10) & 0xFFFF) + isLoNeg); + *relocDataP = (*relocDataP & 0xFFFF0000) | (relocatedAddress & 0xFFFF); + } else if (gOverlayLogSeverity >= 3) { + } + break; + } + } +} + +size_t Overlay_Load(uintptr_t vromStart, uintptr_t vromEnd, uintptr_t vramStart, uintptr_t vramEnd, + void* allocatedRamAddr) { + s32 pad[2]; + s32 size = vromEnd - vromStart; + void* end; + OverlayRelocationSection* ovlRelocs; + + if (gOverlayLogSeverity >= 3) {} + if (gOverlayLogSeverity >= 3) {} + + end = (uintptr_t)allocatedRamAddr + size; + DmaMgr_SendRequest0(allocatedRamAddr, vromStart, size); + + ovlRelocs = (OverlayRelocationSection*)((uintptr_t)end - ((s32*)end)[-1]); + + if (gOverlayLogSeverity >= 3) {} + if (gOverlayLogSeverity >= 3) {} + + Overlay_Relocate(allocatedRamAddr, ovlRelocs, vramStart); + + if (ovlRelocs->bssSize != 0) { + if (gOverlayLogSeverity >= 3) {} + bzero(end, ovlRelocs->bssSize); + } + + size = vramEnd - vramStart; + + osWritebackDCache(allocatedRamAddr, size); + osInvalICache(allocatedRamAddr, size); + + if (gOverlayLogSeverity >= 3) {} + + return size; +} + +void* Overlay_AllocateAndLoad(uintptr_t vromStart, uintptr_t vromEnd, uintptr_t vramStart, uintptr_t vramEnd) { + void* allocatedRamAddr = SystemArena_MallocR(vramEnd - vramStart); + + if (allocatedRamAddr != NULL) { + Overlay_Load(vromStart, vromEnd, vramStart, vramEnd, allocatedRamAddr); + } + + return allocatedRamAddr; +} diff --git a/src/boot/O2/math64.c b/src/boot/O2/math64.c new file mode 100644 index 000000000..cb77bed50 --- /dev/null +++ b/src/boot/O2/math64.c @@ -0,0 +1,191 @@ +/** + * MathF library + * Contains tangent function, wrappers for a number of the handwritten functions in fp, and a suite of arctangents + */ +#include "global.h" +#include "fixed_point.h" + +s32 gUseAtanContFrac; + +/** + * Tangent function computed using libultra sinf and cosf + */ +f32 Math_FTanF(f32 x) { + return sinf(x) / cosf(x); +} + +// Unused +f32 Math_FFloorF(f32 x) { + return floorf(x); +} + +// Unused +f32 Math_FCeilF(f32 x) { + return ceilf(x); +} + +// Unused +f32 Math_FRoundF(f32 x) { + return roundf(x); +} + +// Unused +f32 Math_FTruncF(f32 x) { + return truncf(x); +} + +f32 Math_FNearbyIntF(f32 x) { + return nearbyintf(x); +} + +/** + * Arctangent approximation using a Maclaurin series [https://mathworld.wolfram.com/MaclaurinSeries.html] + * (one quadrant, i.e. |x| < 1) + */ +f32 Math_FAtanTaylorQF(f32 x) { + // Coefficients of Maclaurin series of arctangent + static const f32 coeffs[] = { + -1.0f / 3, +1.0f / 5, -1.0f / 7, +1.0f / 9, -1.0f / 11, +1.0f / 13, -1.0f / 15, +1.0f / 17, 0.0f, + }; + + f32 poly = x; + f32 sq = SQ(x); + f32 exp = x * sq; + const f32* c = coeffs; + f32 term; + + // Calculate the series until adding more terms does not change the float + while (true) { + term = *c++ * exp; + if (poly + term == poly) { + break; + } + poly += term; + exp *= sq; + } + + return poly; +} + +/** + * Extends previous arctangent function to the rest of the real numbers. + * Uses the formulae arctan(x) = pi/2 - arctan(1/x) + * and arctan(x) = pi/4 - arctan( (1-x)/(1+x) ) + * to extend the range in which the series computed by Math_FAtanTaylorQF is a good approximation + */ +f32 Math_FAtanTaylorF(f32 x) { + f32 t; + f32 q; + + if (x > 0.0f) { + t = x; + } else if (x < 0.0f) { + t = -x; + } else if (x == 0.0f) { + return 0.0f; + } else { + return qNaN0x10000; + } + + if (t <= M_SQRT2 - 1.0f) { + return Math_FAtanTaylorQF(x); + } + + if (t >= M_SQRT2 + 1.0f) { + q = M_PI / 2 - Math_FAtanTaylorQF(1.0f / t); + } else { // in the interval (\sqrt{2} - 1, \sqrt{2} + 1) + q = M_PI / 4 - Math_FAtanTaylorQF((1.0f - t) / (1.0f + t)); + } + + if (x > 0.0f) { + return q; + } else { + return -q; + } +} + +/** + * Arctangent approximation using a continued fraction + * Cf. https://en.wikipedia.org/wiki/Gauss%27s_continued_fraction#The_series_2F1_2 , + * https://dlmf.nist.gov/4.25#E4 + */ +f32 Math_FAtanContFracF(f32 x) { + s32 sector; + f32 z; + f32 conv; + f32 sq; + s32 i; + + if (x >= -1.0f && x <= 1.0f) { + sector = 0; + } else if (x > 1.0f) { + sector = 1; + x = 1.0f / x; + } else if (x < -1.0f) { + sector = -1; + x = 1.0f / x; + } else { + return qNaN0x10000; + } + + // Builds the continued fraction from the innermost fraction out + sq = SQ(x); + conv = 0.0f; + z = 8.0f; + for (i = 8; i != 0; i--) { + conv = SQ(z) * sq / (2.0f * z + 1.0f + conv); + z -= 1.0f; + } + conv = x / (1.0f + conv); + + if (sector == 0) { + return conv; + } else if (sector > 0) { + return M_PI / 2 - conv; + } else { + return -M_PI / 2 - conv; + } +} + +/** + * Single-argument arctangent, only used by the two-argument function. + * Nothing else sets the bss variable gUseAtanContFrac, so the Maclaurin series is always used + */ +f32 Math_FAtanF(f32 x) { + if (!gUseAtanContFrac) { + return Math_FAtanTaylorF(x); + } else { + return Math_FAtanContFracF(x); + } +} + +/** + * Main two-argument arctangent function + */ +f32 Math_FAtan2F(f32 y, f32 x) { + if (x == 0.0f) { + if (y == 0.0f) { + return 0.0f; + } else if (y > 0.0f) { + return M_PI / 2; + } else if (y < 0.0f) { + return -M_PI / 2; + } else { + return qNaN0x10000; + } + } else if (x >= 0.0f) { + return Math_FAtanF(y / x); + } else if (y < 0.0f) { + return Math_FAtanF(y / x) - M_PI; + } else { + return M_PI - Math_FAtanF(-(y / x)); + } +} + +f32 Math_FAsinF(f32 x) { + return Math_FAtan2F(x, sqrtf(1.0f - SQ(x))); +} + +f32 Math_FAcosF(f32 x) { + return M_PI / 2 - Math_FAsinF(x); +} diff --git a/src/boot/O2/mtxuty-cvt.c b/src/boot/O2/mtxuty-cvt.c new file mode 100644 index 000000000..a5da24522 --- /dev/null +++ b/src/boot/O2/mtxuty-cvt.c @@ -0,0 +1,19 @@ +#include "global.h" + +void MtxConv_F2L(Mtx* mtx, MtxF* mf) { + s32 i; + s32 j; + + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + s32 value = (mf->mf[i][j] * 0x10000); + + mtx->intPart[i][j] = value >> 16; + mtx->fracPart[i][j] = value; + } + } +} + +void MtxConv_L2F(MtxF* mtx, Mtx* mf) { + guMtxL2F(mtx->mf, mf); +} diff --git a/src/boot/O2/padsetup.c b/src/boot/O2/padsetup.c new file mode 100644 index 000000000..9cef6f196 --- /dev/null +++ b/src/boot/O2/padsetup.c @@ -0,0 +1,34 @@ +#include "global.h" + +s32 PadSetup_Init(OSMesgQueue* mq, u8* outMask, OSContStatus* status) { + s32 ret; + s32 i; + + *outMask = 0xFF; + ret = osContInit(mq, outMask, status); + if (ret != 0) { + return ret; + } + if (*outMask == 0xFF) { + if (osContStartQuery(mq) != 0) { + return 1; + } + osRecvMesg(mq, NULL, OS_MESG_BLOCK); + osContGetQuery(status); + + *outMask = 0; + + for (i = 0; i < MAXCONTROLLERS; i++) { + switch (status[i].errno) { + case 0: + if (status[i].type == CONT_TYPE_NORMAL) { + *outMask |= 1 << i; + } + break; + default: + break; + } + } + } + return 0; +} diff --git a/src/boot/O2/padutils.c b/src/boot/O2/padutils.c new file mode 100644 index 000000000..1c6bbe868 --- /dev/null +++ b/src/boot/O2/padutils.c @@ -0,0 +1,92 @@ +#include "padutils.h" + +void PadUtils_Init(Input* input) { + bzero(input, sizeof(Input)); +} + +void func_80085150(void) { +} + +void PadUtils_ResetPressRel(Input* input) { + input->press.button = 0; + input->rel.button = 0; +} + +u32 PadUtils_CheckCurExact(Input* input, u16 value) { + return value == input->cur.button; +} + +u32 PadUtils_CheckCur(Input* input, u16 key) { + return key == (input->cur.button & key); +} + +u32 PadUtils_CheckPressed(Input* input, u16 key) { + return key == (input->press.button & key); +} + +u32 PadUtils_CheckReleased(Input* input, u16 key) { + return key == (input->rel.button & key); +} + +u16 PadUtils_GetCurButton(Input* input) { + return input->cur.button; +} + +u16 PadUtils_GetPressButton(Input* input) { + return input->press.button; +} + +s8 PadUtils_GetCurX(Input* input) { + return input->cur.stick_x; +} + +s8 PadUtils_GetCurY(Input* input) { + return input->cur.stick_y; +} + +void PadUtils_SetRelXY(Input* input, s32 x, s32 y) { + input->rel.stick_x = x; + input->rel.stick_y = y; +} + +s8 PadUtils_GetRelXImpl(Input* input) { + return input->rel.stick_x; +} + +s8 PadUtils_GetRelYImpl(Input* input) { + return input->rel.stick_y; +} + +s8 PadUtils_GetRelX(Input* input) { + return PadUtils_GetRelXImpl(input); +} + +s8 PadUtils_GetRelY(Input* input) { + return PadUtils_GetRelYImpl(input); +} + +void PadUtils_UpdateRelXY(Input* input) { + s32 curX = PadUtils_GetCurX(input); + s32 curY = PadUtils_GetCurY(input); + s32 relX; + s32 relY; + + if (curX > 7) { + relX = (curX < 0x43) ? curX - 7 : 0x43 - 7; + } else if (curX < -7) { + relX = (curX > -0x43) ? curX + 7 : -0x43 + 7; + } else { + relX = 0; + } + + if (curY > 7) { + relY = (curY < 0x43) ? curY - 7 : 0x43 - 7; + + } else if (curY < -7) { + relY = (curY > -0x43) ? curY + 7 : -0x43 + 7; + } else { + relY = 0; + } + + PadUtils_SetRelXY(input, relX, relY); +} diff --git a/src/boot/O2/printutils.c b/src/boot/O2/printutils.c new file mode 100644 index 000000000..3fb8cf367 --- /dev/null +++ b/src/boot/O2/printutils.c @@ -0,0 +1,17 @@ +#include "global.h" + +s32 PrintUtils_VPrintf(PrintCallback* pfn, const char* fmt, va_list args) { + return _Printf(*pfn, pfn, fmt, args); +} + +s32 PrintUtils_Printf(PrintCallback* pfn, const char* fmt, ...) { + s32 ret; + va_list args; + va_start(args, fmt); + + ret = PrintUtils_VPrintf(pfn, fmt, args); + + va_end(args); + + return ret; +} diff --git a/src/boot/O2/rand.c b/src/boot/O2/rand.c new file mode 100644 index 000000000..2db64e9d5 --- /dev/null +++ b/src/boot/O2/rand.c @@ -0,0 +1,96 @@ +#include "global.h" + +//! The latest generated random number, used to generate the next number in the sequence. +static u32 sRandInt = 1; + +//! Space to store a value to be re-interpreted as a float. +//! This can't be static because it is used in z_kankyo. +u32 sRandFloat; + +//! These values are recommended by the algorithms book *Numerical Recipes in C. The Art of Scientific Computing*, 2nd +//! Edition, 1992, ISBN 0-521-43108-5. (p. 284): +//! > This is about as good as any 32-bit linear congruential generator, entirely adequate for many uses. +#define RAND_MULTIPLIER 1664525 +#define RAND_INCREMENT 1013904223 + +/** + * Generates the next pseudo-random integer. + */ +u32 Rand_Next(void) { + return sRandInt = (sRandInt * RAND_MULTIPLIER) + RAND_INCREMENT; +} + +/** + * Seeds the internal pseudo-random number generator with a provided starting value. + */ +void Rand_Seed(u32 seed) { + sRandInt = seed; +} + +/** + * Returns a pseudo-random float between 0.0f and 1.0f from the internal PRNG. + * + * @note Works by generating the next integer, masking it to an IEEE-754 compliant float between 1.0f and 2.0f, and + * subtracting 1.0f. + * + * @remark This is also recommended by Numerical Recipes, pp. 284-5. + */ +f32 Rand_ZeroOne(void) { + sRandInt = (sRandInt * RAND_MULTIPLIER) + RAND_INCREMENT; + sRandFloat = ((sRandInt >> 9) | 0x3F800000); + return *((f32*)&sRandFloat) - 1.0f; +} + +/** + * Returns a pseudo-random float between -0.5f and 0.5f in the same way as Rand_ZeroOne(). + */ +f32 Rand_Centered(void) { + sRandInt = (sRandInt * RAND_MULTIPLIER) + RAND_INCREMENT; + sRandFloat = ((sRandInt >> 9) | 0x3F800000); + return *((f32*)&sRandFloat) - 1.5f; +} + +//! All functions below are unused variants of the above four, that use a provided random number variable instead of the +//! internal `sRandInt` + +/** + * Seeds a provided pseudo-random number with a provided starting value. + * + * @see Rand_Seed + */ +void Rand_Seed_Variable(u32* rndNum, u32 seed) { + *rndNum = seed; +} + +/** + * Generates the next pseudo-random number from the provided rndNum. + * + * @see Rand_Next + */ +u32 Rand_Next_Variable(u32* rndNum) { + return *rndNum = (*rndNum * RAND_MULTIPLIER) + RAND_INCREMENT; +} + +/** + * Generates the next pseudo-random float between 0.0f and 1.0f from the provided rndNum. + * + * @see Rand_ZeroOne + */ +f32 Rand_ZeroOne_Variable(u32* rndNum) { + u32 next = (*rndNum * RAND_MULTIPLIER) + RAND_INCREMENT; + + sRandFloat = ((*rndNum = next) >> 9) | 0x3F800000; + return *((f32*)&sRandFloat) - 1.0f; +} + +/** + * Generates the next pseudo-random float between -0.5f and 0.5f from the provided rndNum. + * + * @see Rand_ZeroOne, Rand_Centered + */ +f32 Rand_Centered_Variable(u32* rndNum) { + u32 next = (*rndNum * RAND_MULTIPLIER) + RAND_INCREMENT; + + sRandFloat = ((*rndNum = next) >> 9) | 0x3F800000; + return *((f32*)&sRandFloat) - 1.5f; +} diff --git a/src/boot/O2/rcp_utils.c b/src/boot/O2/rcp_utils.c new file mode 100644 index 000000000..f587fad21 --- /dev/null +++ b/src/boot/O2/rcp_utils.c @@ -0,0 +1,23 @@ +#include "ultra64.h" + +void RcpUtils_PrintRegisterStatus(void) { + u32 spStatus = __osSpGetStatus(); + u32 dpStatus = osDpGetStatus(); + + if (spStatus) { + // stubbed debug prints + } + + if (dpStatus) { + // stubbed debug prints + } +} + +void RcpUtils_Reset(void) { + RcpUtils_PrintRegisterStatus(); + // Flush the RDP pipeline and freeze clock counter + osDpSetStatus(DPC_SET_FREEZE | DPC_SET_FLUSH); + // Halt the RSP, disable interrupt on break and set "task done" signal + __osSpSetStatus(SP_SET_HALT | SP_SET_TASKDONE | SP_CLR_INTR_BREAK); + RcpUtils_PrintRegisterStatus(); +} diff --git a/src/boot/O2/sleep.c b/src/boot/O2/sleep.c new file mode 100644 index 000000000..a35abc5b2 --- /dev/null +++ b/src/boot/O2/sleep.c @@ -0,0 +1,27 @@ +#include "global.h" + +void Sleep_Cycles(u64 time) { + OSMesgQueue mq; + OSMesg msg[1]; + OSTimer timer; + + osCreateMesgQueue(&mq, msg, ARRAY_COUNT(msg)); + osSetTimer(&timer, time, 0, &mq, NULL); + osRecvMesg(&mq, NULL, OS_MESG_BLOCK); +} + +void Sleep_Nsec(u32 nsec) { + Sleep_Cycles(OS_NSEC_TO_CYCLES(nsec)); +} + +void Sleep_Usec(u32 usec) { + Sleep_Cycles(OS_USEC_TO_CYCLES(usec)); +} + +void Sleep_Msec(u32 ms) { + Sleep_Cycles((ms * OS_CPU_COUNTER) / 1000ULL); +} + +void Sleep_Sec(u32 sec) { + Sleep_Cycles(sec * OS_CPU_COUNTER); +} diff --git a/src/boot/O2/stackcheck.c b/src/boot/O2/stackcheck.c new file mode 100644 index 000000000..edd1e3a30 --- /dev/null +++ b/src/boot/O2/stackcheck.c @@ -0,0 +1,122 @@ +#include "stackcheck.h" +#include "libc/stdbool.h" +#include "libc/stdint.h" + +StackEntry* sStackInfoListStart = NULL; +StackEntry* sStackInfoListEnd = NULL; + +void StackCheck_Init(StackEntry* entry, void* stackBottom, void* stackTop, u32 initValue, s32 minSpace, + const char* name) { + if (entry == NULL) { + sStackInfoListStart = NULL; + } else { + StackEntry* iter; + + entry->head = stackBottom; + entry->tail = stackTop; + entry->initValue = initValue; + entry->minSpace = minSpace; + entry->name = name; + iter = sStackInfoListStart; + while (iter) { + if (iter == entry) { + return; + } + iter = iter->next; + } + + entry->prev = sStackInfoListEnd; + entry->next = NULL; + + if (sStackInfoListEnd) { + sStackInfoListEnd->next = entry; + } + + sStackInfoListEnd = entry; + if (sStackInfoListStart == NULL) { + sStackInfoListStart = entry; + } + + if (entry->minSpace != -1) { + u32* addr = entry->head; + + while (addr < (u32*)entry->tail) { + *addr++ = entry->initValue; + } + } + } +} + +void StackCheck_Cleanup(StackEntry* entry) { + u32 inconsistency = false; + + if (entry->prev == NULL) { + if (entry == sStackInfoListStart) { + sStackInfoListStart = entry->next; + } else { + inconsistency = true; + } + } else { + entry->prev->next = entry->next; + } + + if (!entry->next) { + if (entry == sStackInfoListEnd) { + sStackInfoListEnd = entry->prev; + } else { + inconsistency = true; + } + } + + if (inconsistency) {} +} + +StackStatus StackCheck_GetState(StackEntry* entry) { + u32* last; + size_t used; + size_t free; + StackStatus status; + + for (last = entry->head; last < (u32*)entry->tail; last++) { + if (entry->initValue != *last) { + break; + } + } + + used = (uintptr_t)entry->tail - (uintptr_t)last; + free = (uintptr_t)last - (uintptr_t)entry->head; + + if (free == 0) { + status = STACK_STATUS_OVERFLOW; + } else if ((free < (size_t)entry->minSpace) && (entry->minSpace != -1)) { + status = STACK_STATUS_WARNING; + } else { + status = STACK_STATUS_OK; + } + + return status; +} + +u32 StackCheck_CheckAll(void) { + u32 ret = 0; + StackEntry* iter = sStackInfoListStart; + + while (iter != NULL) { + StackStatus state = StackCheck_GetState(iter); + + if (state != STACK_STATUS_OK) { + ret = 1; + } + iter = iter->next; + } + + return ret; +} + +u32 StackCheck_Check(StackEntry* entry) { + if (entry == NULL) { + return StackCheck_CheckAll(); + } else { + return StackCheck_GetState(entry); + } +} diff --git a/src/boot/O2/system_heap.c b/src/boot/O2/system_heap.c new file mode 100644 index 000000000..365d94596 --- /dev/null +++ b/src/boot/O2/system_heap.c @@ -0,0 +1,123 @@ +/** + * @file system_heap.c + * + * @note: + * Only SystemHeap_Init() is used, and is essentially just a wrapper for SystemArena_Init(). + * + */ +#include "global.h" +#include "system_malloc.h" + +typedef void (*BlockFunc)(void*); +typedef void (*BlockFunc1)(void*, u32); +typedef void (*BlockFunc8)(void*, u32, u32, u32, u32, u32, u32, u32, u32); + +typedef struct InitFunc { + /* 0x0 */ uintptr_t nextOffset; + /* 0x4 */ void (*func)(void); +} InitFunc; // size = 0x8 + +void* sInitFuncs = NULL; + +char sNew[] = ""; + +UNK_TYPE1 D_80097508[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x00, + 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, +}; + +void* SystemHeap_Malloc(size_t size) { + if (size == 0) { + size = 1; + } + + return __osMalloc(&gSystemArena, size); +} + +void SystemHeap_Free(void* ptr) { + if (ptr != NULL) { + __osFree(&gSystemArena, ptr); + } +} + +void SystemHeap_RunBlockFunc(void* blk, size_t nBlk, size_t blkSize, BlockFunc blockFunc) { + uintptr_t pos = blk; + + for (; pos < (uintptr_t)blk + (nBlk * blkSize); pos += (blkSize & ~0)) { + blockFunc(pos); + } +} + +void SystemHeap_RunBlockFunc1(void* blk, size_t nBlk, size_t blkSize, BlockFunc1 blockFunc) { + uintptr_t pos = blk; + + for (; pos < (uintptr_t)blk + (nBlk * blkSize); pos += (blkSize & ~0)) { + blockFunc(pos, 2); + } +} + +void* SystemHeap_RunBlockFunc8(void* blk, size_t nBlk, size_t blkSize, BlockFunc8 blockFunc) { + if (blk == NULL) { + blk = SystemHeap_Malloc(nBlk * blkSize); + } + + if ((blk != NULL) && (blockFunc != NULL)) { + uintptr_t pos = blk; + + for (; pos < (uintptr_t)blk + (nBlk * blkSize); pos += (blkSize & ~0)) { + blockFunc(pos, 0, 0, 0, 0, 0, 0, 0, 0); + } + } + + return blk; +} + +void SystemHeap_RunBlockFunc1Reverse(void* blk, size_t nBlk, size_t blkSize, BlockFunc1 blockFunc, s32 shouldFree) { + uintptr_t pos; + uintptr_t start; + size_t maskedBlkSize; + + if (blk == NULL) { + return; + } + + if (blockFunc != NULL) { + start = blk; + maskedBlkSize = (blkSize & ~0); + pos = (uintptr_t)start + (nBlk * blkSize); + + while (pos > start) { + pos -= maskedBlkSize; + blockFunc(pos, 2); + } + } + + if (shouldFree) { + SystemHeap_Free(blk); + } +} + +void SystemHeap_RunInits(void) { + InitFunc* initFunc = (InitFunc*)&sInitFuncs; + u32 nextOffset = initFunc->nextOffset; + InitFunc* prev = NULL; + + while (nextOffset != 0) { + initFunc = (InitFunc*)((uintptr_t)initFunc + nextOffset); + + if (initFunc->func != NULL) { + (*initFunc->func)(); + } + + nextOffset = initFunc->nextOffset; + initFunc->nextOffset = (uintptr_t)prev; + prev = initFunc; + } + + sInitFuncs = prev; +} + +void SystemHeap_Init(void* start, size_t size) { + SystemArena_Init(start, size); + SystemHeap_RunInits(); +} diff --git a/src/boot/O2/system_malloc.c b/src/boot/O2/system_malloc.c new file mode 100644 index 000000000..cea017b38 --- /dev/null +++ b/src/boot/O2/system_malloc.c @@ -0,0 +1,51 @@ +#include "global.h" +#include "os_malloc.h" + +Arena gSystemArena; + +void* SystemArena_Malloc(size_t size) { + return __osMalloc(&gSystemArena, size); +} + +void* SystemArena_MallocR(size_t size) { + return __osMallocR(&gSystemArena, size); +} + +void* SystemArena_Realloc(void* oldPtr, size_t newSize) { + return __osRealloc(&gSystemArena, oldPtr, newSize); +} + +void SystemArena_Free(void* ptr) { + __osFree(&gSystemArena, ptr); +} + +void* SystemArena_Calloc(u32 elements, size_t size) { + void* ptr; + size_t totalSize = elements * size; + + ptr = __osMalloc(&gSystemArena, totalSize); + if (ptr != NULL) { + bzero(ptr, totalSize); + } + return ptr; +} + +void SystemArena_GetSizes(size_t* maxFreeBlock, size_t* bytesFree, size_t* bytesAllocated) { + __osGetSizes(&gSystemArena, maxFreeBlock, bytesFree, bytesAllocated); +} + +u32 SystemArena_CheckArena(void) { + return __osCheckArena(&gSystemArena); +} + +void SystemArena_Init(void* start, size_t size) { + __osMallocInit(&gSystemArena, start, size); +} + +void SystemArena_Cleanup(void) { + __osMallocCleanup(&gSystemArena); +} + +u8 SystemArena_IsInitialized(void) { + return __osMallocIsInitalized(&gSystemArena); +} diff --git a/src/boot/boot_main.c b/src/boot/boot_main.c new file mode 100644 index 000000000..dcb66fefd --- /dev/null +++ b/src/boot/boot_main.c @@ -0,0 +1,24 @@ +#include "prevent_bss_reordering.h" +#include "global.h" +#include "idle.h" +#include "stack.h" +#include "stackcheck.h" +#include "z64thread.h" + +StackEntry sBootStackInfo; +OSThread sIdleThread; +STACK(sIdleStack, 0x400); +StackEntry sIdleStackInfo; +STACK(sBootStack, 0x400); + +void bootproc(void) { + StackCheck_Init(&sBootStackInfo, sBootStack, STACK_TOP(sBootStack), 0, -1, "boot"); + osMemSize = osGetMemSize(); + func_800818F4(); + osInitialize(); + osUnmapTLBAll(); + gCartHandle = osCartRomInit(); + StackCheck_Init(&sIdleStackInfo, sIdleStack, STACK_TOP(sIdleStack), 0, 0x100, "idle"); + osCreateThread(&sIdleThread, Z_THREAD_ID_IDLE, Idle_ThreadEntry, NULL, STACK_TOP(sIdleStack), Z_PRIORITY_IDLE); + osStartThread(&sIdleThread); +} diff --git a/src/boot/build.c b/src/boot/build.c new file mode 100644 index 000000000..23bd1cd59 --- /dev/null +++ b/src/boot/build.c @@ -0,0 +1,3 @@ +const char gBuildTeam[] = "zelda@srd44"; +const char gBuildDate[] = "00-07-31 17:04:16"; +const char gBuildMakeOption[] = ""; diff --git a/src/boot/fault.c b/src/boot/fault.c new file mode 100644 index 000000000..f62dfb8b7 --- /dev/null +++ b/src/boot/fault.c @@ -0,0 +1,1128 @@ +/** + * @file fault.c + * + * This file implements the screen that may be viewed when the game crashes. + * This is the second known version of the crash screen, an evolved version from OoT's. + * + * When the game crashes, a red bar will be drawn to the top-left of the screen, indicating that the + * crash screen is available for use. Once this bar appears, it is possible to open the crash screen + * with the following button combination: + * + * (DPad-Left & L & R & C-Right) & Start + * + * When entering this button combination, buttons that are &'d together must all be pressed together. + * + * "Clients" may be registered with the crash screen to extend its functionality. There are + * two kinds of client, "Client" and "AddressConverterClient". Clients contribute one or + * more pages to the crash debugger, while Address Converter Clients allow the crash screen to look up + * the virtual addresses of dynamically allocated overlays. + * + * The crash screen has multiple pages: + * - Thread Context + * This page shows information about the thread on which the program crashed. It displays + * the cause of the crash, state of general-purpose registers, state of floating-point registers + * and the floating-point status register. If a floating-point exception caused the crash, it will + * be displayed next to the floating-point status register. + * - Stack Trace + * This page displays a full backtrace from the crashing function back to the start of the thread. It + * displays the Program Counter for each function and, if applicable, the Virtual Program Counter + * for relocated functions in overlays. + * - Client Pages + * After the stack trace page, currently registered clients are processed and their pages are displayed. + * - Memory Dump + * This page implements a scrollable memory dump. + * - End Screen + * This page informs you that there are no more pages to display. + * + * To navigate the pages, START and A may be used to advance to the next page, and L toggles whether to + * automatically scroll to the next page after some time has passed. + * DPad-Up may be pressed to enable sending fault pages over osSyncPrintf as well as displaying them on-screen. + * DPad-Down disables sending fault pages over osSyncPrintf. + */ + +#include "fault_internal.h" +#include "fault.h" +#include "global.h" +#include "vt.h" +#include "PR/osint.h" +#include "stackcheck.h" +#include "z64thread.h" + +FaultMgr* sFaultInstance; +f32 sFaultTimeTotal; // read but not set anywhere + +// data +const char* sCpuExceptions[] = { + "Interrupt", + "TLB modification", + "TLB exception on load", + "TLB exception on store", + "Address error on load", + "Address error on store", + "Bus error on inst.", + "Bus error on data", + "System call exception", + "Breakpoint exception", + "Reserved instruction", + "Coprocessor unusable", + "Arithmetic overflow", + "Trap exception", + "Virtual coherency on inst.", + "Floating point exception", + "Watchpoint exception", + "Virtual coherency on data", +}; + +const char* sFpuExceptions[] = { + "Unimplemented operation", "Invalid operation", "Division by zero", "Overflow", "Underflow", "Inexact operation", +}; + +void Fault_SleepImpl(u32 duration) { + OSTime value = (duration * OS_CPU_COUNTER) / 1000ULL; + + Sleep_Cycles(value); +} + +/** + * Registers a fault client. + * + * Clients contribute at least one page to the crash screen, drawn by `callback`. + * Arguments are passed on to the callback through `arg0` and `arg1`. + */ +void Fault_AddClient(FaultClient* client, FaultClientCallback callback, void* arg0, void* arg1) { + OSIntMask mask; + u32 alreadyExists = false; + + mask = osSetIntMask(1); + + // Ensure the client is not already registered + { + FaultClient* iter = sFaultInstance->clients; + + while (iter != NULL) { + if (iter == client) { + alreadyExists = true; + goto end; + } + iter = iter->next; + } + } + + client->callback = callback; + client->arg0 = arg0; + client->arg1 = arg1; + client->next = sFaultInstance->clients; + sFaultInstance->clients = client; + +end: + osSetIntMask(mask); + + if (alreadyExists) { + osSyncPrintf(VT_COL(RED, WHITE) "fault_AddClient: %08x は既にリスト中にある\n" VT_RST, client); + } +} + +/** + * Removes a fault client so that the page is no longer displayed if a crash occurs. + */ +void Fault_RemoveClient(FaultClient* client) { + FaultClient* iter = sFaultInstance->clients; + FaultClient* lastIter = NULL; + OSIntMask mask; + u32 listIsEmpty = false; + + mask = osSetIntMask(1); + + while (iter) { + if (iter == client) { + if (lastIter != NULL) { + lastIter->next = client->next; + } else { + sFaultInstance->clients = client; + if (sFaultInstance->clients) { + sFaultInstance->clients = client->next; + } else { + listIsEmpty = 1; + } + } + break; + } + + lastIter = iter; + iter = iter->next; + } + + osSetIntMask(mask); + + if (listIsEmpty) { + osSyncPrintf(VT_COL(RED, WHITE) "fault_RemoveClient: %08x リスト不整合です\n" VT_RST, client); + } +} + +/** + * Registers an address converter client. This enables the crash screen to look up virtual + * addresses of overlays relocated during runtime. Address conversion is carried out by + * `callback`, which either returns a virtual address or NULL if the address could not + * be converted. + * + * The callback is intended to be + * `uintptr_t (*callback)(uintptr_t addr, void* arg)` + * The callback may return 0 if it could not convert the address + */ +void Fault_AddAddrConvClient(FaultAddrConvClient* client, FaultAddrConvClientCallback callback, void* arg) { + OSIntMask mask; + s32 alreadyExists = false; + + mask = osSetIntMask(1); + + { + FaultAddrConvClient* iter = sFaultInstance->addrConvClients; + + while (iter != NULL) { + if (iter == client) { + alreadyExists = true; + goto end; + } + iter = iter->next; + } + } + + client->callback = callback; + client->arg = arg; + client->next = sFaultInstance->addrConvClients; + sFaultInstance->addrConvClients = client; + +end: + osSetIntMask(mask); + + if (alreadyExists) { + osSyncPrintf(VT_COL(RED, WHITE) "fault_AddressConverterAddClient: %08x は既にリスト中にある\n" VT_RST, client); + } +} + +void Fault_RemoveAddrConvClient(FaultAddrConvClient* client) { + FaultAddrConvClient* iter = sFaultInstance->addrConvClients; + FaultAddrConvClient* lastIter = NULL; + OSIntMask mask; + bool listIsEmpty = false; + + mask = osSetIntMask(1); + + while (iter) { + if (iter == client) { + if (lastIter != NULL) { + lastIter->next = client->next; + } else { + sFaultInstance->addrConvClients = client; + if (sFaultInstance->addrConvClients) { + sFaultInstance->addrConvClients = client->next; + } else { + listIsEmpty = true; + } + } + break; + } + + lastIter = iter; + iter = iter->next; + } + + osSetIntMask(mask); + + if (listIsEmpty) { + osSyncPrintf(VT_COL(RED, WHITE) "fault_AddressConverterRemoveClient: %08x は既にリスト中にある\n" VT_RST, + client); + } +} + +/** + * Converts `addr` to a virtual address via the registered + * address converter clients + */ +uintptr_t Fault_ConvertAddress(uintptr_t addr) { + uintptr_t ret; + FaultAddrConvClient* iter = sFaultInstance->addrConvClients; + + while (iter != NULL) { + if (iter->callback != NULL) { + ret = iter->callback(addr, iter->arg); + if (ret != 0) { + return ret; + } + } + iter = iter->next; + } + + return 0; +} + +void Fault_Sleep(u32 msec) { + Fault_SleepImpl(msec); +} + +void Fault_PadCallback(Input* input) { + PadMgr_GetInput2(input, false); +} + +void Fault_UpdatePadImpl(void) { + sFaultInstance->padCallback(sFaultInstance->inputs); +} + +/** + * Awaits user input + * + * L toggles auto-scroll + * DPad-Up enables osSyncPrintf output + * DPad-Down disables osSyncPrintf output + * A and DPad-Right continues and returns true + * DPad-Left continues and returns false + */ +u32 Fault_WaitForInputImpl(void) { + Input* input = &sFaultInstance->inputs[0]; + s32 count = 600; + u32 pressedBtn; + + while (true) { + Fault_Sleep(1000 / 60); + Fault_UpdatePadImpl(); + + pressedBtn = input->press.button; + + if (pressedBtn == BTN_L) { + sFaultInstance->autoScroll = !sFaultInstance->autoScroll; + } + + if (sFaultInstance->autoScroll) { + if (count-- < 1) { + return false; + } + } else { + if ((pressedBtn == BTN_A) || (pressedBtn == BTN_DRIGHT)) { + return false; + } + + if (pressedBtn == BTN_DLEFT) { + return true; + } + + if (pressedBtn == BTN_DUP) { + FaultDrawer_SetOsSyncPrintfEnabled(true); + } + + if (pressedBtn == BTN_DDOWN) { + FaultDrawer_SetOsSyncPrintfEnabled(false); + } + } + } +} + +void Fault_WaitForInput(void) { + Fault_WaitForInputImpl(); +} + +void Fault_DrawRec(s32 x, s32 y, s32 w, s32 h, u16 color) { + FaultDrawer_DrawRecImpl(x, y, x + w - 1, y + h - 1, color); +} + +void Fault_FillScreenBlack(void) { + FaultDrawer_SetForeColor(GPACK_RGBA5551(255, 255, 255, 1)); + FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 1)); + FaultDrawer_FillScreen(); + FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 0)); +} + +void Fault_FillScreenRed(void) { + FaultDrawer_SetForeColor(GPACK_RGBA5551(255, 255, 255, 1)); + FaultDrawer_SetBackColor(GPACK_RGBA5551(240, 0, 0, 1)); + FaultDrawer_FillScreen(); + FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 0)); +} + +void Fault_DrawCornerRec(u16 color) { + Fault_DrawRec(22, 16, 8, 1, color); +} + +void Fault_PrintFReg(s32 index, f32* value) { + u32 raw = *(u32*)value; + s32 v0 = ((raw & 0x7F800000) >> 0x17) - 0x7F; + + if ((v0 >= -0x7E && v0 < 0x80) || raw == 0) { + FaultDrawer_Printf("F%02d:%14.7e ", index, *value); + } else { + // Print subnormal floats as their IEEE-754 hex representation + FaultDrawer_Printf("F%02d: %08x(16) ", index, raw); + } +} + +void Fault_LogFReg(s32 idx, f32* value) { + u32 raw = *(u32*)value; + s32 v0 = ((raw & 0x7F800000) >> 0x17) - 0x7F; + + if ((v0 >= -0x7E && v0 < 0x80) || raw == 0) { + osSyncPrintf("F%02d:%14.7e ", idx, *value); + } else { + osSyncPrintf("F%02d: %08x(16) ", idx, *(u32*)value); + } +} + +void Fault_PrintFPCR(u32 value) { + s32 i; + u32 flag = 0x20000; + + FaultDrawer_Printf("FPCSR:%08xH ", value); + + // Go through each of the six causes and print the name of + // the first cause that is set + for (i = 0; i < ARRAY_COUNT(sFpuExceptions); i++) { + if (value & flag) { + FaultDrawer_Printf("(%s)", sFpuExceptions[i]); + break; + } + flag >>= 1; + } + FaultDrawer_Printf("\n"); +} + +void Fault_LogFPCSR(u32 value) { + s32 i; + u32 flag = 0x20000; + + osSyncPrintf("FPCSR:%08xH ", value); + for (i = 0; i < ARRAY_COUNT(sFpuExceptions); i++) { + if (value & flag) { + osSyncPrintf("(%s)\n", sFpuExceptions[i]); + break; + } + flag >>= 1; + } +} + +void Fault_PrintThreadContext(OSThread* thread) { + __OSThreadContext* threadCtx; + s16 causeStrIdx = _SHIFTR((u32)thread->context.cause, 2, 5); + + if (causeStrIdx == 23) { // Watchpoint + causeStrIdx = 16; + } + if (causeStrIdx == 31) { // Virtual coherency on data + causeStrIdx = 17; + } + + FaultDrawer_FillScreen(); + FaultDrawer_SetCharPad(-2, 4); + FaultDrawer_SetCursor(22, 20); + + threadCtx = &thread->context; + FaultDrawer_Printf("THREAD:%d (%d:%s)\n", thread->id, causeStrIdx, sCpuExceptions[causeStrIdx]); + FaultDrawer_SetCharPad(-1, 0); + + FaultDrawer_Printf("PC:%08xH SR:%08xH VA:%08xH\n", (u32)threadCtx->pc, (u32)threadCtx->sr, + (u32)threadCtx->badvaddr); + FaultDrawer_Printf("AT:%08xH V0:%08xH V1:%08xH\n", (u32)threadCtx->at, (u32)threadCtx->v0, (u32)threadCtx->v1); + FaultDrawer_Printf("A0:%08xH A1:%08xH A2:%08xH\n", (u32)threadCtx->a0, (u32)threadCtx->a1, (u32)threadCtx->a2); + FaultDrawer_Printf("A3:%08xH T0:%08xH T1:%08xH\n", (u32)threadCtx->a3, (u32)threadCtx->t0, (u32)threadCtx->t1); + FaultDrawer_Printf("T2:%08xH T3:%08xH T4:%08xH\n", (u32)threadCtx->t2, (u32)threadCtx->t3, (u32)threadCtx->t4); + FaultDrawer_Printf("T5:%08xH T6:%08xH T7:%08xH\n", (u32)threadCtx->t5, (u32)threadCtx->t6, (u32)threadCtx->t7); + FaultDrawer_Printf("S0:%08xH S1:%08xH S2:%08xH\n", (u32)threadCtx->s0, (u32)threadCtx->s1, (u32)threadCtx->s2); + FaultDrawer_Printf("S3:%08xH S4:%08xH S5:%08xH\n", (u32)threadCtx->s3, (u32)threadCtx->s4, (u32)threadCtx->s5); + FaultDrawer_Printf("S6:%08xH S7:%08xH T8:%08xH\n", (u32)threadCtx->s6, (u32)threadCtx->s7, (u32)threadCtx->t8); + FaultDrawer_Printf("T9:%08xH GP:%08xH SP:%08xH\n", (u32)threadCtx->t9, (u32)threadCtx->gp, (u32)threadCtx->sp); + FaultDrawer_Printf("S8:%08xH RA:%08xH LO:%08xH\n\n", (u32)threadCtx->s8, (u32)threadCtx->ra, (u32)threadCtx->lo); + + Fault_PrintFPCR(threadCtx->fpcsr); + FaultDrawer_Printf("\n"); + Fault_PrintFReg(0, &threadCtx->fp0.f.f_even); + Fault_PrintFReg(2, &threadCtx->fp2.f.f_even); + FaultDrawer_Printf("\n"); + Fault_PrintFReg(4, &threadCtx->fp4.f.f_even); + Fault_PrintFReg(6, &threadCtx->fp6.f.f_even); + FaultDrawer_Printf("\n"); + Fault_PrintFReg(8, &threadCtx->fp8.f.f_even); + Fault_PrintFReg(0xA, &threadCtx->fp10.f.f_even); + FaultDrawer_Printf("\n"); + Fault_PrintFReg(0xC, &threadCtx->fp12.f.f_even); + Fault_PrintFReg(0xE, &threadCtx->fp14.f.f_even); + FaultDrawer_Printf("\n"); + Fault_PrintFReg(0x10, &threadCtx->fp16.f.f_even); + Fault_PrintFReg(0x12, &threadCtx->fp18.f.f_even); + FaultDrawer_Printf("\n"); + Fault_PrintFReg(0x14, &threadCtx->fp20.f.f_even); + Fault_PrintFReg(0x16, &threadCtx->fp22.f.f_even); + FaultDrawer_Printf("\n"); + Fault_PrintFReg(0x18, &threadCtx->fp24.f.f_even); + Fault_PrintFReg(0x1A, &threadCtx->fp26.f.f_even); + FaultDrawer_Printf("\n"); + Fault_PrintFReg(0x1C, &threadCtx->fp28.f.f_even); + Fault_PrintFReg(0x1E, &threadCtx->fp30.f.f_even); + FaultDrawer_Printf("\n"); + FaultDrawer_SetCharPad(0, 0); + + if (sFaultTimeTotal != 0.0f) { + FaultDrawer_DrawText(160, 216, "%5.2f sec\n", sFaultTimeTotal); + } +} + +void osSyncPrintfThreadContext(OSThread* thread) { + __OSThreadContext* threadCtx; + s16 causeStrIdx = _SHIFTR((u32)thread->context.cause, 2, 5); + + if (causeStrIdx == 23) { // Watchpoint + causeStrIdx = 16; + } + if (causeStrIdx == 31) { // Virtual coherency on data + causeStrIdx = 17; + } + + threadCtx = &thread->context; + osSyncPrintf("\n"); + osSyncPrintf("THREAD ID:%d (%d:%s)\n", thread->id, causeStrIdx, sCpuExceptions[causeStrIdx]); + + osSyncPrintf("PC:%08xH SR:%08xH VA:%08xH\n", (u32)threadCtx->pc, (u32)threadCtx->sr, (u32)threadCtx->badvaddr); + osSyncPrintf("AT:%08xH V0:%08xH V1:%08xH\n", (u32)threadCtx->at, (u32)threadCtx->v0, (u32)threadCtx->v1); + osSyncPrintf("A0:%08xH A1:%08xH A2:%08xH\n", (u32)threadCtx->a0, (u32)threadCtx->a1, (u32)threadCtx->a2); + osSyncPrintf("A3:%08xH T0:%08xH T1:%08xH\n", (u32)threadCtx->a3, (u32)threadCtx->t0, (u32)threadCtx->t1); + osSyncPrintf("T2:%08xH T3:%08xH T4:%08xH\n", (u32)threadCtx->t2, (u32)threadCtx->t3, (u32)threadCtx->t4); + osSyncPrintf("T5:%08xH T6:%08xH T7:%08xH\n", (u32)threadCtx->t5, (u32)threadCtx->t6, (u32)threadCtx->t7); + osSyncPrintf("S0:%08xH S1:%08xH S2:%08xH\n", (u32)threadCtx->s0, (u32)threadCtx->s1, (u32)threadCtx->s2); + osSyncPrintf("S3:%08xH S4:%08xH S5:%08xH\n", (u32)threadCtx->s3, (u32)threadCtx->s4, (u32)threadCtx->s5); + osSyncPrintf("S6:%08xH S7:%08xH T8:%08xH\n", (u32)threadCtx->s6, (u32)threadCtx->s7, (u32)threadCtx->t8); + osSyncPrintf("T9:%08xH GP:%08xH SP:%08xH\n", (u32)threadCtx->t9, (u32)threadCtx->gp, (u32)threadCtx->sp); + osSyncPrintf("S8:%08xH RA:%08xH LO:%08xH\n", (u32)threadCtx->s8, (u32)threadCtx->ra, (u32)threadCtx->lo); + osSyncPrintf("\n"); + Fault_LogFPCSR(threadCtx->fpcsr); + osSyncPrintf("\n"); + Fault_LogFReg(0, &threadCtx->fp0.f.f_even); + Fault_LogFReg(2, &threadCtx->fp2.f.f_even); + osSyncPrintf("\n"); + Fault_LogFReg(4, &threadCtx->fp4.f.f_even); + Fault_LogFReg(6, &threadCtx->fp6.f.f_even); + osSyncPrintf("\n"); + Fault_LogFReg(8, &threadCtx->fp8.f.f_even); + Fault_LogFReg(10, &threadCtx->fp10.f.f_even); + osSyncPrintf("\n"); + Fault_LogFReg(12, &threadCtx->fp12.f.f_even); + Fault_LogFReg(14, &threadCtx->fp14.f.f_even); + osSyncPrintf("\n"); + Fault_LogFReg(16, &threadCtx->fp16.f.f_even); + Fault_LogFReg(18, &threadCtx->fp18.f.f_even); + osSyncPrintf("\n"); + Fault_LogFReg(20, &threadCtx->fp20.f.f_even); + Fault_LogFReg(22, &threadCtx->fp22.f.f_even); + osSyncPrintf("\n"); + Fault_LogFReg(24, &threadCtx->fp24.f.f_even); + Fault_LogFReg(26, &threadCtx->fp26.f.f_even); + osSyncPrintf("\n"); + Fault_LogFReg(28, &threadCtx->fp28.f.f_even); + Fault_LogFReg(30, &threadCtx->fp30.f.f_even); + osSyncPrintf("\n"); +} + +/** + * Iterates through the active thread queue for a user thread with either + * the CPU break or Fault flag set. + */ +OSThread* Fault_FindFaultedThread(void) { + OSThread* iter = __osGetActiveQueue(); + + while (iter->priority != OS_PRIORITY_THREADTAIL) { + if ((iter->priority > OS_PRIORITY_IDLE) && (iter->priority < OS_PRIORITY_APPMAX) && + (iter->flags & (OS_FLAG_CPU_BREAK | OS_FLAG_FAULT))) { + return iter; + } + iter = iter->tlnext; + } + + return NULL; +} +void Fault_Wait5Seconds(void) { + s32 pad; + OSTime start = osGetTime(); + + do { + Fault_Sleep(1000 / 60); + } while ((osGetTime() - start) <= OS_SEC_TO_CYCLES(5)); + + sFaultInstance->autoScroll = true; +} + +/** + * Waits for the following button combination to be entered before returning: + * + * (DPad-Left & L & R & C-Right) & Start + */ +void Fault_WaitForButtonCombo(void) { + Input* input = &sFaultInstance->inputs[0]; + + FaultDrawer_SetForeColor(GPACK_RGBA5551(255, 255, 255, 1)); + FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 1)); + + do { + do { + Fault_Sleep(1000 / 60); + Fault_UpdatePadImpl(); + } while (!CHECK_BTN_ALL(input->press.button, BTN_RESET)); + } while (!CHECK_BTN_ALL(input->cur.button, BTN_DLEFT | BTN_L | BTN_R | BTN_CRIGHT)); +} + +void Fault_DrawMemDumpContents(const char* title, uintptr_t addr, u32 param_3) { + uintptr_t alignedAddr = addr; + u32* writeAddr; + s32 y; + s32 x; + + // Ensure address is within the bounds of RDRAM (Fault_DrawMemDump has already done this) + if (alignedAddr < K0BASE) { + alignedAddr = K0BASE; + } + // 8MB RAM, leave room to display 0x100 bytes on the final page + //! @bug The loop below draws 22 * 4 * 4 = 0x160 bytes per page. Due to this, by scrolling further than + //! 0x807FFEA0 some invalid bytes are read from outside of 8MB RDRAM space. This does not cause a crash, + //! however the values it displays are meaningless. On N64 hardware these invalid addresses are read as 0. + if (alignedAddr > (K0BASE + 0x800000 - 0x100)) { + alignedAddr = K0BASE + 0x800000 - 0x100; + } + + // Ensure address is word-aligned + alignedAddr &= ~3; + writeAddr = (u32*)alignedAddr; + + Fault_FillScreenBlack(); + FaultDrawer_SetCharPad(-2, 0); + + FaultDrawer_DrawText(36, 18, "%s %08x", title ? title : "PrintDump", alignedAddr); + + if (alignedAddr >= K0BASE && alignedAddr < K2BASE) { + for (y = 0; y < 22; y++) { + FaultDrawer_DrawText(24, 28 + y * 9, "%06x", writeAddr); + for (x = 0; x < 4; x++) { + FaultDrawer_DrawText(82 + x * 52, 28 + y * 9, "%08x", *writeAddr++); + } + } + } + + FaultDrawer_SetCharPad(0, 0); +} + +/** + * Draws the memory dump page. + * + * DPad-Up scrolls up. + * DPad-Down scrolls down. + * Holding A while scrolling speeds up scrolling by a factor of 0x10. + * Holding B while scrolling speeds up scrolling by a factor of 0x100. + * + * L toggles auto-scrolling pages. + * START and A move on to the next page. + * + * @param pc Program counter, pressing C-Up jumps to this address + * @param sp Stack pointer, pressing C-Down jumps to this address + * @param cLeftJump Unused parameter, pressing C-Left jumps to this address + * @param cRightJump Unused parameter, pressing C-Right jumps to this address + */ +void Fault_DrawMemDump(uintptr_t pc, uintptr_t sp, uintptr_t cLeftJump, uintptr_t cRightJump) { + s32 scrollCountdown; + s32 off; + Input* input = &sFaultInstance->inputs[0]; + uintptr_t addr = pc; + + do { + scrollCountdown = 0; + // Ensure address is within the bounds of RDRAM + if (addr < K0BASE) { + addr = K0BASE; + } + // 8MB RAM, leave room to display 0x100 bytes on the final page + if (addr > (K0BASE + 0x800000 - 0x100)) { + addr = K0BASE + 0x800000 - 0x100; + } + + // Align down the address to 0x10 bytes and draw the page contents + addr &= ~0xF; + Fault_DrawMemDumpContents("Dump", addr, 0); + + scrollCountdown = 600; + while (sFaultInstance->autoScroll) { + // Count down until it's time to move on to the next page + if (scrollCountdown == 0) { + return; + } + + scrollCountdown--; + + Fault_Sleep(1000 / 60); + Fault_UpdatePadImpl(); + + if (CHECK_BTN_ALL(input->press.button, BTN_L)) { + sFaultInstance->autoScroll = false; + } + } + + // Wait for input + do { + Fault_Sleep(1000 / 60); + Fault_UpdatePadImpl(); + } while (input->press.button == 0); + + // Move to next page + if (CHECK_BTN_ALL(input->press.button, BTN_START)) { + return; + } + + // Memory dump controls + + off = 0x10; + if (CHECK_BTN_ALL(input->cur.button, BTN_A)) { + off *= 0x10; + } + if (CHECK_BTN_ALL(input->cur.button, BTN_B)) { + off *= 0x100; + } + if (CHECK_BTN_ALL(input->press.button, BTN_DUP)) { + addr -= off; + } + if (CHECK_BTN_ALL(input->press.button, BTN_DDOWN)) { + addr += off; + } + if (CHECK_BTN_ALL(input->press.button, BTN_CUP)) { + addr = pc; + } + if (CHECK_BTN_ALL(input->press.button, BTN_CDOWN)) { + addr = sp; + } + if (CHECK_BTN_ALL(input->press.button, BTN_CLEFT)) { + addr = cLeftJump; + } + if (CHECK_BTN_ALL(input->press.button, BTN_CRIGHT)) { + addr = cRightJump; + } + + } while (!CHECK_BTN_ALL(input->press.button, BTN_L)); + + // Resume auto-scroll and move to next page + sFaultInstance->autoScroll = true; +} + +/** + * Searches a single function's stack frame for the function it was called from. + * There are two cases that must be covered: Leaf and non-leaf functions. + * + * A leaf function is one that does not call any other function, in this case the + * return address need not be saved to the stack. Since a leaf function does not + * call other functions, only the function the stack trace begins in could possibly + * be a leaf function, in which case the return address is in the thread context's + * $ra already, as it never left. + * + * The procedure is therefore + * - Iterate instructions + * - Once jr $ra is found, set pc to $ra + * - Done after delay slot + * + * A non-leaf function calls other functions, it is necessary for the return address + * to be saved to the stack. In these functions, it is important to keep track of the + * stack frame size of each function. + * + * The procedure is therefore + * - Iterate instructions + * - If lw $ra <imm>($sp) is found, fetch the saved $ra from stack memory + * - If addiu $sp, $sp, <imm> is found, modify $sp by the immediate value + * - If jr $ra is found, set pc to $ra + * - Done after delay slot + * + * Note that searching for one jr $ra is sufficient, as only leaf functions can have + * multiple jr $ra in the same function. + * + * There is also additional handling for eret and j. Neither of these instructions + * appear in IDO compiled C, however do show up in the exception handler. It is not + * possible to backtrace through an eret as an interrupt can occur at any time, so + * there is no choice but to give up here. For j instructions, they can be followed + * and the backtrace may continue as normal. + */ +void Fault_WalkStack(uintptr_t* spPtr, uintptr_t* pcPtr, uintptr_t* raPtr) { + uintptr_t sp = *spPtr; + uintptr_t pc = *pcPtr; + uintptr_t ra = *raPtr; + u32 lastInsn; + u16 insnHi; + s16 insnLo; + u32 imm; + + if ((sp % 4 != 0) || (sp < K0BASE) || (sp >= K2BASE) || (ra % 4 != 0) || (ra < K0BASE) || (ra >= K2BASE)) { + *spPtr = 0; + *pcPtr = 0; + *raPtr = 0; + return; + } + + if ((pc % 4 != 0) || (pc < K0BASE) || (pc >= K2BASE)) { + *pcPtr = ra; + return; + } + + lastInsn = 0; + while (true) { + insnHi = *(uintptr_t*)pc >> 16; + insnLo = *(uintptr_t*)pc & 0xFFFF; + imm = insnLo; + + if (insnHi == 0x8FBF) { + // lw $ra, <imm>($sp) + // read return address saved on the stack + ra = *(uintptr_t*)(sp + imm); + } else if (insnHi == 0x27BD) { + // addiu $sp, $sp, <imm> + // stack pointer increment or decrement + sp += imm; + } else if (*(uintptr_t*)pc == 0x42000018) { + // eret + // cannot backtrace through an eret, give up + sp = 0; + pc = 0; + ra = 0; + goto done; + } + if (lastInsn == 0x3E00008) { + // jr $ra + // return to previous function + pc = ra; + goto done; + } else if (lastInsn >> 26 == 2) { + // j <target> + // extract jump target + pc = (pc >> 28 << 28) | (lastInsn << 6 >> 4); + goto done; + } + + lastInsn = *(uintptr_t*)pc; + pc += sizeof(u32); + } + +done: + *spPtr = sp; + *pcPtr = pc; + *raPtr = ra; +} + +/** + * Draws the stack trace page contents for the specified thread + */ +void Fault_DrawStackTrace(OSThread* thread, u32 flags) { + s32 line; + uintptr_t sp = thread->context.sp; + uintptr_t ra = thread->context.ra; + uintptr_t pc = thread->context.pc; + s32 pad; + uintptr_t addr; + + Fault_FillScreenBlack(); + FaultDrawer_DrawText(120, 16, "STACK TRACE"); + FaultDrawer_DrawText(36, 24, "SP PC (VPC)"); + + for (line = 1; (line < 22) && (((ra != 0) || (sp != 0)) && (pc != (uintptr_t)__osCleanupThread)); line++) { + FaultDrawer_DrawText(0x24, line * 8 + 24, "%08x %08x", sp, pc); + + if (flags & 1) { + // Try to convert the relocated program counter to the corresponding unrelocated virtual address + addr = Fault_ConvertAddress(pc); + if (addr != 0) { + FaultDrawer_Printf(" -> %08x", addr); + } + } else { + FaultDrawer_Printf(" -> ????????"); + } + + Fault_WalkStack(&sp, &pc, &ra); + } +} + +void Fault_LogStackTrace(OSThread* thread, u32 flags) { + s32 line; + uintptr_t sp = thread->context.sp; + uintptr_t ra = thread->context.ra; + uintptr_t pc = thread->context.pc; + uintptr_t addr; + + osSyncPrintf("STACK TRACE"); + osSyncPrintf("SP PC (VPC)\n"); + + for (line = 1; (line < 22) && (((ra != 0) || (sp != 0)) && (pc != (uintptr_t)__osCleanupThread)); line++) { + osSyncPrintf("%08x %08x", sp, pc); + + if (flags & 1) { + // Try to convert the relocated program counter to the corresponding unrelocated virtual address + addr = Fault_ConvertAddress(pc); + if (addr != 0) { + osSyncPrintf(" -> %08x", addr); + } + } else { + osSyncPrintf(" -> ????????"); + } + osSyncPrintf("\n"); + + Fault_WalkStack(&sp, &pc, &ra); + } +} + +void Fault_ResumeThread(OSThread* thread) { + thread->context.cause = 0; + thread->context.fpcsr = 0; + thread->context.pc += sizeof(u32); + *(u32*)thread->context.pc = 0x0000000D; // write in a break instruction + osWritebackDCache((void*)thread->context.pc, 4); + osInvalICache((void*)thread->context.pc, 4); + osStartThread(thread); +} + +void Fault_DisplayFrameBuffer(void) { + void* fb; + + osViSetYScale(1.0f); + osViSetMode(&osViModeNtscLan1); + osViSetSpecialFeatures(OS_VI_GAMMA_OFF | OS_VI_DITHER_FILTER_ON); + osViBlack(false); + + if (sFaultInstance->fb) { + fb = sFaultInstance->fb; + } else { + fb = osViGetNextFramebuffer(); + if ((uintptr_t)fb == K0BASE) { + fb = (void*)(PHYS_TO_K0(osMemSize) - SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(u16)); + } + } + + osViSwapBuffer(fb); + FaultDrawer_SetDrawerFrameBuffer(fb, SCREEN_WIDTH, SCREEN_HEIGHT); +} + +/** + * Runs all registered fault clients. Each fault client displays a page + * on the crash screen. + */ +void Fault_ProcessClients(void) { + FaultClient* client = sFaultInstance->clients; + s32 idx = 0; + + while (client != NULL) { + if (client->callback != NULL) { + Fault_FillScreenBlack(); + FaultDrawer_SetCharPad(-2, 0); + FaultDrawer_Printf(FAULT_COLOR(DARK_GRAY) "CallBack (%d) %08x %08x %08x\n" FAULT_COLOR(WHITE), idx++, + client, client->arg0, client->arg1); + FaultDrawer_SetCharPad(0, 0); + client->callback(client->arg0, client->arg1); + Fault_WaitForInput(); + Fault_DisplayFrameBuffer(); + } + client = client->next; + } +} + +void Fault_SetOptionsFromController3(void) { + static u32 faultCustomOptions; + Input* input3 = &sFaultInstance->inputs[3]; + u32 pad; + uintptr_t pc; + uintptr_t ra; + uintptr_t sp; + + // BTN_RESET is the "neutral reset". Corresponds to holding L+R and pressing S + if (CHECK_BTN_ALL(input3->press.button, BTN_RESET)) { + faultCustomOptions = !faultCustomOptions; + } + + if (faultCustomOptions) { + pc = gGraphThread.context.pc; + ra = gGraphThread.context.ra; + sp = gGraphThread.context.sp; + if (CHECK_BTN_ALL(input3->cur.button, BTN_R)) { + static u32 faultCopyToLog; + + faultCopyToLog = !faultCopyToLog; + FaultDrawer_SetOsSyncPrintfEnabled(faultCopyToLog); + } + if (CHECK_BTN_ALL(input3->cur.button, BTN_A)) { + osSyncPrintf("GRAPH PC=%08x RA=%08x STACK=%08x\n", pc, ra, sp); + } + if (CHECK_BTN_ALL(input3->cur.button, BTN_B)) { + FaultDrawer_SetDrawerFrameBuffer(osViGetNextFramebuffer(), 0x140, 0xF0); + Fault_DrawRec(0, 0xD7, 0x140, 9, 1); + FaultDrawer_SetCharPad(-2, 0); + FaultDrawer_DrawText(0x20, 0xD8, "GRAPH PC %08x RA %08x SP %08x", pc, ra, sp); + } + } +} + +void Fault_UpdatePad(void) { + Fault_UpdatePadImpl(); + Fault_SetOptionsFromController3(); +} + +#define FAULT_MSG_CPU_BREAK ((OSMesg)1) +#define FAULT_MSG_FAULT ((OSMesg)2) +#define FAULT_MSG_UNK ((OSMesg)3) + +void Fault_ThreadEntry(void* arg) { + OSMesg msg; + u32 pad; + OSThread* faultedThread; + + // Direct OS event messages to the fault event queue + osSetEventMesg(OS_EVENT_CPU_BREAK, &sFaultInstance->queue, FAULT_MSG_CPU_BREAK); + osSetEventMesg(OS_EVENT_FAULT, &sFaultInstance->queue, FAULT_MSG_FAULT); + + while (true) { + do { + // Wait for a thread to hit a fault + osRecvMesg(&sFaultInstance->queue, &msg, OS_MESG_BLOCK); + + if (msg == FAULT_MSG_CPU_BREAK) { + sFaultInstance->msgId = (u32)FAULT_MSG_CPU_BREAK; + // "Fault manager: OS_EVENT_CPU_BREAK received" + osSyncPrintf("フォルトマネージャ:OS_EVENT_CPU_BREAKを受信しました\n"); + } else if (msg == FAULT_MSG_FAULT) { + sFaultInstance->msgId = (u32)FAULT_MSG_FAULT; + // "Fault manager: OS_EVENT_FAULT received" + osSyncPrintf("フォルトマネージャ:OS_EVENT_FAULTを受信しました\n"); + } else if (msg == FAULT_MSG_UNK) { + Fault_UpdatePad(); + faultedThread = NULL; + continue; + } else { + sFaultInstance->msgId = (u32)FAULT_MSG_UNK; + // "Fault manager: received an unknown message" + osSyncPrintf("フォルトマネージャ:不明なメッセージを受信しました\n"); + } + + faultedThread = __osGetCurrFaultedThread(); + osSyncPrintf("__osGetCurrFaultedThread()=%08x\n", faultedThread); + + if (faultedThread == NULL) { + faultedThread = Fault_FindFaultedThread(); + osSyncPrintf("FindFaultedThread()=%08x\n", faultedThread); + } + } while (faultedThread == NULL); + + __osSetFpcCsr(__osGetFpcCsr() & ~(FPCSR_EV | FPCSR_EZ | FPCSR_EO | FPCSR_EU | FPCSR_EI)); + sFaultInstance->faultedThread = faultedThread; + + while (!sFaultInstance->faultHandlerEnabled) { + Fault_Sleep(1000); + } + Fault_Sleep(1000 / 2); + + // Show fault framebuffer + Fault_DisplayFrameBuffer(); + + if (sFaultInstance->autoScroll) { + Fault_Wait5Seconds(); + } else { + // Draw error bar signifying the crash screen is available + Fault_DrawCornerRec(GPACK_RGBA5551(255, 0, 0, 1)); + Fault_WaitForButtonCombo(); + } + + // Set auto-scrolling and default colors + sFaultInstance->autoScroll = true; + FaultDrawer_SetForeColor(GPACK_RGBA5551(255, 255, 255, 1)); + FaultDrawer_SetBackColor(GPACK_RGBA5551(0, 0, 0, 0)); + + // Draw pages + do { + // Thread context page + Fault_PrintThreadContext(faultedThread); + osSyncPrintfThreadContext(faultedThread); + Fault_WaitForInput(); + + // Stack trace page + Fault_DrawStackTrace(faultedThread, 0); + Fault_LogStackTrace(faultedThread, 0); + Fault_WaitForInput(); + + // Client pages + Fault_ProcessClients(); + + // Memory dump page + Fault_DrawMemDump((u32)(faultedThread->context.pc - 0x100), (u32)faultedThread->context.sp, 0, 0); + Fault_DrawStackTrace(faultedThread, 1); + Fault_LogStackTrace(faultedThread, 1); + Fault_WaitForInput(); + + // End page + Fault_FillScreenRed(); + FaultDrawer_DrawText(64, 80, " CONGRATURATIONS! "); + FaultDrawer_DrawText(64, 90, "All Pages are displayed."); + FaultDrawer_DrawText(64, 100, " THANK YOU! "); + FaultDrawer_DrawText(64, 110, " You are great debugger!"); + Fault_WaitForInput(); + } while (!sFaultInstance->exit); + + while (!sFaultInstance->exit) {} + + Fault_ResumeThread(faultedThread); + } +} + +void Fault_SetFrameBuffer(void* fb, u16 w, u16 h) { + sFaultInstance->fb = fb; + FaultDrawer_SetDrawerFrameBuffer(fb, w, h); +} + +STACK(sFaultStack, 0x600); +StackEntry sFaultStackInfo; +FaultMgr gFaultMgr; + +void Fault_Init(void) { + sFaultInstance = &gFaultMgr; + bzero(sFaultInstance, sizeof(FaultMgr)); + FaultDrawer_Init(); + FaultDrawer_SetInputCallback(Fault_WaitForInput); + sFaultInstance->exit = false; + sFaultInstance->msgId = 0; + sFaultInstance->faultHandlerEnabled = false; + sFaultInstance->faultedThread = NULL; + sFaultInstance->padCallback = Fault_PadCallback; + sFaultInstance->clients = NULL; + sFaultInstance->autoScroll = false; + gFaultMgr.faultHandlerEnabled = true; + osCreateMesgQueue(&sFaultInstance->queue, sFaultInstance->msg, ARRAY_COUNT(sFaultInstance->msg)); + StackCheck_Init(&sFaultStackInfo, sFaultStack, STACK_TOP(sFaultStack), 0, 0x100, "fault"); + osCreateThread(&sFaultInstance->thread, Z_THREAD_ID_FAULT, Fault_ThreadEntry, NULL, STACK_TOP(sFaultStack), + Z_PRIORITY_FAULT); + osStartThread(&sFaultInstance->thread); +} + +/** + * Fault page for Hungup crashes. Displays the thread id and two messages + * specified in arguments to `Fault_AddHungupAndCrashImpl`. + */ +void Fault_HangupFaultClient(const char* exp1, const char* exp2) { + osSyncPrintf("HungUp on Thread %d\n", osGetThreadId(NULL)); + osSyncPrintf("%s\n", exp1 != NULL ? exp1 : "(NULL)"); + osSyncPrintf("%s\n", exp2 != NULL ? exp2 : "(NULL)"); + FaultDrawer_Printf("HungUp on Thread %d\n", osGetThreadId(NULL)); + FaultDrawer_Printf("%s\n", exp1 != NULL ? exp1 : "(NULL)"); + FaultDrawer_Printf("%s\n", exp2 != NULL ? exp2 : "(NULL)"); +} + +/** + * Immediately crashes the current thread, for cases where an irrecoverable + * error occurs. The parameters specify two messages detailing the error, one + * or both may be NULL. + */ +void Fault_AddHungupAndCrashImpl(const char* exp1, const char* exp2) { + FaultClient client; + s32 pad; + + Fault_AddClient(&client, (void*)Fault_HangupFaultClient, (void*)exp1, (void*)exp2); + *(u32*)0x11111111 = 0; // trigger an exception via unaligned memory access +} + +/** + * Like `Fault_AddHungupAndCrashImpl`, however provides a fixed message containing + * file and line number + */ +void Fault_AddHungupAndCrash(const char* file, s32 line) { + char msg[0x100]; + + sprintf(msg, "HungUp %s:%d", file, line); + Fault_AddHungupAndCrashImpl(msg, NULL); +} diff --git a/src/boot/fault_drawer.c b/src/boot/fault_drawer.c new file mode 100644 index 000000000..6031ccaa6 --- /dev/null +++ b/src/boot/fault_drawer.c @@ -0,0 +1,310 @@ +/** + * @file fault_drawer.c + * + * Implements routines for drawing text with a fixed font directly to a framebuffer, used in displaying + * the crash screen implemented by fault.c + */ + +#include "fault.h" +#include "fault_internal.h" +#include "global.h" +#include "vt.h" + +typedef struct { + /* 0x00 */ u16* frameBuffer; + /* 0x04 */ u16 w; + /* 0x06 */ u16 h; + /* 0x08 */ u16 yStart; + /* 0x0A */ u16 yEnd; + /* 0x0C */ u16 xStart; + /* 0x0E */ u16 xEnd; + /* 0x10 */ u16 foreColor; + /* 0x12 */ u16 backColor; + /* 0x14 */ u16 cursorX; + /* 0x16 */ u16 cursorY; + /* 0x18 */ const u32* fontData; + /* 0x1C */ u8 charW; + /* 0x1D */ u8 charH; + /* 0x1E */ s8 charWPad; + /* 0x1F */ s8 charHPad; + /* 0x20 */ u16 printColors[10]; + /* 0x34 */ u8 escCode; // bool + /* 0x35 */ u8 osSyncPrintfEnabled; + /* 0x38 */ FaultDrawerCallback inputCallback; +} FaultDrawer; // size = 0x3C + +extern const u32 sFaultDrawerFont[]; + +FaultDrawer sFaultDrawer; + +FaultDrawer* sFaultDrawerInstance = &sFaultDrawer; + +#define FAULT_DRAWER_CURSOR_X 22 +#define FAULT_DRAWER_CURSOR_Y 16 + +FaultDrawer sFaultDrawerDefault = { + FAULT_FB_ADDRESS, // frameBuffer + SCREEN_WIDTH, // w + SCREEN_HEIGHT, // h + FAULT_DRAWER_CURSOR_Y, // yStart + SCREEN_HEIGHT - FAULT_DRAWER_CURSOR_Y - 1, // yEnd + FAULT_DRAWER_CURSOR_X, // xStart + SCREEN_WIDTH - FAULT_DRAWER_CURSOR_X - 1, // xEnd + GPACK_RGBA5551(255, 255, 255, 255), // foreColor + GPACK_RGBA5551(0, 0, 0, 0), // backColor + FAULT_DRAWER_CURSOR_X, // cursorX + FAULT_DRAWER_CURSOR_Y, // cursorY + sFaultDrawerFont, // fontData + 8, // charW + 8, // charH + 0, // charWPad + 0, // charHPad + { + // printColors + GPACK_RGBA5551(0, 0, 0, 1), // BLACK + GPACK_RGBA5551(255, 0, 0, 1), // RED + GPACK_RGBA5551(0, 255, 0, 1), // GREEN + GPACK_RGBA5551(255, 255, 0, 1), // YELLOW + GPACK_RGBA5551(0, 0, 255, 1), // BLUE + GPACK_RGBA5551(255, 0, 255, 1), // MAGENTA + GPACK_RGBA5551(0, 255, 255, 1), // CYAN + GPACK_RGBA5551(255, 255, 255, 1), // WHITE + GPACK_RGBA5551(120, 120, 120, 1), // DARK GRAY + GPACK_RGBA5551(176, 176, 176, 1), // LIGHT GRAY + }, + false, // escCode + false, // osSyncPrintfEnabled + NULL, // inputCallback +}; + +//! TODO: Needs to be extracted +#pragma GLOBAL_ASM("asm/non_matchings/boot/fault_drawer/sFaultDrawerFont.s") + +void FaultDrawer_SetOsSyncPrintfEnabled(u32 enabled) { + sFaultDrawerInstance->osSyncPrintfEnabled = enabled; +} + +void FaultDrawer_DrawRecImpl(s32 xStart, s32 yStart, s32 xEnd, s32 yEnd, u16 color) { + u16* frameBuffer; + s32 x; + s32 y; + s32 xDiff = sFaultDrawerInstance->w - xStart; + s32 yDiff = sFaultDrawerInstance->h - yStart; + s32 xSize = xEnd - xStart + 1; + s32 ySize = yEnd - yStart + 1; + + if ((xDiff > 0) && (yDiff > 0)) { + if (xDiff < xSize) { + xSize = xDiff; + } + + if (yDiff < ySize) { + ySize = yDiff; + } + + frameBuffer = sFaultDrawerInstance->frameBuffer + sFaultDrawerInstance->w * yStart + xStart; + for (y = 0; y < ySize; y++) { + for (x = 0; x < xSize; x++) { + *frameBuffer++ = color; + } + frameBuffer += sFaultDrawerInstance->w - xSize; + } + + osWritebackDCacheAll(); + } +} + +void FaultDrawer_DrawChar(char c) { + s32 x; + s32 y; + u32 data; + s32 cursorX = sFaultDrawerInstance->cursorX; + s32 cursorY = sFaultDrawerInstance->cursorY; + s32 shift = c % 4; + const u32* dataPtr = &sFaultDrawerInstance->fontData[(((c / 8) * 16) + ((c & 4) >> 2))]; + u16* frameBuffer = sFaultDrawerInstance->frameBuffer + (sFaultDrawerInstance->w * cursorY) + cursorX; + + if ((sFaultDrawerInstance->xStart <= cursorX) && + ((sFaultDrawerInstance->charW + cursorX - 1) <= sFaultDrawerInstance->xEnd) && + (sFaultDrawerInstance->yStart <= cursorY) && + ((sFaultDrawerInstance->charH + cursorY - 1) <= sFaultDrawerInstance->yEnd)) { + for (y = 0; y < sFaultDrawerInstance->charH; y++) { + u32 mask = 0x10000000 << shift; + + data = *dataPtr; + for (x = 0; x < sFaultDrawerInstance->charW; x++) { + if (mask & data) { + frameBuffer[x] = sFaultDrawerInstance->foreColor; + } else if (sFaultDrawerInstance->backColor & 1) { + frameBuffer[x] = sFaultDrawerInstance->backColor; + } + mask >>= 4; + } + frameBuffer += sFaultDrawerInstance->w; + dataPtr += 2; + } + } +} + +s32 FaultDrawer_ColorToPrintColor(u16 color) { + s32 i; + + for (i = 0; i < ARRAY_COUNT(sFaultDrawerInstance->printColors); i++) { + if (color == sFaultDrawerInstance->printColors[i]) { + return i; + } + } + return -1; +} + +void FaultDrawer_UpdatePrintColor(void) { + s32 index; + + if (sFaultDrawerInstance->osSyncPrintfEnabled) { + osSyncPrintf(VT_RST); + + index = FaultDrawer_ColorToPrintColor(sFaultDrawerInstance->foreColor); + if ((index >= 0) && (index < 8)) { + osSyncPrintf(VT_SGR("3%d"), index); + } + + index = FaultDrawer_ColorToPrintColor(sFaultDrawerInstance->backColor); + if ((index >= 0) && (index < 8)) { + osSyncPrintf(VT_SGR("4%d"), index); + } + } +} + +void FaultDrawer_SetForeColor(u16 color) { + sFaultDrawerInstance->foreColor = color; + FaultDrawer_UpdatePrintColor(); +} + +void FaultDrawer_SetBackColor(u16 color) { + sFaultDrawerInstance->backColor = color; + FaultDrawer_UpdatePrintColor(); +} + +void FaultDrawer_SetFontColor(u16 color) { + FaultDrawer_SetForeColor(color | 1); // force alpha to be set +} + +void FaultDrawer_SetCharPad(s8 padW, s8 padH) { + sFaultDrawerInstance->charWPad = padW; + sFaultDrawerInstance->charHPad = padH; +} + +void FaultDrawer_SetCursor(s32 x, s32 y) { + if (sFaultDrawerInstance->osSyncPrintfEnabled) { + osSyncPrintf( + VT_CUP("%d", "%d"), + (y - sFaultDrawerInstance->yStart) / (sFaultDrawerInstance->charH + sFaultDrawerInstance->charHPad), + (x - sFaultDrawerInstance->xStart) / (sFaultDrawerInstance->charW + sFaultDrawerInstance->charWPad)); + } + sFaultDrawerInstance->cursorX = x; + sFaultDrawerInstance->cursorY = y; +} + +void FaultDrawer_FillScreen() { + if (sFaultDrawerInstance->osSyncPrintfEnabled) { + osSyncPrintf(VT_CLS); + } + + FaultDrawer_DrawRecImpl(sFaultDrawerInstance->xStart, sFaultDrawerInstance->yStart, sFaultDrawerInstance->xEnd, + sFaultDrawerInstance->yEnd, sFaultDrawerInstance->backColor | 1); + FaultDrawer_SetCursor(sFaultDrawerInstance->xStart, sFaultDrawerInstance->yStart); +} + +void* FaultDrawer_FormatStringFunc(void* arg, const char* str, size_t count) { + for (; count != 0; count--, str++) { + if (sFaultDrawerInstance->escCode) { + sFaultDrawerInstance->escCode = false; + if (*str >= '1' && *str <= '9') { + FaultDrawer_SetForeColor(sFaultDrawerInstance->printColors[*str - '0']); + } + } else { + switch (*str) { + case '\n': + if (sFaultDrawerInstance->osSyncPrintfEnabled) { + osSyncPrintf("\n"); + } + + sFaultDrawerInstance->cursorX = sFaultDrawerInstance->w; + break; + + case FAULT_ESC: + sFaultDrawerInstance->escCode = true; + break; + + default: + if (sFaultDrawerInstance->osSyncPrintfEnabled) { + osSyncPrintf("%c", *str); + } + + FaultDrawer_DrawChar(*str); + sFaultDrawerInstance->cursorX += sFaultDrawerInstance->charW + sFaultDrawerInstance->charWPad; + } + } + + if (sFaultDrawerInstance->cursorX >= (sFaultDrawerInstance->xEnd - sFaultDrawerInstance->charW)) { + sFaultDrawerInstance->cursorX = sFaultDrawerInstance->xStart; + sFaultDrawerInstance->cursorY += sFaultDrawerInstance->charH + sFaultDrawerInstance->charHPad; + if (sFaultDrawerInstance->yEnd - sFaultDrawerInstance->charH <= sFaultDrawerInstance->cursorY) { + if (sFaultDrawerInstance->inputCallback != NULL) { + sFaultDrawerInstance->inputCallback(); + FaultDrawer_FillScreen(); + } + sFaultDrawerInstance->cursorY = sFaultDrawerInstance->yStart; + } + } + } + + osWritebackDCacheAll(); + + return arg; +} + +const char D_80099080[] = "(null)"; + +s32 FaultDrawer_VPrintf(const char* fmt, va_list ap) { + return _Printf(FaultDrawer_FormatStringFunc, sFaultDrawerInstance, fmt, ap); +} + +s32 FaultDrawer_Printf(const char* fmt, ...) { + s32 ret; + va_list args; + + va_start(args, fmt); + + ret = FaultDrawer_VPrintf(fmt, args); + + va_end(args); + + return ret; +} + +void FaultDrawer_DrawText(s32 x, s32 y, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + + FaultDrawer_SetCursor(x, y); + FaultDrawer_VPrintf(fmt, args); + + va_end(args); +} + +void FaultDrawer_SetDrawerFrameBuffer(void* frameBuffer, u16 w, u16 h) { + sFaultDrawerInstance->frameBuffer = frameBuffer; + sFaultDrawerInstance->w = w; + sFaultDrawerInstance->h = h; +} + +void FaultDrawer_SetInputCallback(FaultDrawerCallback callback) { + sFaultDrawerInstance->inputCallback = callback; +} + +void FaultDrawer_Init() { + sFaultDrawerInstance = &sFaultDrawer; + bcopy(&sFaultDrawerDefault, sFaultDrawerInstance, sizeof(FaultDrawer)); + sFaultDrawerInstance->frameBuffer = (u16*)(PHYS_TO_K0(osMemSize) - SCREEN_HEIGHT * SCREEN_WIDTH * sizeof(u16)); +} diff --git a/src/boot/idle.c b/src/boot/idle.c new file mode 100644 index 000000000..ccdf98695 --- /dev/null +++ b/src/boot/idle.c @@ -0,0 +1,127 @@ +#include "irqmgr.h" +#include "main.h" +#include "stack.h" +#include "stackcheck.h" +#include "z64thread.h" + +// Variables are put before most headers as a hacky way to bypass bss reordering +IrqMgr gIrqMgr; +STACK(sIrqMgrStack, 0x500); +StackEntry sIrqMgrStackInfo; +OSThread sMainThread; +STACK(sMainStack, 0x900); +StackEntry sMainStackInfo; +OSMesg sPiMgrCmdBuff[50]; +OSMesgQueue gPiMgrCmdQueue; +OSViMode gViConfigMode; +u8 gViConfigModeType; + +#include "global.h" +#include "buffers.h" +#include "idle.h" + +u8 D_80096B20 = 1; +vu8 gViConfigUseBlack = true; +u8 gViConfigAdditionalScanLines = 0; +u32 gViConfigFeatures = OS_VI_DITHER_FILTER_ON | OS_VI_GAMMA_OFF; +f32 gViConfigXScale = 1.0f; +f32 gViConfigYScale = 1.0f; + +void Main_ClearMemory(void* begin, void* end) { + if (begin < end) { + bzero(begin, (uintptr_t)end - (uintptr_t)begin); + } +} + +void Main_InitFramebuffer(u32* framebuffer, size_t numBytes, u32 value) { + for (; numBytes > 0; numBytes -= sizeof(u32)) { + *framebuffer++ = value; + } +} + +void Main_InitScreen(void) { + Main_InitFramebuffer((u32*)gFramebuffer1, sizeof(gFramebuffer1), + (GPACK_RGBA5551(0, 0, 0, 1) << 16) | GPACK_RGBA5551(0, 0, 0, 1)); + ViConfig_UpdateVi(false); + osViSwapBuffer(gFramebuffer1); + osViBlack(false); +} + +void Main_InitMemory(void) { + void* memStart = (void*)0x80000400; + void* memEnd = OS_PHYSICAL_TO_K0(osMemSize); + + Main_ClearMemory(memStart, gFramebuffer1); + Main_ClearMemory(D_80025D00, bootproc); + Main_ClearMemory(gGfxSPTaskYieldBuffer, memEnd); +} + +void Main_Init(void) { + DmaRequest dmaReq; + OSMesgQueue mq; + OSMesg msg[1]; + size_t prevSize; + + osCreateMesgQueue(&mq, msg, ARRAY_COUNT(msg)); + + prevSize = gDmaMgrDmaBuffSize; + gDmaMgrDmaBuffSize = 0; + + DmaMgr_SendRequestImpl(&dmaReq, SEGMENT_START(code), SEGMENT_ROM_START(code), + SEGMENT_ROM_END(code) - SEGMENT_ROM_START(code), 0, &mq, NULL); + Main_InitScreen(); + Main_InitMemory(); + osRecvMesg(&mq, NULL, OS_MESG_BLOCK); + + gDmaMgrDmaBuffSize = prevSize; + + Main_ClearMemory(SEGMENT_BSS_START(code), SEGMENT_BSS_END(code)); +} + +void Main_ThreadEntry(void* arg) { + StackCheck_Init(&sIrqMgrStackInfo, sIrqMgrStack, STACK_TOP(sIrqMgrStack), 0, 0x100, "irqmgr"); + IrqMgr_Init(&gIrqMgr, STACK_TOP(sIrqMgrStack), Z_PRIORITY_IRQMGR, 1); + DmaMgr_Start(); + Main_Init(); + Main(arg); + DmaMgr_Stop(); +} + +void Idle_InitVideo(void) { + osCreateViManager(OS_PRIORITY_VIMGR); + + gViConfigFeatures = OS_VI_DITHER_FILTER_ON | OS_VI_GAMMA_OFF; + gViConfigXScale = 1.0f; + gViConfigYScale = 1.0f; + + switch (osTvType) { + case OS_TV_NTSC: + gViConfigModeType = OS_VI_NTSC_LAN1; + gViConfigMode = osViModeNtscLan1; + break; + + case OS_TV_MPAL: + gViConfigModeType = OS_VI_MPAL_LAN1; + gViConfigMode = osViModeMpalLan1; + break; + + case OS_TV_PAL: + gViConfigModeType = OS_VI_FPAL_LAN1; + gViConfigMode = osViModeFpalLan1; + gViConfigYScale = 0.833f; + break; + } + + D_80096B20 = 1; +} + +void Idle_ThreadEntry(void* arg) { + Idle_InitVideo(); + osCreatePiManager(OS_PRIORITY_PIMGR, &gPiMgrCmdQueue, sPiMgrCmdBuff, ARRAY_COUNT(sPiMgrCmdBuff)); + StackCheck_Init(&sMainStackInfo, sMainStack, STACK_TOP(sMainStack), 0, 0x400, "main"); + osCreateThread(&sMainThread, Z_THREAD_ID_MAIN, Main_ThreadEntry, arg, STACK_TOP(sMainStack), Z_PRIORITY_MAIN); + osStartThread(&sMainThread); + osSetThreadPri(NULL, OS_PRIORITY_IDLE); + + for (;;) {} +} diff --git a/src/boot/irqmgr.c b/src/boot/irqmgr.c new file mode 100644 index 000000000..9a773f279 --- /dev/null +++ b/src/boot/irqmgr.c @@ -0,0 +1,170 @@ +#include "global.h" +#include "stackcheck.h" +#include "z64thread.h" + +vs32 gIrqMgrResetStatus = 0; +volatile OSTime sIrqMgrResetTime = 0; +volatile OSTime sIrqMgrRetraceTime = 0; +s32 sIrqMgrRetraceCount = 0; + +void IrqMgr_AddClient(IrqMgr* irqmgr, IrqMgrClient* client, OSMesgQueue* msgQueue) { + u32 saveMask; + + saveMask = osSetIntMask(1); + + client->queue = msgQueue; + client->next = irqmgr->callbacks; + irqmgr->callbacks = client; + + osSetIntMask(saveMask); + + if (irqmgr->prenmiStage > 0) { + osSendMesg(client->queue, &irqmgr->prenmiMsg.type, OS_MESG_NOBLOCK); + } + if (irqmgr->prenmiStage > 1) { + osSendMesg(client->queue, &irqmgr->nmiMsg.type, OS_MESG_NOBLOCK); + } +} + +void IrqMgr_RemoveClient(IrqMgr* irqmgr, IrqMgrClient* remove) { + IrqMgrClient* iter; + IrqMgrClient* last; + u32 saveMask; + + iter = irqmgr->callbacks; + last = NULL; + + saveMask = osSetIntMask(1); + + while (iter != NULL) { + if (iter == remove) { + if (last != NULL) { + last->next = remove->next; + } else { + irqmgr->callbacks = remove->next; + } + break; + } + last = iter; + iter = iter->next; + } + + osSetIntMask(saveMask); +} + +void IrqMgr_SendMesgForClient(IrqMgr* irqmgr, OSMesg msg) { + IrqMgrClient* iter = irqmgr->callbacks; + + while (iter != NULL) { + osSendMesg(iter->queue, msg, OS_MESG_NOBLOCK); + iter = iter->next; + } +} + +void IrqMgr_JamMesgForClient(IrqMgr* irqmgr, OSMesg msg) { + IrqMgrClient* iter = irqmgr->callbacks; + + while (iter != NULL) { + if (iter->queue->validCount < iter->queue->msgCount) { + osSendMesg(iter->queue, msg, OS_MESG_NOBLOCK); + } + iter = iter->next; + } +} + +void IrqMgr_HandlePreNMI(IrqMgr* irqmgr) { + gIrqMgrResetStatus = 1; + irqmgr->prenmiStage = 1; + + sIrqMgrResetTime = irqmgr->lastPrenmiTime = osGetTime(); + + // Wait .45 seconds then generate a stage 2 prenmi interrupt + osSetTimer(&irqmgr->prenmiTimer, OS_USEC_TO_CYCLES(450000), 0, &irqmgr->irqQueue, (OSMesg)0x29F); + + IrqMgr_JamMesgForClient(irqmgr, &irqmgr->prenmiMsg.type); +} + +void IrqMgr_CheckStack(void) { + StackCheck_Check(NULL); +} + +void IrqMgr_HandlePRENMI450(IrqMgr* irqmgr) { + gIrqMgrResetStatus = 2; + irqmgr->prenmiStage = 2; + + // Wait .03 seconds then generate a stage 3 prenmi interrupt + osSetTimer(&irqmgr->prenmiTimer, OS_USEC_TO_CYCLES(30000), 0, &irqmgr->irqQueue, (OSMesg)0x2A0); + + IrqMgr_SendMesgForClient(irqmgr, &irqmgr->nmiMsg.type); +} + +void IrqMgr_HandlePRENMI480(IrqMgr* irqmgr) { + // Wait .52 seconds. After this we will have waited an entire second + osSetTimer(&irqmgr->prenmiTimer, OS_USEC_TO_CYCLES(520000), 0, &irqmgr->irqQueue, (OSMesg)0x2A1); + + osAfterPreNMI(); +} + +void IrqMgr_HandlePRENMI500(IrqMgr* irqmgr) { + IrqMgr_CheckStack(); +} +void IrqMgr_HandleRetrace(IrqMgr* irqmgr) { + if (sIrqMgrRetraceTime == 0) { + if (irqmgr->lastFrameTime == 0) { + irqmgr->lastFrameTime = osGetTime(); + } else { + sIrqMgrRetraceTime = osGetTime() - irqmgr->lastFrameTime; + } + } + + sIrqMgrRetraceCount += 1; + IrqMgr_SendMesgForClient(irqmgr, irqmgr); +} + +void IrqMgr_ThreadEntry(IrqMgr* irqmgr) { + u32 interrupt; + u32 stop; + + interrupt = 0; + stop = 0; + while (stop == 0) { + if (stop) { + ; + } + + osRecvMesg(&irqmgr->irqQueue, (OSMesg*)&interrupt, OS_MESG_BLOCK); + switch (interrupt) { + case 0x29A: + IrqMgr_HandleRetrace(irqmgr); + break; + case 0x29D: + IrqMgr_HandlePreNMI(irqmgr); + break; + case 0x29F: + IrqMgr_HandlePRENMI450(irqmgr); + break; + case 0x2A0: + IrqMgr_HandlePRENMI480(irqmgr); + break; + case 0x2A1: + IrqMgr_HandlePRENMI500(irqmgr); + break; + } + } +} + +void IrqMgr_Init(IrqMgr* irqmgr, void* stack, OSPri pri, u8 retraceCount) { + irqmgr->callbacks = NULL; + irqmgr->verticalRetraceMesg.type = 1; + irqmgr->prenmiMsg.type = 4; + irqmgr->nmiMsg.type = 3; + irqmgr->prenmiStage = 0; + irqmgr->lastPrenmiTime = 0; + + osCreateMesgQueue(&irqmgr->irqQueue, (OSMesg*)irqmgr->irqBuffer, ARRAY_COUNT(irqmgr->irqBuffer)); + osSetEventMesg(OS_EVENT_PRENMI, &irqmgr->irqQueue, (OSMesg)0x29D); + osViSetEvent(&irqmgr->irqQueue, (OSMesg)0x29A, retraceCount); + + osCreateThread(&irqmgr->thread, Z_THREAD_ID_IRQMGR, IrqMgr_ThreadEntry, irqmgr, stack, pri); + osStartThread(&irqmgr->thread); +} diff --git a/src/boot/syncprintf.c b/src/boot/syncprintf.c new file mode 100644 index 000000000..7ceb04bb1 --- /dev/null +++ b/src/boot/syncprintf.c @@ -0,0 +1,10 @@ +#include "global.h" + +void osSyncPrintfUnused(const char* fmt, ...) { +} + +void osSyncPrintf(const char* fmt, ...) { +} + +void rmonPrintf(const char* fmt, ...) { +} diff --git a/src/boot/viconfig.c b/src/boot/viconfig.c new file mode 100644 index 000000000..ccba4e932 --- /dev/null +++ b/src/boot/viconfig.c @@ -0,0 +1,57 @@ +#include "global.h" +#include "idle.h" + +void ViConfig_UpdateVi(u32 black) { + if (black) { + switch (osTvType) { + case OS_TV_MPAL: + osViSetMode(&osViModeMpalLan1); + break; + + case OS_TV_PAL: + osViSetMode(&osViModePalLan1); + break; + + case OS_TV_NTSC: + default: + osViSetMode(&osViModeNtscLan1); + break; + } + + if (gViConfigFeatures != 0) { + osViSetSpecialFeatures(gViConfigFeatures); + } + + if (gViConfigYScale != 1) { + osViSetYScale(1); + } + } else { + osViSetMode(&gViConfigMode); + + if (gViConfigAdditionalScanLines != 0) { + osViExtendVStart(gViConfigAdditionalScanLines); + } + + if (gViConfigFeatures != 0) { + osViSetSpecialFeatures(gViConfigFeatures); + } + + if (gViConfigXScale != 1) { + osViSetXScale(gViConfigXScale); + } + + if (gViConfigYScale != 1) { + osViSetYScale(gViConfigYScale); + } + } + + gViConfigUseBlack = black; +} + +void ViConfig_UpdateBlack(void) { + if (gViConfigUseBlack) { + osViBlack(true); + } else { + osViBlack(false); + } +} diff --git a/src/boot/yaz0.c b/src/boot/yaz0.c new file mode 100644 index 000000000..edb7fc5cc --- /dev/null +++ b/src/boot/yaz0.c @@ -0,0 +1,150 @@ +#include "global.h" +#include "fault.h" + +u8 sYaz0DataBuffer[0x400]; +u8* sYaz0CurDataEnd; +uintptr_t sYaz0CurRomStart; +u32 sYaz0CurSize; +u8* sYaz0MaxPtr; +void* gYaz0DecompressDstEnd; + +void* Yaz0_FirstDMA() { + u32 pad0; + u32 dmaSize; + u32 curSize; + + sYaz0MaxPtr = sYaz0CurDataEnd - 0x19; + + curSize = (u32)sYaz0CurDataEnd - (u32)sYaz0DataBuffer; + dmaSize = (curSize > sYaz0CurSize) ? sYaz0CurSize : curSize; + + DmaMgr_DmaRomToRam(sYaz0CurRomStart, sYaz0DataBuffer, dmaSize); + sYaz0CurRomStart += dmaSize; + sYaz0CurSize -= dmaSize; + return sYaz0DataBuffer; +} + +void* Yaz0_NextDMA(void* curSrcPos) { + u8* dst; + u32 restSize; + u32 dmaSize; + OSPri oldPri; + + restSize = (u32)sYaz0CurDataEnd - (u32)curSrcPos; + + dst = (restSize & 7) ? (sYaz0DataBuffer - (restSize & 7)) + 8 : sYaz0DataBuffer; + + bcopy(curSrcPos, dst, restSize); + dmaSize = ((u32)sYaz0CurDataEnd - (u32)dst) - restSize; + if (sYaz0CurSize < dmaSize) { + dmaSize = sYaz0CurSize; + } + + if (dmaSize != 0) { + DmaMgr_DmaRomToRam(sYaz0CurRomStart, dst + restSize, dmaSize); + sYaz0CurRomStart += dmaSize; + sYaz0CurSize -= dmaSize; + if (!sYaz0CurSize) { + sYaz0MaxPtr = dst + restSize + dmaSize; + } + } else { + oldPri = osGetThreadPri(NULL); + osSetThreadPri(NULL, 0x7F); + osSyncPrintf("圧縮展開異常\n"); + osSetThreadPri(NULL, oldPri); + } + + return dst; +} + +typedef struct { + /* 0x0 */ u32 magic; // Yaz0 + /* 0x4 */ u32 decSize; + /* 0x8 */ u32 compInfoOffset; // only used in mio0 + /* 0xC */ u32 uncompDataOffset; // only used in mio0 +} Yaz0Header; // size = 0x10 + +#define YAZ0_MAGIC 0x59617A30 // "Yaz0" + +s32 Yaz0_DecompressImpl(u8* src, u8* dst) { + u32 bitIdx = 0; + u8* dstEnd; + u32 chunkHeader = 0; + u32 nibble; + u8* backPtr; + s32 chunkSize; + u32 off; + u32 magic; + + magic = ((Yaz0Header*)src)->magic; + + if (magic != YAZ0_MAGIC) { + return -1; + } + + dstEnd = dst + ((Yaz0Header*)src)->decSize; + src = src + sizeof(Yaz0Header); + + do { + if (bitIdx == 0) { + if ((sYaz0MaxPtr < src) && (sYaz0CurSize != 0)) { + src = Yaz0_NextDMA(src); + } + + chunkHeader = *src++; + bitIdx = 8; + } + + if (chunkHeader & (1 << 7)) { // uncompressed + *dst = *src; + dst++; + src++; + } else { // compressed + off = ((*src & 0xF) << 8 | *(src + 1)); + nibble = *src >> 4; + backPtr = dst - off; + src += 2; + + chunkSize = (nibble == 0) // N = chunkSize; B = back offset + ? (u32)(*src++ + 0x12) // 3 bytes 0B BB NN + : nibble + 2; // 2 bytes NB BB + + do { + *dst++ = *(backPtr++ - 1); + chunkSize--; + } while (chunkSize != 0); + } + chunkHeader <<= 1; + bitIdx--; + } while (dst != dstEnd); + + gYaz0DecompressDstEnd = dstEnd; + + return 0; +} + +void Yaz0_Decompress(uintptr_t romStart, void* dst, size_t size) { + s32 status; + u32 pad; + char sp80[0x50]; + char sp30[0x50]; + + if (sYaz0CurDataEnd != NULL) { + while (sYaz0CurDataEnd != NULL) { + Sleep_Usec(10); + } + } + + sYaz0CurDataEnd = sYaz0DataBuffer + sizeof(sYaz0DataBuffer); + sYaz0CurRomStart = romStart; + sYaz0CurSize = size; + status = Yaz0_DecompressImpl(Yaz0_FirstDMA(), dst); + + if (status != 0) { + sprintf(sp80, "slidma slidstart_szs ret=%d", status); + sprintf(sp30, "src:%08lx dst:%08lx siz:%08lx", romStart, dst, size); + Fault_AddHungupAndCrashImpl(sp80, sp30); + } + + sYaz0CurDataEnd = NULL; +} diff --git a/src/boot/z_std_dma.c b/src/boot/z_std_dma.c new file mode 100644 index 000000000..ac4992264 --- /dev/null +++ b/src/boot/z_std_dma.c @@ -0,0 +1,242 @@ +#include "prevent_bss_reordering.h" +#include "global.h" +#include "fault.h" +#include "stack.h" +#include "stackcheck.h" +#include "z64thread.h" + +size_t gDmaMgrDmaBuffSize = 0x2000; + +StackEntry sDmaMgrStackInfo; +u16 sNumDmaEntries; +OSMesgQueue sDmaMgrMsgQueue; +OSMesg sDmaMgrMsgs[32]; +OSThread sDmaMgrThread; +STACK(sDmaMgrStack, 0x500); + +s32 DmaMgr_DmaRomToRam(uintptr_t rom, void* ram, size_t size) { + OSIoMesg ioMsg; + OSMesgQueue queue; + OSMesg msg[1]; + s32 ret; + size_t buffSize = gDmaMgrDmaBuffSize; + + osInvalDCache(ram, size); + osCreateMesgQueue(&queue, msg, ARRAY_COUNT(msg)); + + if (buffSize != 0) { + while (buffSize < size) { + ioMsg.hdr.pri = 0; + ioMsg.hdr.retQueue = &queue; + ioMsg.devAddr = rom; + ioMsg.dramAddr = ram; + ioMsg.size = buffSize; + ret = osEPiStartDma(gCartHandle, &ioMsg, 0); + if (ret) { + goto END; + } + + osRecvMesg(&queue, NULL, OS_MESG_BLOCK); + size -= buffSize; + rom = rom + buffSize; + ram = (u8*)ram + buffSize; + } + } + ioMsg.hdr.pri = 0; + ioMsg.hdr.retQueue = &queue; + ioMsg.devAddr = rom; + ioMsg.dramAddr = ram; + ioMsg.size = size; + ret = osEPiStartDma(gCartHandle, &ioMsg, 0); + if (ret) { + goto END; + } + + osRecvMesg(&queue, NULL, OS_MESG_BLOCK); + + osInvalDCache(ram, size); + +END: + return ret; +} + +s32 DmaMgr_DmaHandler(OSPiHandle* pihandle, OSIoMesg* mb, s32 direction) { + return osEPiStartDma(pihandle, mb, direction); +} + +DmaEntry* DmaMgr_FindDmaEntry(uintptr_t vrom) { + DmaEntry* curr; + + for (curr = dmadata; curr->vromEnd != 0; curr++) { + if (vrom < curr->vromStart) { + continue; + } + if (vrom >= curr->vromEnd) { + continue; + } + + return curr; + } + + return NULL; +} + +u32 DmaMgr_TranslateVromToRom(uintptr_t vrom) { + DmaEntry* entry = DmaMgr_FindDmaEntry(vrom); + + if (entry != NULL) { + if (entry->romEnd == 0) { + return vrom + entry->romStart - entry->vromStart; + } + + if (vrom == entry->vromStart) { + return entry->romStart; + } + + return -1; + } + + return -1; +} + +s32 DmaMgr_FindDmaIndex(uintptr_t vrom) { + DmaEntry* entry = DmaMgr_FindDmaEntry(vrom); + + if (entry != NULL) { + return entry - dmadata; + } + + return -1; +} + +const char* func_800809F4(u32 a0) { + return "??"; +} + +void DmaMgr_ProcessMsg(DmaRequest* req) { + uintptr_t vrom; + void* ram; + size_t size; + uintptr_t romStart; + size_t romSize; + DmaEntry* dmaEntry; + s32 index; + + vrom = req->vromAddr; + ram = req->dramAddr; + size = req->size; + + index = DmaMgr_FindDmaIndex(vrom); + + if ((index >= 0) && (index < sNumDmaEntries)) { + dmaEntry = &dmadata[index]; + if (dmaEntry->romEnd == 0) { + if (dmaEntry->vromEnd < (vrom + size)) { + Fault_AddHungupAndCrash("../z_std_dma.c", 499); + } + DmaMgr_DmaRomToRam((dmaEntry->romStart + vrom) - dmaEntry->vromStart, (u8*)ram, size); + return; + } + + romSize = dmaEntry->romEnd - dmaEntry->romStart; + romStart = dmaEntry->romStart; + + if (vrom != dmaEntry->vromStart) { + Fault_AddHungupAndCrash("../z_std_dma.c", 518); + } + + if (size != (dmaEntry->vromEnd - dmaEntry->vromStart)) { + Fault_AddHungupAndCrash("../z_std_dma.c", 525); + } + + osSetThreadPri(NULL, 10); + Yaz0_Decompress(romStart, ram, romSize); + osSetThreadPri(NULL, 17); + } else { + Fault_AddHungupAndCrash("../z_std_dma.c", 558); + } +} + +void DmaMgr_ThreadEntry(void* a0) { + OSMesg msg; + DmaRequest* req; + + while (1) { + osRecvMesg(&sDmaMgrMsgQueue, &msg, OS_MESG_BLOCK); + + if (msg == NULL) { + break; + } + + req = (DmaRequest*)msg; + + DmaMgr_ProcessMsg(req); + if (req->notifyQueue) { + osSendMesg(req->notifyQueue, req->notifyMsg, OS_MESG_NOBLOCK); + } + } +} + +s32 DmaMgr_SendRequestImpl(DmaRequest* request, void* vramStart, uintptr_t vromStart, size_t size, UNK_TYPE4 unused, + OSMesgQueue* queue, OSMesg msg) { + if (gIrqMgrResetStatus >= 2) { + return -2; + } + + request->vromAddr = vromStart; + request->dramAddr = vramStart; + request->size = size; + request->unk14 = 0; + request->notifyQueue = queue; + request->notifyMsg = msg; + + osSendMesg(&sDmaMgrMsgQueue, request, OS_MESG_BLOCK); + + return 0; +} + +s32 DmaMgr_SendRequest0(void* vramStart, uintptr_t vromStart, size_t size) { + DmaRequest req; + OSMesgQueue queue; + OSMesg msg[1]; + s32 ret; + + osCreateMesgQueue(&queue, msg, ARRAY_COUNT(msg)); + + ret = DmaMgr_SendRequestImpl(&req, vramStart, vromStart, size, 0, &queue, NULL); + + if (ret == -1) { + return ret; + } else { + osRecvMesg(&queue, NULL, OS_MESG_BLOCK); + } + + return 0; +} + +void DmaMgr_Start(void) { + DmaMgr_DmaRomToRam(SEGMENT_ROM_START(dmadata), dmadata, SEGMENT_ROM_SIZE(dmadata)); + + { + DmaEntry* iter = dmadata; + u32 idx = 0; + + while (iter->vromEnd != 0) { + iter++; + idx++; + } + + sNumDmaEntries = idx; + } + + osCreateMesgQueue(&sDmaMgrMsgQueue, sDmaMgrMsgs, ARRAY_COUNT(sDmaMgrMsgs)); + StackCheck_Init(&sDmaMgrStackInfo, sDmaMgrStack, STACK_TOP(sDmaMgrStack), 0, 0x100, "dmamgr"); + osCreateThread(&sDmaMgrThread, Z_THREAD_ID_DMAMGR, DmaMgr_ThreadEntry, NULL, STACK_TOP(sDmaMgrStack), + Z_PRIORITY_DMAMGR); + + osStartThread(&sDmaMgrThread); +} + +void DmaMgr_Stop(void) { + osSendMesg(&sDmaMgrMsgQueue, NULL, OS_MESG_BLOCK); +} |
