From 783014e53254fe17102a34c30120eeabf5227a47 Mon Sep 17 00:00:00 2001 From: spicyjpeg Date: Wed, 19 Oct 2022 14:15:28 +0200 Subject: Clean up SDK debug logging, fix getTPage() --- libpsn00b/libc/_start.s | 18 ------------------ libpsn00b/libc/_stubs.s | 27 +++++++++++++++++++++++++++ libpsn00b/libc/abort.c | 21 +++++++++++++-------- 3 files changed, 40 insertions(+), 26 deletions(-) delete mode 100644 libpsn00b/libc/_start.s create mode 100644 libpsn00b/libc/_stubs.s (limited to 'libpsn00b/libc') diff --git a/libpsn00b/libc/_start.s b/libpsn00b/libc/_start.s deleted file mode 100644 index 56075c8..0000000 --- a/libpsn00b/libc/_start.s +++ /dev/null @@ -1,18 +0,0 @@ -# PSn00bSDK _start() trampoline -# (C) 2022 spicyjpeg - MPL licensed -# -# This file provides a weak function that can be easily overridden to e.g. set -# $sp or perform additional initialization before the "real" _start() -# (_start_inner()) is called. - -.set noreorder -.section .text - -.global _start -.type _start, @function -.weak _start -_start: - la $gp, _gp - - j _start_inner - nop diff --git a/libpsn00b/libc/_stubs.s b/libpsn00b/libc/_stubs.s new file mode 100644 index 0000000..aa7bfbe --- /dev/null +++ b/libpsn00b/libc/_stubs.s @@ -0,0 +1,27 @@ +# PSn00bSDK _start() trampoline and logging endpoint +# (C) 2022 spicyjpeg - MPL licensed +# +# This file provides a weak function that can be easily overridden to e.g. set +# $sp or perform additional initialization before the "real" _start() function +# (_start_inner()) is called. The _sdk_log_inner() function called by other +# libraries to log debug messages can also be overridden in a similar way. + +.set noreorder + +.section .text._start +.global _start +.type _start, @function +.weak _start +_start: + la $gp, _gp + j _start_inner + nop + +.section .text._sdk_log_inner +.global _sdk_log_inner +.type _sdk_log_inner, @function +.weak _sdk_log_inner +_sdk_log_inner: + li $t2, 0xa0 + jr $t2 + li $t1, 0x3f diff --git a/libpsn00b/libc/abort.c b/libpsn00b/libc/abort.c index 2db5016..9a4661a 100644 --- a/libpsn00b/libc/abort.c +++ b/libpsn00b/libc/abort.c @@ -1,19 +1,24 @@ +/* + * PSn00bSDK assert macro and internal logging + * (C) 2022 spicyjpeg - MPL licensed + */ -#include +#include +#include -/* Standard abort */ +/* Internal function used by assert() macro */ -void abort(void) { - _sdk_log("abort()\n"); +void _assert_abort(const char *file, int line, const char *expr) { + _sdk_log_inner("%s:%d: assert(%s)\n", file, line, expr); for (;;) __asm__ volatile(""); } -/* Internal function used by assert() macro */ +/* Standard abort */ -void _assert_abort(const char *file, int line, const char *expr) { - _sdk_log("%s:%d: assert(%s)\n", file, line, expr); +void abort(void) { + _sdk_log_inner("abort()\n"); for (;;) __asm__ volatile(""); @@ -22,7 +27,7 @@ void _assert_abort(const char *file, int line, const char *expr) { /* Pure virtual function call (C++) */ void __cxa_pure_virtual(void) { - _sdk_log("__cxa_pure_virtual()\n"); + _sdk_log_inner("__cxa_pure_virtual()\n"); for (;;) __asm__ volatile(""); -- cgit v1.2.3 From b1632d7df0e840692612461a80d0e05d6a3228ed Mon Sep 17 00:00:00 2001 From: spicyjpeg Date: Fri, 21 Oct 2022 13:24:54 +0200 Subject: Rewrite psxsio with new API, fix compiler warnings --- CHANGELOG.md | 6 +- libpsn00b/include/assert.h | 7 +- libpsn00b/include/psxsio.h | 258 +++++++++++++++++++++++++++++++-------- libpsn00b/libc/memset.s | 5 + libpsn00b/psxcd/isofs.c | 1 + libpsn00b/psxetc/dl.c | 3 +- libpsn00b/psxgpu/common.c | 68 ++++++----- libpsn00b/psxsio/_sio_control.s | 184 ---------------------------- libpsn00b/psxsio/sio.c | 263 ++++++++++++++++++++++++++++++++++++++++ libpsn00b/psxsio/siocons.c | 220 --------------------------------- libpsn00b/psxsio/tty.c | 107 ++++++++++++++++ 11 files changed, 631 insertions(+), 491 deletions(-) delete mode 100644 libpsn00b/psxsio/_sio_control.s create mode 100644 libpsn00b/psxsio/sio.c delete mode 100644 libpsn00b/psxsio/siocons.c create mode 100644 libpsn00b/psxsio/tty.c (limited to 'libpsn00b/libc') diff --git a/CHANGELOG.md b/CHANGELOG.md index fb40eb6..e9d7e16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ to ensure the changelog can be parsed correctly. ------------------------------------------------------------------------------- -## 2022-10-19: 0.21 +## 2022-10-21: 0.21 spicyjpeg: @@ -31,6 +31,10 @@ spicyjpeg: for more advanced control of the drawing queue. The `getTPage()` macro now supports extended Y coordinates (512-1023) on systems with 2 MB of VRAM. +- psxsio: Removed `_sio_control()` and replaced it with a completely new + asynchronous buffered serial port driver. Rewritten the serial TTY driver to + make use of the new API. + ## 2022-10-16 spicyjpeg: diff --git a/libpsn00b/include/assert.h b/libpsn00b/include/assert.h index eabe369..e93c983 100644 --- a/libpsn00b/include/assert.h +++ b/libpsn00b/include/assert.h @@ -9,6 +9,9 @@ #ifndef __ASSERT_H #define __ASSERT_H +void _assert_abort(const char *file, int line, const char *expr); +void _sdk_log_inner(const char *fmt, ...); + #ifdef NDEBUG #define assert(expr) @@ -16,12 +19,10 @@ #else -void _assert_abort(const char *file, int line, const char *expr); -void _sdk_log_inner(const char *fmt, ...); - #define assert(expr) { \ if (!(expr)) _assert_abort(__FILE__, __LINE__, #expr); \ } + #ifdef SDK_LIBRARY_NAME #define _sdk_log(fmt, ...) _sdk_log_inner(SDK_LIBRARY_NAME ": " fmt, ##__VA_ARGS__) #else diff --git a/libpsn00b/include/psxsio.h b/libpsn00b/include/psxsio.h index 3f571d7..e0cc49b 100644 --- a/libpsn00b/include/psxsio.h +++ b/libpsn00b/include/psxsio.h @@ -1,64 +1,220 @@ +/* + * PSn00bSDK serial port library + * (C) 2019-2022 Lameguy64, spicyjpeg - MPL licensed + */ + #ifndef __PSXSIO_H #define __PSXSIO_H -#define SR_TXRDY 0x1 -#define SR_RXRDY 0x2 -#define SR_TXU 0x4 -#define SR_PERROR 0x8 -#define SR_OE 0x10 -#define SR_FE 0x20 -#define SR_DSR 0x80 -#define SR_CTS 0x100 -#define SR_IRQ 0x200 - -#define SIO_TXRDY 0x1 -#define SIO_RXRDY 0x2 -#define SIO_TXU 0x4 -#define SIO_PERROR 0x8 -#define SIO_OE 0x10 -#define SIO_FE 0x20 -#define SIO_DSR 0x80 -#define SIO_CTS 0x100 -#define SIO_IRQ 0x200 - -#define MR_CHLEN_5 0x00 -#define MR_CHLEN_6 0x04 -#define MR_CHLEN_7 0x08 -#define MR_CHLEN_8 0x0C -#define MR_PEN 0x10 -#define MR_P_EVEN 0x30 -#define MR_SB_01 0x40 -#define MR_SB_10 0x80 -#define MR_SB_11 0xc0 - -#define CR_TXEN 0x1 -#define CR_DTR 0x2 -#define CR_RXEN 0x4 -#define CR_BRK 0x8 -#define CR_INTRST 0x10 -#define CR_RTS 0x20 -#define CR_ERRRST 0x40 -#define CR_BUFSZ_1 0x00 -#define CR_BUFSZ_2 0x100 -#define CR_BUFSZ_4 0x200 -#define CR_BUFSZ_8 0x300 -#define CR_TXIEN 0x400 -#define CR_RXIEN 0x800 -#define CR_DSRIEN 0x1000 +#include + +/* Register definitions (used internally) */ + +typedef enum _SIO_StatusRegFlag { + SR_TXRDY = 1 << 0, + SR_RXRDY = 1 << 1, + SR_TXU = 1 << 2, + SR_PERROR = 1 << 3, + SR_OE = 1 << 4, + SR_FE = 1 << 5, + SR_DSR = 1 << 7, + SR_CTS = 1 << 8, + SR_IRQ = 1 << 9 +} SIO_StatusRegFlag; + +typedef enum _SIO_ModeRegFlag { + MR_BR_1 = 1 << 0, + MR_BR_16 = 2 << 0, + MR_BR_64 = 3 << 0, + MR_CHLEN_5 = 0 << 2, + MR_CHLEN_6 = 1 << 2, + MR_CHLEN_7 = 2 << 2, + MR_CHLEN_8 = 3 << 2, + MR_PEN = 1 << 4, + MR_P_EVEN = 1 << 5, + MR_SB_01 = 1 << 6, + MR_SB_10 = 2 << 6, + MR_SB_11 = 3 << 6 +} SIO_ModeRegFlag; + +typedef enum _SIO_ControlRegFlag { + CR_TXEN = 1 << 0, + CR_DTR = 1 << 1, + CR_RXEN = 1 << 2, + CR_BRK = 1 << 3, + CR_INTRST = 1 << 4, + CR_RTS = 1 << 5, + CR_ERRRST = 1 << 6, + CR_BUFSZ_1 = 0 << 8, + CR_BUFSZ_2 = 1 << 8, + CR_BUFSZ_4 = 2 << 8, + CR_BUFSZ_8 = 3 << 8, + CR_TXIEN = 1 << 10, + CR_RXIEN = 1 << 11, + CR_DSRIEN = 1 << 12 +} SIO_ControlRegFlag; + +typedef enum _SIO_FlowControl { + SIO_FC_NONE = 0, + SIO_FC_RTS_CTS = 1 + //SIO_FC_DTR_DSR = 2 +} SIO_FlowControl; + +/* Public API */ #ifdef __cplusplus extern "C" { #endif -int _sio_control(int cmd, int arg, int param); -void AddSIO(int baud); -void DelSIO(void); +/** + * @brief Resets the serial port, initializes the library's internal ring + * buffers and installs a serial IRQ handler. The given mode value (normally + * MR_CHLEN_8|MR_SB_01 for 8 data bits, 1 stop bit and no parity) is copied to + * the SIO_MODE register. Flow control is disabled by default (see + * SIO_SetFlowControl() for more details). + * + * This function must be called prior to using SIO_ReadByte(), SIO_ReadSync(), + * SIO_WriteByte(), SIO_WriteSync() or SIO_SetFlowControl(), and must not be + * called from an IRQ callback. + * + * @param baud Baud rate in bits per second + * @param mode Binary OR of SIO_ModeRegFlag enum members + */ +void SIO_Init(int baud, uint16_t mode); + +/** + * @brief Resets the serial port and removes the IRQ callback added by + * SIO_Init(), restoring any previously installed handler. + */ +void SIO_Quit(void); -void *Sio1Callback(void (*func)(void)); +/** + * @brief Changes the serial port's flow control mode. The following modes are + * available: + * + * - SIO_FC_NONE (default): do not assert RTS or DTR automatically and ignore + * DSR. Note that the hardware will still wait for CTS to be asserted before + * sending any data; there is no way to disable this behavior. + * - SIO_FC_RTS_CTS: assert RTS when the RX buffer is full and wait for CTS to + * be asserted before sending any data. + * + * The flow control mode shall only be changed while the TX and RX buffers are + * empty. + * + * @param mode + */ +void SIO_SetFlowControl(SIO_FlowControl mode); -// ORIGINAL -void WaitSIO(void); -int kbhit(); +/** + * @brief Reads a byte from the RX buffer. If the buffer is empty, blocks + * indefinitely until a byte is received. + * + * WARNING: this function shall not be used in a critical section or IRQ + * callback as no data is sent or received while interrupts are disabled. It + * also lacks a timeout, so consider polling for new data using SIO_ReadByte2() + * or SIO_ReadSync(1) and implementing a timeout instead. + * + * @return Received byte + */ +int SIO_ReadByte(void); + +/** + * @brief Non-blocking variant of SIO_ReadByte(). Reads a byte from the RX + * buffer or returns -1 if the buffer is empty. Unlike SIO_ReadByte() this + * function is safe to use in a critical section (although no data will be + * received while interrupts are disabled). + * + * @return Received byte, -1 if no data is available + */ +int SIO_ReadByte2(void); + +/** + * @brief Waits for at least one byte to be available in the RX buffer (if + * mode = 0) or returns the length of the RX buffer (if mode = 1). + * + * WARNING: this function shall not be used in a critical section or IRQ + * callback as no data is sent or received while interrupts are disabled. Using + * SIO_ReadSync(0) is additionally discouraged as it lacks a timeout; consider + * polling for new data using SIO_ReadByte2() or SIO_ReadSync(1) and + * implementing a timeout instead. + * + * @param mode + * @return Number of RX bytes in the buffer + */ +int SIO_ReadSync(int mode); + +/** + * @brief Registers a function to be called whenever a byte is received. The + * received byte is appended to the RX buffer and passed as an argument to the + * callback. The callback will run in the exception handler's context, so it + * should be as fast as possible and not use any function that relies on + * interrupts in order to work. + * + * @param func + * @return Previously set callback or NULL + */ +void *SIO_ReadCallback(void (*func)(uint8_t)); + +/** + * @brief Sends the given byte, or appends it to the TX buffer if the serial + * port is busy. If the buffer is full, blocks until the byte can be stored in + * the buffer (with a timeout). + * + * WARNING: this function shall not be used in a critical section or IRQ + * callback as no data is sent or received while interrupts are disabled. + * + * @param value + * @return Number of TX bytes previously pending, -1 in case of a timeout + */ +int SIO_WriteByte(uint8_t value); + +/** + * @brief Non-blocking variant of SIO_WriteByte(). Sends the given byte, or + * appends it to the TX buffer if the serial port is busy. If the buffer is + * full, returns -1 without actually sending the byte. Unlike SIO_WriteByte() + * this function is safe to use in a critical section (although no data will be + * sent while interrupts are disabled). + * + * @param value + * @return Number of TX bytes previously pending, -1 in case of failure + */ +int SIO_WriteByte2(uint8_t value); + +/** + * @brief Waits for all bytes pending in the TX buffer to be sent (if mode = 0) + * or returns the length of the TX buffer (if mode = 1). + * + * WARNING: this function shall not be used in a critical section or IRQ + * callback as no data is sent or received while interrupts are disabled. + * + * @param mode + * @return Number of TX bytes pending, -1 in case of a timeout (mode = 0) + */ +int SIO_WriteSync(int mode); + +/** + * @brief Installs a BIOS file driver to redirect TTY stdin/stdout (including + * BIOS messages as well as PSn00bSDK's own debug logging) to the serial port. + * Uses SIO_Init() internally. The port is configured for 8 data bits, 1 stop + * bit and no parity. + * + * This function shall only be used for debugging purposes. Picking a high baud + * rate is recommended as all TTY writes are blocking and bypass the TX buffer. + * + * NOTE: some executable loaders, such as Unirom and Caetla, already replace + * the BIOS TTY driver with a custom one. Calling AddSIO() will break the + * built-in TTY functionality of these loaders. + * + * @param baud Baud rate in bits per second + */ +void AddSIO(int baud); + +/** + * @brief Uninstalls the BIOS driver installed by AddSIO() and attempts to + * restore a "dummy" TTY driver. Calling this function is not recommended as + * any further TTY usage may crash the system. + */ +void DelSIO(void); #ifdef __cplusplus } diff --git a/libpsn00b/libc/memset.s b/libpsn00b/libc/memset.s index 5a1589d..73f92bd 100644 --- a/libpsn00b/libc/memset.s +++ b/libpsn00b/libc/memset.s @@ -9,6 +9,7 @@ memset: # If more than 16 bytes have to be written then take the "large" path, # otherwise use the code below. + blez $a2, .Lnull_count addiu $t0, $a2, -16 bgtz $t0, .Llarge_fill move $v0, $a0 # return_value = dest @@ -41,6 +42,10 @@ memset: jr $ra sb $a1, 0xf($a0) +.Lnull_count: + jr $ra + move $v0, $a0 # return_value = dest + .Llarge_fill: # Initialize fast filling by repeating the fill byte 4 times, so it can be # written 32 bits at a time. diff --git a/libpsn00b/psxcd/isofs.c b/libpsn00b/psxcd/isofs.c index cb60152..e00ddeb 100644 --- a/libpsn00b/psxcd/isofs.c +++ b/libpsn00b/psxcd/isofs.c @@ -1,4 +1,5 @@ +#undef SDK_LIBRARY_NAME #define SDK_LIBRARY_NAME "psxcd/iso" #include diff --git a/libpsn00b/psxetc/dl.c b/libpsn00b/psxetc/dl.c index 51c1d2d..fa5e74d 100644 --- a/libpsn00b/psxetc/dl.c +++ b/libpsn00b/psxetc/dl.c @@ -23,6 +23,7 @@ * of entries */ +#undef SDK_LIBRARY_NAME #define SDK_LIBRARY_NAME "psxetc/dl" #include @@ -234,7 +235,7 @@ int32_t DL_ParseSymbolMap(const char *ptr, size_t size) { // insists on printing 64-bit addresses... wtf) and normalize the // type letter to upper case, then check if the entry is valid and // non-null. - void *address = (void *) address64; + void *address = (void *) ((uint32_t) address64); char _type = toupper(type_string[0]); uint32_t hash = _elf_hash(name); uint32_t hash_mod = hash % _symbol_map.nbucket; diff --git a/libpsn00b/psxgpu/common.c b/libpsn00b/psxgpu/common.c index 9eb4ea4..9f45f10 100644 --- a/libpsn00b/psxgpu/common.c +++ b/libpsn00b/psxgpu/common.c @@ -38,6 +38,9 @@ static volatile uint16_t _last_hblank; /* Private interrupt handlers */ +#define _ENTER_CRITICAL() uint16_t mask = IRQ_MASK; IRQ_MASK = 0; +#define _EXIT_CRITICAL() IRQ_MASK = mask; + static void _vblank_handler(void) { _vblank_counter++; @@ -51,9 +54,10 @@ static void _gpu_dma_handler(void) { __asm__ volatile(""); if (--_queue_length) { - volatile QueueEntry *entry = &_draw_queue[_queue_head++]; - _queue_head %= QUEUE_LENGTH; + int head = _queue_head; + _queue_head = (head + 1) % QUEUE_LENGTH; + volatile QueueEntry *entry = &_draw_queue[head]; entry->func(entry->arg1, entry->arg2, entry->arg3); } else { GPU_GP1 = 0x04000000; // Disable DMA request @@ -145,19 +149,22 @@ int VSync(int mode) { } void *VSyncHaltFunction(void (*func)(void)) { + //_ENTER_CRITICAL(); + void *old_callback = _vsync_halt_func; _vsync_halt_func = func; + //_EXIT_CRITICAL(); return old_callback; } void *VSyncCallback(void (*func)(void)) { - EnterCriticalSection(); + _ENTER_CRITICAL(); void *old_callback = _vsync_callback; _vsync_callback = func; - ExitCriticalSection(); + _EXIT_CRITICAL(); return old_callback; } @@ -176,37 +183,36 @@ int EnqueueDrawOp( // to checking if DMA is busy; disabling them afterwards would create a // race condition where the DMA transfer could end while interrupts are // being disabled. Interrupts are disabled through the IRQ_MASK register - // rather than by calling EnterCriticalSection() for performance reasons. - uint16_t mask = IRQ_MASK; - IRQ_MASK = 0; + // rather than via syscalls for performance reasons. + _ENTER_CRITICAL(); + int length = _queue_length; - if (_queue_length) { - int length = _queue_length; - - if (length >= QUEUE_LENGTH) { - IRQ_MASK = mask; - _sdk_log("draw queue overflow, dropping commands\n"); - return -1; - } + if (!length) { + _queue_length = 1; + _EXIT_CRITICAL(); - volatile QueueEntry *entry = &_draw_queue[_queue_tail++]; - _queue_tail %= QUEUE_LENGTH; - _queue_length = length + 1; - - entry->func = func; - entry->arg1 = arg1; - entry->arg2 = arg2; - entry->arg3 = arg3; + func(arg1, arg2, arg3); + return 0; + } + if (length >= QUEUE_LENGTH) { + _EXIT_CRITICAL(); - IRQ_MASK = mask; - return length; + _sdk_log("draw queue overflow, dropping commands\n"); + return -1; } - _queue_length = 1; + int tail = _queue_tail; + _queue_tail = (tail + 1) % QUEUE_LENGTH; + _queue_length = length + 1; + + volatile QueueEntry *entry = &_draw_queue[tail]; + entry->func = func; + entry->arg1 = arg1; + entry->arg2 = arg2; + entry->arg3 = arg3; - IRQ_MASK = mask; - func(arg1, arg2, arg3); - return 0; + _EXIT_CRITICAL(); + return length; } int DrawSync(int mode) { @@ -236,12 +242,12 @@ int DrawSync(int mode) { } void *DrawSyncCallback(void (*func)(void)) { - EnterCriticalSection(); + _ENTER_CRITICAL(); void *old_callback = _drawsync_callback; _drawsync_callback = func; - ExitCriticalSection(); + _EXIT_CRITICAL(); return old_callback; } diff --git a/libpsn00b/psxsio/_sio_control.s b/libpsn00b/psxsio/_sio_control.s deleted file mode 100644 index 6378def..0000000 --- a/libpsn00b/psxsio/_sio_control.s +++ /dev/null @@ -1,184 +0,0 @@ -.set noreorder - -.include "hwregs_a.inc" - -.section .text - -# Currently implemented serial control functions: -# -# cmd(a0) sub(a1) -# 0 0 Get serial status -# 0 1 Get control line status -# 0 2 Get serial mode -# 0 3 Get baud rate -# 0 4 Read 1 byte -# 1 1 Set serial control -# 1 2 Set serial mode -# 1 3 Set baud rate -# 1 4 Write 1 byte -# 2 0 Reset serial -# 2 1 Acknowledge serial - -.global _sio_control -.type _sio_control, @function -_sio_control: - - # a0 - command - # a1 - subcommand - # a2 - argument - - lui $a3, IOBASE - - beq $a0, $0, .Lcmd0 - nop - beq $a0, 1, .Lcmd1 - nop - beq $a0, 2, .Lcmd2 - nop - jr $ra - nop - - -.Lcmd0: - - beq $a1, $0, .Lcmd0arg0 - nop - beq $a1, 1, .Lcmd0arg1 - nop - beq $a1, 2, .Lcmd0arg2 - nop - beq $a1, 3, .Lcmd0arg3 - nop - beq $a1, 4, .Lcmd0arg4 - nop - jr $ra - nop - -.Lcmd0arg0: # Get SIO status - - lhu $v0, SIO_STAT($a3) - nop - - jr $ra - andi $v0, 0x3FF - -.Lcmd0arg1: # Get control line status - - lhu $v1, SIO_CTRL($a3) - nop - srl $v0, $v1, 1 - andi $v0, 0x1 - srl $v1, 4 - andi $v1, 0x2 - - jr $ra - or $v0, $v1 - - -.Lcmd0arg2: # Get serial mode - - lhu $v0, SIO_MODE($a3) - nop - jr $ra - andi $v0, 0xFF - -.Lcmd0arg3: - - lui $v1, 0x1f - lhu $v0, SIO_BAUD($a3) - ori $v1, 0xa400 - div $v1, $v0 - nop - nop - mflo $v0 - jr $ra - nop - -.Lcmd0arg4: # Serial RX read - - lbu $v0, SIO_TXRX($a3) - nop - jr $ra - nop - - -.Lcmd1: - - beq $a1, 1, .Lcmd1arg1 - nop - beq $a1, 2, .Lcmd1arg2 - nop - beq $a1, 3, .Lcmd1arg3 - nop - beq $a1, 4, .Lcmd1arg4 - nop - jr $ra - nop - -.Lcmd1arg1: - - andi $v0, $a2, 0x1CFF - sh $a2, SIO_CTRL($a3) - jr $ra - nop - -.Lcmd1arg2: - - sh $a2, SIO_MODE($a3) - jr $ra - nop - -.Lcmd1arg3: - - lui $v0, 0x1f - ori $v0, 0xa400 - divu $v0, $a2 - bnez $a2, .Lgood_baud - nop - jr $ra - nop - -.Lgood_baud: - - mflo $v0 - sh $v0, SIO_BAUD($a3) - nop - jr $ra - nop - -.Lcmd1arg4: - - sb $a2, SIO_TXRX($a3) - nop - jr $ra - nop - -.Lcmd2: - - beq $a1, $0 , .Lcmd2arg0 - li $v0, 1 - beq $a1, $v0, .Lcmd2arg1 - nop - jr $ra - nop - -.Lcmd2arg0: - - li $v0, 0x40 - sh $v0, SIO_CTRL($a3) - sh $0 , SIO_MODE($a3) - sh $0 , SIO_BAUD($a3) - nop - jr $ra - nop - -.Lcmd2arg1: - - lhu $v0, SIO_CTRL($a3) - nop - ori $v0, 0x10 - sh $v0, SIO_CTRL($a3) - jr $ra - nop - - \ No newline at end of file diff --git a/libpsn00b/psxsio/sio.c b/libpsn00b/psxsio/sio.c new file mode 100644 index 0000000..3fb3caf --- /dev/null +++ b/libpsn00b/psxsio/sio.c @@ -0,0 +1,263 @@ +/* + * PSn00bSDK buffered serial port driver + * (C) 2022 spicyjpeg - MPL licensed + * + * TODO: add proper support for DTR/DSR flow control + */ + +#include +#include +#include +#include +#include +#include + +#define BUFFER_LENGTH 128 +#define SIO_SYNC_TIMEOUT 0x100000 + +/* Private types */ + +typedef struct { + uint8_t data[BUFFER_LENGTH]; + uint8_t head, tail, length; +} RingBuffer; + +/* Internal globals */ + +static SIO_FlowControl _flow_control; +static uint16_t _ctrl_reg_flag; + +static void (*_read_callback)(uint8_t) = (void *) 0; +static void (*_old_sio_handler)(void) = (void *) 0; + +static volatile RingBuffer _tx_buffer, _rx_buffer; + +/* Private interrupt handler */ + +#define _ENTER_CRITICAL() uint16_t mask = IRQ_MASK; IRQ_MASK = 0; +#define _EXIT_CRITICAL() IRQ_MASK = mask; + +static void _sio_handler(void) { + // Handle any incoming bytes. + while (SIO_STAT & SR_RXRDY) { + uint8_t value = SIO_TXRX; + int length = _rx_buffer.length; + + if (length >= BUFFER_LENGTH) { + //_sdk_log("RX overrun, dropping bytes\n"); + break; + } + + int tail = _rx_buffer.tail; + _rx_buffer.tail = (tail + 1) % BUFFER_LENGTH; + _rx_buffer.length = length + 1; + + _rx_buffer.data[tail] = value; + if (_read_callback) + _read_callback(value); + } + + // Send the next byte in the buffer if the TX unit is ready. Note that + // checking for CTS is unnecessary as the serial port is already hardwired + // to do so. + if (SIO_STAT & (SR_TXRDY | SR_TXU)) { + int length = _tx_buffer.length; + + if (length) { + int head = _tx_buffer.head; + _tx_buffer.head = (head + 1) % BUFFER_LENGTH; + _tx_buffer.length = length - 1; + + SIO_CTRL |= CR_TXIEN; + SIO_TXRX = _tx_buffer.data[head]; + } else { + SIO_CTRL &= CR_TXIEN ^ 0xffff; + } + } + + // Acknowledge the IRQ and update flow control signals. + if (_rx_buffer.length < BUFFER_LENGTH) + SIO_CTRL = CR_INTRST | (SIO_CTRL | _ctrl_reg_flag); + else + SIO_CTRL = CR_INTRST | (SIO_CTRL & (_ctrl_reg_flag ^ 0xffff)); +} + +/* Serial port initialization API */ + +void SIO_Init(int baud, uint16_t mode) { + EnterCriticalSection(); + _old_sio_handler = InterruptCallback(8, &_sio_handler); + + SIO_CTRL = CR_ERRRST; + SIO_MODE = (mode & 0xfffc) | MR_BR_16; + SIO_BAUD = (uint16_t) ((int) 0x1fa400 / baud); + SIO_CTRL = CR_TXEN | CR_RXEN | CR_RXIEN; + + _tx_buffer.head = 0; + _tx_buffer.tail = 0; + _tx_buffer.length = 0; + _rx_buffer.head = 0; + _rx_buffer.tail = 0; + _rx_buffer.length = 0; + + _flow_control = SIO_FC_NONE; + _ctrl_reg_flag = 0; + + ExitCriticalSection(); +} + +void SIO_Quit(void) { + EnterCriticalSection(); + InterruptCallback(8, _old_sio_handler); + + SIO_CTRL = CR_ERRRST; + + ExitCriticalSection(); +} + +void SIO_SetFlowControl(SIO_FlowControl mode) { + _ENTER_CRITICAL(); + + switch (mode) { + case SIO_FC_NONE: + _flow_control = SIO_FC_NONE; + _ctrl_reg_flag = 0; + + SIO_CTRL &= 0xffff ^ CR_DSRIEN; + break; + + case SIO_FC_RTS_CTS: + _flow_control = SIO_FC_RTS_CTS; + _ctrl_reg_flag = CR_RTS; + + SIO_CTRL &= 0xffff ^ CR_DSRIEN; + break; + + /*case SIO_FC_DTR_DSR: + _flow_control = SIO_FC_DTR_DSR; + _ctrl_reg_flag = CR_DTR; + + SIO_CTRL |= CR_DSRIEN; + break;*/ + } + + _EXIT_CRITICAL(); +} + +/* Reading API */ + +int SIO_ReadByte(void) { + /*for (int i = SIO_SYNC_TIMEOUT; i; i--) { + if (_rx_buffer.length) + return SIO_ReadByte2(); + }*/ + while (!_rx_buffer.length) + __asm__ volatile(""); + + return SIO_ReadByte2(); +} + +int SIO_ReadByte2(void) { + if (!_rx_buffer.length) + return -1; + + _ENTER_CRITICAL(); + + int head = _rx_buffer.head; + _rx_buffer.head = (head + 1) % BUFFER_LENGTH; + _rx_buffer.length--; + + _EXIT_CRITICAL(); + return _rx_buffer.data[head]; +} + +int SIO_ReadSync(int mode) { + if (mode) + return _rx_buffer.length; + + /*for (int i = SIO_SYNC_TIMEOUT; i; i--) { + if (_rx_buffer.length) + return 0; + }*/ + while (!_rx_buffer.length) + __asm__ volatile(""); + + return 0; +} + +void *SIO_ReadCallback(void (*func)(uint8_t)) { + EnterCriticalSection(); + + void *old_callback = _read_callback; + _read_callback = func; + + ExitCriticalSection(); +} + +/* Writing API */ + +int SIO_WriteByte(uint8_t value) { + for (int i = SIO_SYNC_TIMEOUT; i; i--) { + if (_tx_buffer.length < BUFFER_LENGTH) + return SIO_WriteByte2(value); + } + + //_sdk_log("SIO_WriteByte() timeout\n"); + return -1; +} + +int SIO_WriteByte2(uint8_t value) { + // If the TX unit is currently busy, append the byte to the buffer instead + // of sending it immediately. Note that interrupts must be disabled *prior* + // to checking if TX is busy; disabling them afterwards would create a race + // condition where the transfer could end while interrupts are being + // disabled. Interrupts are disabled through the IRQ_MASK register rather + // than via syscalls for performance reasons. + _ENTER_CRITICAL(); + + if (SIO_STAT & (SR_TXRDY | SR_TXU)) { + SIO_TXRX = value; + _EXIT_CRITICAL(); + return 0; + } + + int length = _tx_buffer.length; + + if (length >= BUFFER_LENGTH) { + _EXIT_CRITICAL(); + + //_sdk_log("TX overrun, dropping bytes\n"); + return -1; + } + + int tail = _tx_buffer.tail; + _tx_buffer.tail = (tail + 1) % BUFFER_LENGTH; + _tx_buffer.length = length + 1; + + _tx_buffer.data[tail] = value; + SIO_CTRL |= CR_TXIEN; + + _EXIT_CRITICAL(); + return length; +} + +int SIO_WriteSync(int mode) { + if (mode) + return _tx_buffer.length; + + // Wait for the buffer to become empty. + for (int i = SIO_SYNC_TIMEOUT; i; i--) { + if (!_tx_buffer.length) + break; + } + + if (!_tx_buffer.length) { + // Wait for the TX unit to finish sending the last byte. + while (!(SIO_STAT & (SR_TXRDY | SR_TXU))) + __asm__ volatile(""); + } else { + //_sdk_log("SIO_WriteSync() timeout\n"); + } + + return _tx_buffer.length; +} diff --git a/libpsn00b/psxsio/siocons.c b/libpsn00b/psxsio/siocons.c deleted file mode 100644 index 5937920..0000000 --- a/libpsn00b/psxsio/siocons.c +++ /dev/null @@ -1,220 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#define SIO_BUFF_LEN 32 - -static volatile int _sio_key_pending; - -static volatile int _sio_buff_rpos; -static volatile int _sio_buff_wpos; -static volatile char _sio_buff[SIO_BUFF_LEN]; - -static void _sio_init() { - - _sio_key_pending = 0; - - memset((void*)_sio_buff, 0, SIO_BUFF_LEN); - _sio_buff_rpos = 0; - _sio_buff_wpos = 0; - -} - -static int _sio_open(FCB *fcb, const char* file, int mode) { - - fcb->diskid = 1; - - return 0; - -} - -static int _sio_inout(FCB *fcb, int cmd) { - - int i; - - if(cmd == 2) { // Write - - for(i=0; itrns_len; i++) { - while(!(_sio_control(0, 0, 0) & SR_TXU)); - _sio_control(1, 4, ((char*)fcb->trns_addr)[i]); - } - - return fcb->trns_len; - - } else if (cmd == 1) { // Read - - /*for(i=0; itrns_len; i++) { - while(!(_sio_control(0, 0, 0) & SR_RXRDY)); - ((char*)fcb->trns_addr)[i] = _sio_control(0, 4, 0); - }*/ - - - - for(i=0; itrns_len; i++) { - - while( _sio_key_pending == 0 ); - - ((char*)fcb->trns_addr)[i] = _sio_buff[_sio_buff_rpos]; - _sio_key_pending--; - _sio_buff_rpos++; - if( _sio_buff_rpos >= SIO_BUFF_LEN ) - { - _sio_buff_rpos = 0; - } - - } - - return fcb->trns_len; - - } - - return 0; - -} - -static int _sio_ioctl(FCB *fcb, int cmd, int arg) -{ - if( cmd == FIOCSCAN ) - { - if( _sio_key_pending ) - { - return 0; - } - } - - return -1; -} - -static int _sio_close(int h) { - - return h; - -} - -static void _sio_tty_cb(void) -{ - _sio_key_pending++; - - // Get received byte - if( _sio_key_pending < SIO_BUFF_LEN ) - { - _sio_buff[_sio_buff_wpos] = _sio_control(0, 4, 0); - _sio_buff_wpos++; - if( _sio_buff_wpos >= SIO_BUFF_LEN ) - { - _sio_buff_wpos = 0; - } - } - else - { - _sio_control(0, 4, 0); - } - - // Acknowledge SIO IRQ - _sio_control(2, 1, 0); -} - -static int _sio_dummy(void) -{ - return -1; -} - -static DCB _sio_dcb = { - "tty", - 0x3, - 0x1, - 0x0, - (void*)_sio_init, // init - (void*)_sio_open, // open - (void*)_sio_inout, // inout - _sio_close, // close - _sio_ioctl, // ioctl - _sio_dummy, // read - _sio_dummy, // write - _sio_dummy, // erase - _sio_dummy, // undelete - _sio_dummy, // firstfile - _sio_dummy, // nextfile - _sio_dummy, // format - _sio_dummy, // chdir - _sio_dummy, // rename - _sio_dummy, // remove - _sio_dummy // testdevice -}; - - -volatile void (*_sio_callback)(void) = NULL; - -void AddSIO(int baud) { - - _sio_control(2, 0, 0); - _sio_control(1, 2, MR_SB_01|MR_CHLEN_8|0x02); - _sio_control(1, 3, baud); - _sio_control(1, 1, CR_RXEN|CR_TXEN|CR_RXIEN); - - close(0); - close(1); - - DelDev(_sio_dcb.name); - AddDev(&_sio_dcb); - - Sio1Callback(_sio_tty_cb); - - open(_sio_dcb.name, 2); - open(_sio_dcb.name, 1); - -} - -void DelSIO(void) { - - Sio1Callback(NULL); - - // Reset serial interface - _sio_control(2, 0, 0); - - // Remove TTY device - DelDev(_sio_dcb.name); - - // Add dummy TTY device - AddDummyTty(); - -} - -void WaitSIO(void) { - - while((_sio_control(0, 0, 0)&(SR_RXRDY)) != (SR_RXRDY)); - _sio_control(0, 4, NULL); - -} - -void *Sio1Callback(void (*func)()) { - - void *old_isr; //= *((int*)&_sio_callback); - - EnterCriticalSection(); - - if( func ) { - - old_isr = InterruptCallback(8, func); - //_sio_callback = func; - - } else { - - old_isr = InterruptCallback(8, NULL); - //_sio_callback = NULL; - - } - - ExitCriticalSection(); - - return old_isr; - -} - -int kbhit() -{ - return(_sio_key_pending>0); -} diff --git a/libpsn00b/psxsio/tty.c b/libpsn00b/psxsio/tty.c new file mode 100644 index 0000000..4dc9fd1 --- /dev/null +++ b/libpsn00b/psxsio/tty.c @@ -0,0 +1,107 @@ +/* + * PSn00bSDK serial port BIOS TTY driver + * (C) 2019-2022 Lameguy64, spicyjpeg - MPL licensed + * + * This driver is designed to be as simple and reliable as possible: as such it + * only relies on the SIO_*() API for receiving and sends data synchronously. + * This allows printf() to work without issues, albeit slowly, if called from a + * critical section or even from an interrupt handler. + */ + +#include +#include +#include +#include + +/* TTY device control block */ + +static int _sio_open(FCB *fcb, const char* file, int mode) { + fcb->diskid = 1; + return 0; +} + +static int _sio_inout(FCB *fcb, int cmd) { + char *ptr = (char*) fcb->trns_addr; + + switch (cmd) { + case 1: // read + for (int i = 0; i < fcb->trns_len; i++) + *(ptr++) = (char) SIO_ReadByte(); + + return fcb->trns_len; + + case 2: // write + for (int i = 0; i < fcb->trns_len; i++) { + while (!(SIO_STAT & (SR_TXRDY | SR_TXU))) + __asm__ volatile(""); + + SIO_TXRX = *(ptr++); + } + + return fcb->trns_len; + + default: + return 0; + } +} + +static int _sio_close(FCB *fcb) { + return 0; +} + +static int _sio_ioctl(FCB *fcb, int cmd, int arg) { + switch (cmd) { + case FIOCSCAN: + return SIO_ReadSync(1) ? 0 : -1; + + default: + return -1; + } +} + +static int _sio_dummy(void) { + return -1; +} + +static DCB _sio_dcb = { + .name = "tty", + .flags = 3, + .ssize = 1, + .desc = "PSXSIO SERIAL CONSOLE", + .f_init = &_sio_dummy, + .f_open = &_sio_open, + .f_inout = &_sio_inout, + .f_close = &_sio_close, + .f_ioctl = &_sio_ioctl, + .f_read = &_sio_dummy, + .f_write = &_sio_dummy, + .f_erase = &_sio_dummy, + .f_undelete = &_sio_dummy, + .f_firstfile = &_sio_dummy, + .f_nextfile = &_sio_dummy, + .f_format = &_sio_dummy, + .f_chdir = &_sio_dummy, + .f_rename = &_sio_dummy, + .f_remove = &_sio_dummy, + .f_testdevice = &_sio_dummy +}; + +/* Public API */ + +void AddSIO(int baud) { + SIO_Init(baud, MR_SB_01 | MR_CHLEN_8); + + close(0); + close(1); + DelDev(_sio_dcb.name); + AddDev(&_sio_dcb); + open(_sio_dcb.name, 2); + open(_sio_dcb.name, 1); +} + +void DelSIO(void) { + SIO_Quit(); + + DelDev(_sio_dcb.name); + AddDummyTty(); +} -- cgit v1.2.3 From 5f25c0bf306d316c87fca9d3fe160d6661be230d Mon Sep 17 00:00:00 2001 From: spicyjpeg Date: Sun, 23 Oct 2022 15:03:16 +0200 Subject: Library bugfixes and additions, _sdk_log_inner() removal --- libpsn00b/include/assert.h | 7 ++- libpsn00b/include/psxsio.h | 14 +++-- libpsn00b/include/psxspu.h | 22 +++++-- libpsn00b/libc/_start.s | 17 +++++ libpsn00b/libc/_stubs.s | 27 -------- libpsn00b/libc/abort.c | 8 ++- libpsn00b/psxpress/vlc.s | 152 ++++++++++++++++++--------------------------- libpsn00b/psxsio/sio.c | 16 +++-- libpsn00b/psxspu/common.c | 9 ++- 9 files changed, 131 insertions(+), 141 deletions(-) create mode 100644 libpsn00b/libc/_start.s delete mode 100644 libpsn00b/libc/_stubs.s (limited to 'libpsn00b/libc') diff --git a/libpsn00b/include/assert.h b/libpsn00b/include/assert.h index e93c983..12212af 100644 --- a/libpsn00b/include/assert.h +++ b/libpsn00b/include/assert.h @@ -9,8 +9,9 @@ #ifndef __ASSERT_H #define __ASSERT_H +#include + void _assert_abort(const char *file, int line, const char *expr); -void _sdk_log_inner(const char *fmt, ...); #ifdef NDEBUG @@ -24,9 +25,9 @@ void _sdk_log_inner(const char *fmt, ...); } #ifdef SDK_LIBRARY_NAME -#define _sdk_log(fmt, ...) _sdk_log_inner(SDK_LIBRARY_NAME ": " fmt, ##__VA_ARGS__) +#define _sdk_log(fmt, ...) printf(SDK_LIBRARY_NAME ": " fmt, ##__VA_ARGS__) #else -#define _sdk_log(fmt, ...) _sdk_log_inner(fmt, ##__VA_ARGS__) +#define _sdk_log(fmt, ...) printf(fmt, ##__VA_ARGS__) #endif #endif diff --git a/libpsn00b/include/psxsio.h b/libpsn00b/include/psxsio.h index e0cc49b..d5f7d9a 100644 --- a/libpsn00b/include/psxsio.h +++ b/libpsn00b/include/psxsio.h @@ -145,15 +145,19 @@ int SIO_ReadSync(int mode); /** * @brief Registers a function to be called whenever a byte is received. The - * received byte is appended to the RX buffer and passed as an argument to the - * callback. The callback will run in the exception handler's context, so it - * should be as fast as possible and not use any function that relies on - * interrupts in order to work. + * received byte is passed as an argument to the callback, which shall then + * return a zero value to also store the byte in the RX buffer or a non-zero + * value to drop it. This can be used to e.g. filter or validate incoming data, + * or to bypass the library's RX buffer for custom buffering purposes. + * + * The callback will run in the exception handler's context, so it should be as + * fast as possible and shall not call any function that relies on interrupts + * being enabled. * * @param func * @return Previously set callback or NULL */ -void *SIO_ReadCallback(void (*func)(uint8_t)); +void *SIO_ReadCallback(int (*func)(uint8_t)); /** * @brief Sends the given byte, or appends it to the TX buffer if the serial diff --git a/libpsn00b/include/psxspu.h b/libpsn00b/include/psxspu.h index cf78e3d..7858e88 100644 --- a/libpsn00b/include/psxspu.h +++ b/libpsn00b/include/psxspu.h @@ -73,6 +73,20 @@ typedef struct _SpuCommonAttr { SpuExtAttr cd, ext; } SpuCommonAttr; +/* Macros */ + +#define getSPUAddr(addr) ((uint16_t) (((addr) + 7) / 8)) +#define getSPUSampleRate(rate) ((uint16_t) (((rate) * (1 << 12)) / 44100)) + +#define getSPUADSR(ar, dr, sr, rr, sl) ( \ + (sl) | \ + ((dr) << 4) | \ + ((ar) << 8) | \ + ((rr) << 16) | \ + ((sr) << 22) | \ + (1 << 30) \ +) + /* "Useless" macros for official SDK compatibility */ #define SpuSetCommonMasterVolume(left, right) \ @@ -87,18 +101,18 @@ typedef struct _SpuCommonAttr { ((enable) ? (SPU_CTRL |= 0x0002) : (SPU_CTRL &= 0xfffd)) #define SpuSetReverbAddr(addr) \ - (SPU_REVERB_ADDR = ((addr) + 7) / 8) + (SPU_REVERB_ADDR = getSPUAddr(addr)) #define SpuSetIRQAddr(addr) \ - (SPU_IRQ_ADDR = ((addr) + 7) / 8) + (SPU_IRQ_ADDR = getSPUAddr(addr)) #define SpuSetVoiceVolume(ch, left, right) \ (SPU_CH_VOL_L(ch) = (left), SPU_CH_VOL_R(ch) = (right)) #define SpuSetVoicePitch(ch, pitch) \ (SPU_CH_FREQ(ch) = (pitch)) #define SpuSetVoiceStartAddr(ch, addr) \ - (SPU_CH_ADDR(ch) = ((addr) + 7) / 8) + (SPU_CH_ADDR(ch) = getSPUAddr(addr)) #define SpuSetVoiceADSR(ch, ar, dr, sr, rr, sl) \ - (SPU_CH_ADSR(ch) = ((sl)) | ((dr) << 4) | ((ar) << 8) | ((rr) << 16) | ((sr) << 22) | (1 << 30)) + (SPU_CH_ADSR(ch) = getSPUADSR(ar, dr, sr, rr, sl)) #define SpuSetKey(enable, voice_bit) \ ((enable) ? (SPU_KEY_ON = (voice_bit)) : (SPU_KEY_OFF = (voice_bit))) diff --git a/libpsn00b/libc/_start.s b/libpsn00b/libc/_start.s new file mode 100644 index 0000000..fcd4c4c --- /dev/null +++ b/libpsn00b/libc/_start.s @@ -0,0 +1,17 @@ +# PSn00bSDK _start() trampoline +# (C) 2022 spicyjpeg - MPL licensed +# +# This file provides a weak function that can be easily overridden to e.g. set +# $sp or perform additional initialization before the "real" _start() function +# (_start_inner()) is called. + +.set noreorder + +.section .text._start +.global _start +.type _start, @function +.weak _start +_start: + la $gp, _gp + j _start_inner + nop diff --git a/libpsn00b/libc/_stubs.s b/libpsn00b/libc/_stubs.s deleted file mode 100644 index aa7bfbe..0000000 --- a/libpsn00b/libc/_stubs.s +++ /dev/null @@ -1,27 +0,0 @@ -# PSn00bSDK _start() trampoline and logging endpoint -# (C) 2022 spicyjpeg - MPL licensed -# -# This file provides a weak function that can be easily overridden to e.g. set -# $sp or perform additional initialization before the "real" _start() function -# (_start_inner()) is called. The _sdk_log_inner() function called by other -# libraries to log debug messages can also be overridden in a similar way. - -.set noreorder - -.section .text._start -.global _start -.type _start, @function -.weak _start -_start: - la $gp, _gp - j _start_inner - nop - -.section .text._sdk_log_inner -.global _sdk_log_inner -.type _sdk_log_inner, @function -.weak _sdk_log_inner -_sdk_log_inner: - li $t2, 0xa0 - jr $t2 - li $t1, 0x3f diff --git a/libpsn00b/libc/abort.c b/libpsn00b/libc/abort.c index 9a4661a..0a3c325 100644 --- a/libpsn00b/libc/abort.c +++ b/libpsn00b/libc/abort.c @@ -3,13 +3,15 @@ * (C) 2022 spicyjpeg - MPL licensed */ +#undef SDK_LIBRARY_NAME + #include #include /* Internal function used by assert() macro */ void _assert_abort(const char *file, int line, const char *expr) { - _sdk_log_inner("%s:%d: assert(%s)\n", file, line, expr); + _sdk_log("%s:%d: assert(%s)\n", file, line, expr); for (;;) __asm__ volatile(""); @@ -18,7 +20,7 @@ void _assert_abort(const char *file, int line, const char *expr) { /* Standard abort */ void abort(void) { - _sdk_log_inner("abort()\n"); + _sdk_log("abort()\n"); for (;;) __asm__ volatile(""); @@ -27,7 +29,7 @@ void abort(void) { /* Pure virtual function call (C++) */ void __cxa_pure_virtual(void) { - _sdk_log_inner("__cxa_pure_virtual()\n"); + _sdk_log("__cxa_pure_virtual()\n"); for (;;) __asm__ volatile(""); diff --git a/libpsn00b/psxpress/vlc.s b/libpsn00b/psxpress/vlc.s index 885a3f7..75e33d3 100644 --- a/libpsn00b/psxpress/vlc.s +++ b/libpsn00b/psxpress/vlc.s @@ -29,17 +29,17 @@ .set VLC_Context_block_index, 20 .set VLC_Context_coeff_index, 21 -.set DECDCTSMALLTAB_lut0, 0 -.set DECDCTSMALLTAB_lut2, 4 -.set DECDCTSMALLTAB_lut3, 36 -.set DECDCTSMALLTAB_lut4, 292 -.set DECDCTSMALLTAB_lut5, 308 -.set DECDCTSMALLTAB_lut7, 324 -.set DECDCTSMALLTAB_lut8, 356 -.set DECDCTSMALLTAB_lut9, 420 -.set DECDCTSMALLTAB_lut10, 484 -.set DECDCTSMALLTAB_lut11, 548 -.set DECDCTSMALLTAB_lut12, 612 +.set DECDCTTAB_lut0, 0 +.set DECDCTTAB_lut2, 4 +.set DECDCTTAB_lut3, 36 +.set DECDCTTAB_lut4, 292 +.set DECDCTTAB_lut5, 308 +.set DECDCTTAB_lut7, 324 +.set DECDCTTAB_lut8, 356 +.set DECDCTTAB_lut9, 420 +.set DECDCTTAB_lut10, 484 +.set DECDCTTAB_lut11, 548 +.set DECDCTTAB_lut12, 612 .section .text.DecDCTvlcStart .global DecDCTvlcStart @@ -115,7 +115,7 @@ _vlc_skip_context_load: # Obtain the addresses of the lookup table and jump area in advance so that # they don't have to be retrieved for each coefficient decoded. lw $t8, _vlc_huffman_table - la $t9, .Lac_jump_area + la $t9, .Lac_prefix_10 beqz $a2, .Lstop_processing addiu $a1, 4 # output = (uint16_t *) &output[1] @@ -123,32 +123,32 @@ _vlc_skip_context_load: .Lprocess_next_code_loop: # while (max_size) # This is the "hot" part of the decoder, executed for each code in the # bitstream. The first step is to determine if the next code is a DC or AC - # coefficient. The GTE is also given the task of counting the number of - # leading zeroes/ones, which takes 2 more cycles. + # coefficient. bnez $t7, .Lprocess_ac_coefficient - mtc2 $t0, $30 + addiu $t7, 1 # coeff_index++ bnez $t4, .Lprocess_dc_v3_coefficient - #nop + li $v1, 0x01ff .Lprocess_dc_v2_coefficient: # if (!coeff_index && !is_v3) # The DC coefficient in version 2 frames is not compressed. Value 0x1ff is # used to signal the end of the bitstream. srl $v0, $t0, 22 # prefix = (window >> (32 - 10)) - li $v1, 0x01ff beq $v0, $v1, .Lstop_processing # if (prefix == 0x1ff) break or $v0, $t3 # *output = prefix | quant_scale sll $t0, 10 # window <<= 10 - addiu $t5, -10 # bit_offset -= 10 b .Lwrite_value - addiu $t7, 1 # coeff_index++ + addiu $t5, -10 # bit_offset -= 10 .Lprocess_dc_v3_coefficient: # if (!coeff_index && is_v3) # TODO: version 3 is currently not supported. jr $ra li $v0, -1 - #b .Lwrite_value .Lprocess_ac_coefficient: # if (coeff_index) + # Start counting the number of leading zeroes/ones using the GTE. This + # takes 2 more cycles. + mtc2 $t0, $30 + # Check whether the prefix code is one of the shorter, more common ones. srl $v0, $t0, 30 li $v1, 3 @@ -157,33 +157,34 @@ _vlc_skip_context_load: beq $v0, $v1, .Lac_prefix_10 li $v1, 1 beq $v0, $v1, .Lac_prefix_01 - #srl $v0, $t0, 29 - #beq $v0, $v1, .Lac_prefix_001 - #nop + nop # If the code is longer, retrieve the number of leading zeroes from the GTE # and use it as an index into the jump area. Each block in the area is 8 # instructions long and handles decoding a specific prefix. mfc2 $v0, $31 - nop - andi $v0, 15 # jump_addr = &ac_jump_area[(prefix % 16) * 8 * sizeof(u32)] - sll $v0, 5 + li $v1, 11 + bgt $v0, $v1, .Lreturn_error # if (prefix > 11) return -1 + sll $v0, 5 # jump_addr = &ac_jump_area[prefix * 8 * sizeof(u32)] addu $v0, $t9 jr $v0 nop +.Lreturn_error: + jr $ra + li $v0, -1 + .Lac_prefix_11: # Prefix 11 is followed by a single bit. srl $v0, $t0, 28 # index = ((window >> (32 - 2 - 1)) & 1) * sizeof(u16) andi $v0, 2 addu $v0, $t8 # value = table->lut0[index] - lhu $v0, DECDCTSMALLTAB_lut0($v0) + lhu $v0, DECDCTTAB_lut0($v0) sll $t0, 3 # window <<= 3 - addiu $t5, -3 # bit_offset -= 3 b .Lwrite_value - addiu $t7, 1 # coeff_index++ + addiu $t5, -3 # bit_offset -= 3 + #.word 0 -.Lac_jump_area: .Lac_prefix_10: # Prefix 10 marks the end of a block. li $v0, 0xfe00 # value = 0xfe00 @@ -202,11 +203,10 @@ _vlc_skip_context_load: srl $v0, $t0, 25 # index = ((window >> (32 - 2 - 3)) & 7) * sizeof(u32) andi $v0, 28 addu $v0, $t8 # value = table->lut2[index] - lw $v0, DECDCTSMALLTAB_lut2($v0) - addiu $t7, 1 # coeff_index++ + lw $v0, DECDCTTAB_lut2($v0) b .Lupdate_window_and_write srl $v1, $v0, 16 # length = value >> 16 - .word 0 + .word 0, 0 .Lac_prefix_001: # Prefix 001 can be followed by a 6-bit lookup index starting with 00, or a @@ -214,136 +214,106 @@ _vlc_skip_context_load: srl $v0, $t0, 21 # index = ((window >> (32 - 3 - 6)) & 63) * sizeof(u32) andi $v0, 252 addu $v0, $t8 # value = table->lut3[index] - lw $v0, DECDCTSMALLTAB_lut3($v0) - addiu $t7, 1 # coeff_index++ + lw $v0, DECDCTTAB_lut3($v0) b .Lupdate_window_and_write srl $v1, $v0, 16 # length = value >> 16 - .word 0 + .word 0, 0 .Lac_prefix_0001: # Prefix 0001 is followed by a 3-bit lookup index. srl $v0, $t0, 24 # index = ((window >> (32 - 4 - 3)) & 7) * sizeof(u16) andi $v0, 14 addu $v0, $t8 # value = table->lut4[index] - lhu $v0, DECDCTSMALLTAB_lut4($v0) + lhu $v0, DECDCTTAB_lut4($v0) sll $t0, 7 # window <<= 4 + 3 - addiu $t5, -7 # bit_offset -= 4 + 3 b .Lwrite_value - addiu $t7, 1 # coeff_index++ + addiu $t5, -7 # bit_offset -= 4 + 3 + .word 0 .Lac_prefix_00001: # Prefix 00001 is followed by a 3-bit lookup index. srl $v0, $t0, 23 # index = ((window >> (32 - 5 - 3)) & 7) * sizeof(u16) andi $v0, 14 addu $v0, $t8 # value = table->lut5[index] - lhu $v0, DECDCTSMALLTAB_lut5($v0) + lhu $v0, DECDCTTAB_lut5($v0) sll $t0, 8 # window <<= 5 + 3 - addiu $t5, -8 # bit_offset -= 5 + 3 b .Lwrite_value - addiu $t7, 1 # coeff_index++ + addiu $t5, -8 # bit_offset -= 5 + 3 + .word 0 .Lac_prefix_000001: # Prefix 000001 is an escape code followed by a full 16-bit MDEC value. srl $v0, $t0, 10 # value = window >> (32 - 6 - 16) sll $t0, 22 # window <<= 6 + 16 - addiu $t5, -22 # bit_offset -= 6 + 16 b .Lwrite_value - addiu $t7, 1 # coeff_index++ - .word 0, 0, 0 + addiu $t5, -22 # bit_offset -= 6 + 16 + .word 0, 0, 0, 0 .Lac_prefix_0000001: # Prefix 0000001 is followed by a 4-bit lookup index. srl $v0, $t0, 20 # index = ((window >> (32 - 7 - 4)) & 15) * sizeof(u16) andi $v0, 30 addu $v0, $t8 # value = table->lut7[index] - lhu $v0, DECDCTSMALLTAB_lut7($v0) + lhu $v0, DECDCTTAB_lut7($v0) sll $t0, 11 # window <<= 7 + 4 - addiu $t5, -11 # bit_offset -= 7 + 4 b .Lwrite_value - addiu $t7, 1 # coeff_index++ + addiu $t5, -11 # bit_offset -= 7 + 4 + .word 0 .Lac_prefix_00000001: # Prefix 00000001 is followed by a 5-bit lookup index. srl $v0, $t0, 18 # index = ((window >> (32 - 8 - 5)) & 31) * sizeof(u16) andi $v0, 62 addu $v0, $t8 # value = table->lut8[index] - lhu $v0, DECDCTSMALLTAB_lut8($v0) + lhu $v0, DECDCTTAB_lut8($v0) sll $t0, 13 # window <<= 8 + 5 - addiu $t5, -13 # bit_offset -= 8 + 5 b .Lwrite_value - addiu $t7, 1 # coeff_index++ + addiu $t5, -13 # bit_offset -= 8 + 5 + .word 0 .Lac_prefix_000000001: # Prefix 000000001 is followed by a 5-bit lookup index. srl $v0, $t0, 17 # index = ((window >> (32 - 9 - 5)) & 31) * sizeof(u16) andi $v0, 62 addu $v0, $t8 # value = table->lut9[index] - lhu $v0, DECDCTSMALLTAB_lut9($v0) + lhu $v0, DECDCTTAB_lut9($v0) sll $t0, 14 # window <<= 9 + 5 - addiu $t5, -14 # bit_offset -= 9 + 5 b .Lwrite_value - addiu $t7, 1 # coeff_index++ + addiu $t5, -14 # bit_offset -= 9 + 5 + .word 0 .Lac_prefix_0000000001: # Prefix 0000000001 is followed by a 5-bit lookup index. srl $v0, $t0, 16 # index = ((window >> (32 - 10 - 5)) & 31) * sizeof(u16) andi $v0, 62 addu $v0, $t8 # value = table->lut10[index] - lhu $v0, DECDCTSMALLTAB_lut10($v0) + lhu $v0, DECDCTTAB_lut10($v0) sll $t0, 15 # window <<= 10 + 5 - addiu $t5, -15 # bit_offset -= 10 + 5 b .Lwrite_value - addiu $t7, 1 # coeff_index++ + addiu $t5, -15 # bit_offset -= 10 + 5 + .word 0 .Lac_prefix_00000000001: # Prefix 00000000001 is followed by a 5-bit lookup index. srl $v0, $t0, 15 # index = ((window >> (32 - 11 - 5)) & 31) * sizeof(u16) andi $v0, 62 addu $v0, $t8 # value = table->lut11[index] - lhu $v0, DECDCTSMALLTAB_lut11($v0) + lhu $v0, DECDCTTAB_lut11($v0) sll $t0, 16 # window <<= 11 + 5 - addiu $t5, -16 # bit_offset -= 11 + 5 b .Lwrite_value - addiu $t7, 1 # coeff_index++ + addiu $t5, -16 # bit_offset -= 11 + 5 + .word 0 .Lac_prefix_000000000001: # Prefix 000000000001 is followed by a 5-bit lookup index. srl $v0, $t0, 14 # index = ((window >> (32 - 12 - 5)) & 31) * sizeof(u16) andi $v0, 62 addu $v0, $t8 # value = table->lut12[index] - lhu $v0, DECDCTSMALLTAB_lut12($v0) + lhu $v0, DECDCTTAB_lut12($v0) sll $t0, 17 # window <<= 12 + 5 - addiu $t5, -17 # bit_offset -= 12 + 5 b .Lwrite_value - addiu $t7, 1 # coeff_index++ - - # Prefix 0000000000001 is not valid. - beqz $t0, .Lstop_processing - nop - jr $ra - li $v0, -1 - .word 0, 0, 0, 0 - - # Prefix 00000000000001 is not valid. - beqz $t0, .Lstop_processing - nop - jr $ra - li $v0, -1 - .word 0, 0, 0, 0 - - # Prefix 000000000000001 is not valid. - beqz $t0, .Lstop_processing - nop - jr $ra - li $v0, -1 - .word 0, 0, 0, 0 - - # Prefix 0000000000000001 is not valid. - beqz $t0, .Lstop_processing - nop - jr $ra - li $v0, -1 - #.word 0, 0, 0, 0 + addiu $t5, -17 # bit_offset -= 12 + 5 + .word 0 .Lupdate_window_and_write: sllv $t0, $t0, $v1 # window <<= length diff --git a/libpsn00b/psxsio/sio.c b/libpsn00b/psxsio/sio.c index 3fb3caf..6b80352 100644 --- a/libpsn00b/psxsio/sio.c +++ b/libpsn00b/psxsio/sio.c @@ -27,7 +27,7 @@ typedef struct { static SIO_FlowControl _flow_control; static uint16_t _ctrl_reg_flag; -static void (*_read_callback)(uint8_t) = (void *) 0; +static int (*_read_callback)(uint8_t) = (void *) 0; static void (*_old_sio_handler)(void) = (void *) 0; static volatile RingBuffer _tx_buffer, _rx_buffer; @@ -41,7 +41,15 @@ static void _sio_handler(void) { // Handle any incoming bytes. while (SIO_STAT & SR_RXRDY) { uint8_t value = SIO_TXRX; - int length = _rx_buffer.length; + + // Skip storing this byte into the RX buffer if the callback returns a + // non-zero value. + if (_read_callback) { + if (_read_callback(value)) + continue; + } + + int length = _rx_buffer.length; if (length >= BUFFER_LENGTH) { //_sdk_log("RX overrun, dropping bytes\n"); @@ -53,8 +61,6 @@ static void _sio_handler(void) { _rx_buffer.length = length + 1; _rx_buffer.data[tail] = value; - if (_read_callback) - _read_callback(value); } // Send the next byte in the buffer if the TX unit is ready. Note that @@ -185,7 +191,7 @@ int SIO_ReadSync(int mode) { return 0; } -void *SIO_ReadCallback(void (*func)(uint8_t)) { +void *SIO_ReadCallback(int (*func)(uint8_t)) { EnterCriticalSection(); void *old_callback = _read_callback; diff --git a/libpsn00b/psxspu/common.c b/libpsn00b/psxspu/common.c index 42f7cb7..1613ca9 100644 --- a/libpsn00b/psxspu/common.c +++ b/libpsn00b/psxspu/common.c @@ -38,13 +38,16 @@ static void _dma_transfer(uint32_t *data, size_t length, int write) { length += DMA_CHUNK_LENGTH - 1; } - SPU_CTRL &= 0xffcf; // Disable DMA request + SPU_DMA_CTRL = 0x0004; // Reset transfer mode + SPU_CTRL &= 0xffcf; // Disable DMA request _wait_status(0x0030, 0x0000); // Enable DMA request for writing (2) or reading (3) + uint16_t ctrl = write ? 0x0020 : 0x0030; + SPU_ADDR = _transfer_addr; - SPU_CTRL |= write ? 0x0020 : 0x0030; - _wait_status(0x0400, 0x0000); + SPU_CTRL |= ctrl; + _wait_status(0x0430, ctrl); DMA_MADR(4) = (uint32_t) data; if (length < DMA_CHUNK_LENGTH) -- cgit v1.2.3 From 68daf6d338aba6e32e687d4151eaddc8735227b3 Mon Sep 17 00:00:00 2001 From: spicyjpeg Date: Sun, 30 Oct 2022 08:28:44 +0100 Subject: Refactor dynamic linker, misc. cleanups --- examples/sound/spustream/main.c | 4 +- examples/system/dynlink/main.c | 44 +++-- libpsn00b/include/dlfcn.h | 237 +++++++++++++----------- libpsn00b/include/stdlib.h | 12 +- libpsn00b/libc/memset.s | 6 +- libpsn00b/psxetc/dl.c | 399 +++++++++++++--------------------------- 6 files changed, 287 insertions(+), 415 deletions(-) (limited to 'libpsn00b/libc') diff --git a/examples/sound/spustream/main.c b/examples/sound/spustream/main.c index 68cf9b0..d240433 100644 --- a/examples/sound/spustream/main.c +++ b/examples/sound/spustream/main.c @@ -19,8 +19,8 @@ * As the loop point doesn't necessarily have to be within the chunk itself, it * can be used to "queue" another chunk to be played immediately after the * current one. This allows for double buffering: two chunks are always kept in - * SPU RAM and one is overwritten with while the other is playing. Chunks are - * laid out in SPU RAM as follows: + * SPU RAM and one is overwritten with a new chunk while the other is playing. + * Chunks are laid out in SPU RAM as follows: * * ________________________________________________ * / __________________ \ diff --git a/examples/system/dynlink/main.c b/examples/system/dynlink/main.c index fcce5b1..d813c07 100644 --- a/examples/system/dynlink/main.c +++ b/examples/system/dynlink/main.c @@ -7,8 +7,8 @@ * symbol map file, which is generated at compile time by GCC's nm command and * included into the CD image. The symbol map lists all functions/variables in * the executable and their type, address and size. Currently only searching - * for a symbol's address by its name (DL_GetSymbolByName()) is supported, - * however this may be expanded in the future. + * for a symbol's address by its name (DL_GetMapSymbol()) is supported, however + * this may be expanded in the future. * * Being able to introspect local symbols at runtime, in turn, allows us to use * another set of APIs to load, link and execute code from an external file @@ -140,18 +140,18 @@ void display(RenderContext *ctx) { /* Symbol overriding example */ -static volatile uint32_t resolve_counter = 0; +static volatile int resolve_counter = 0; // This function will override printf(), i.e. DLLs will use this instead of the // "real" printf() present in the executable, thanks to the custom resolver -// defined below. We'll use this to redirect the DLL's output to the debug text -// window. +// defined below. We'll use this to redirect the DLL's output to be shown on +// screen. int dll_printf(const char *format, ...) { va_list args; va_start(args, format); - char buffer[256]; - int32_t return_value = vsprintf(buffer, format, args); + char buffer[256]; + int return_value = vsprintf(buffer, format, args); va_end(args); FntPrint(-1, "DLL: %s", buffer); @@ -163,7 +163,7 @@ int dll_printf(const char *format, ...) { // This function will be called by the linker for each undefined symbol // (function or variable) in the DLL, and should return the address of the // symbol so the dynamic linker can patch it in. The default resolver tries to -// find them in the currently loaded symbol map using DL_GetSymbolByName(). +// find them in the currently loaded symbol map using DL_GetMapSymbol(). void *custom_resolver(DLL *dll, const char *name) { if (!strcmp(name, "printf")) { printf("Resolving printf() -> dll_printf() (#%d)\n", resolve_counter++); @@ -173,7 +173,7 @@ void *custom_resolver(DLL *dll, const char *name) { printf("Resolving %s() (#%d)\n", name, resolve_counter++); // Custom resolvers should always fall back to the default behavior. - return DL_GetSymbolByName(name); + return DL_GetMapSymbol(name); } /* Global variables and structs */ @@ -187,7 +187,7 @@ typedef struct { void (*render)(RenderContext *, uint16_t buttons); } DLL_API; -static DLL *dll = 0; +static DLL dll; static DLL_API dll_api; static RenderContext ctx; @@ -225,20 +225,18 @@ size_t load_file(const char *filename, void **ptr) { } void load_dll(const char *filename) { - // As we're passing RTLD_FREE_ON_DESTROY to DL_CreateDLL(), calling + // As we're passing DL_FREE_ON_DESTROY to DL_CreateDLL(), calling // DL_DestroyDLL() will also deallocate the buffer the DLL was loaded into. - if (dll) - DL_DestroyDLL(dll); + DL_DestroyDLL(&dll); void *ptr; size_t len = load_file(filename, &ptr); - dll = DL_CreateDLL(ptr, len, RTLD_LAZY | RTLD_FREE_ON_DESTROY); - if (!dll) - SHOW_ERROR("FAILED TO PARSE %s\nERROR=%d\n", filename, (int32_t) DL_GetLastError()); + if (!DL_CreateDLL(&dll, ptr, len, DL_LAZY | DL_FREE_ON_DESTROY)) + SHOW_ERROR("FAILED TO PARSE %s\n", filename); - dll_api.init = DL_GetDLLSymbol(dll, "init"); - dll_api.render = DL_GetDLLSymbol(dll, "render"); + dll_api.init = DL_GetDLLSymbol(&dll, "init"); + dll_api.render = DL_GetDLLSymbol(&dll, "render"); printf("DLL init() @ %08x, render() @ %08x\n", dll_api.init, dll_api.render); @@ -266,14 +264,14 @@ int main(int argc, const char* argv[]) { size_t len = load_file("\\MAIN.MAP;1", &ptr); if (!DL_ParseSymbolMap(ptr, len)) - SHOW_ERROR("FAILED TO PARSE SYMBOL MAP\nERROR=%d\n", (int32_t) DL_GetLastError()); + SHOW_ERROR("FAILED TO PARSE SYMBOL MAP\n"); free(ptr); // Try to obtain a reference to a local function. - void (*_display)() = DL_GetSymbolByName("display"); + void (*_display)() = DL_GetMapSymbol("display"); if (!_display) - SHOW_ERROR("FAILED TO LOOK UP LOCAL FUNCTION\nERROR=%d\n", (int32_t) DL_GetLastError()); + SHOW_ERROR("FAILED TO LOOK UP LOCAL FUNCTION\n"); printf("Symbol map test, display() @ %08x\n", _display); @@ -295,7 +293,7 @@ int main(int argc, const char* argv[]) { DL_PRE_CALL(dll_api.render); dll_api.render(&ctx, last_buttons); - FntPrint(-1, "MAIN: DLL ADDR=%08x SIZE=%d\n", dll->ptr, dll->size); + FntPrint(-1, "MAIN: DLL ADDR=%08x SIZE=%d\n", dll.ptr, dll.size); FntPrint(-1, "MAIN: %d FUNCTIONS RESOLVED\n", resolve_counter); FntPrint(-1, "[START] LOAD NEXT DLL\n"); FntFlush(-1); @@ -320,7 +318,7 @@ int main(int argc, const char* argv[]) { last_buttons = pad->btn; } - //DL_DestroyDLL(dll); + //DL_DestroyDLL(&dll); //DL_UnloadSymbolMap(); return 0; } diff --git a/libpsn00b/include/dlfcn.h b/libpsn00b/include/dlfcn.h index 5848a95..3c5260d 100644 --- a/libpsn00b/include/dlfcn.h +++ b/libpsn00b/include/dlfcn.h @@ -7,38 +7,31 @@ #define __DLFCN_H #include +#include #include -/* Helper macro for setting $t9 before calling a function */ +/* Macros */ -#define DL_PRE_CALL(func) { \ - __asm__ volatile("move $t9, %0;" :: "r"(func) : "$t9"); \ -} +/** + * @brief Prepares for a DLL function call. + * + * @details Sets the $t9 register to the specified value (which should be a + * pointer to a DLL function obtained using DL_GetDLLSymbol()). This must be + * done prior to calling a DLL function from the main executable to ensure the + * DLL can correctly invoke the symbol resolver if necessary. + * + * This macro is not required when calling a DLL function from another DLL, as + * GCC will generate code to set $t9 appropriately. + */ +#define DL_PRE_CALL(func) \ + __asm__ volatile("move $t9, %0;" :: "r"(func) : "$t9"); -/* Types */ - -#define RTLD_DEFAULT ((DLL *) 0) - -typedef enum _DL_Error { - RTLD_E_NONE = 0, // No error - RTLD_E_FILE_OPEN = 1, // Unable to find or open file - RTLD_E_FILE_ALLOC = 2, // Unable to allocate buffer to load file into - RTLD_E_FILE_READ = 3, // Failed to read file - RTLD_E_NO_MAP = 4, // No symbol map has been loaded yet - RTLD_E_MAP_ALLOC = 5, // Unable to allocate symbol map structures - RTLD_E_NO_SYMBOLS = 6, // No symbols found in symbol map - RTLD_E_DLL_NULL = 7, // Unable to initialize DLL from null pointer - RTLD_E_DLL_ALLOC = 8, // Unable to allocate DLL metadata structures - RTLD_E_DLL_FORMAT = 9, // Unsupported DLL type or format - RTLD_E_MAP_SYMBOL = 10, // Symbol not found in symbol map - RTLD_E_DLL_SYMBOL = 11, // Symbol not found in DLL - RTLD_E_HASH_LOOKUP = 12 // Hash table lookup failed due to internal error -} DL_Error; +/* Structure and enum definitions */ typedef enum _DL_ResolveMode { - RTLD_LAZY = 1, // Resolve functions when they are first called (default) - RTLD_NOW = 2, // Resolve all symbols immediately on load - RTLD_FREE_ON_DESTROY = 4 // Automatically free DLL buffer when closing DLL + DL_LAZY = 1, // Resolve functions when they are first called (default) + DL_NOW = 2, // Resolve all symbols immediately on load + DL_FREE_ON_DESTROY = 4 // Automatically free DLL buffer when closing DLL } DL_ResolveMode; // Members of this struct should not be accessed directly in most cases, but @@ -55,151 +48,171 @@ typedef struct _DLL { uint16_t got_length; } DLL; -/* API */ +/* Public API */ #ifdef __cplusplus extern "C" { #endif /** - * @brief Reads the symbol table from the provided string buffer (which may or - * may not be null-terminated), parses it and stores the parsed entries into a - * private hash table; the buffer won't be further referenced and can be safely - * deallocated after parsing. Returns the number of entries successfully parsed - * or -1 if an error occurred. + * @brief Creates an empty symbol map in memory. * - * This function expects the string buffer to contain one more lines, each of - * which must follow this format: + * @details Initializes the internal symbol hash table to contain at most the + * given number of symbols. Once this function is called, symbols can be + * registered using DL_AddMapSymbol() and then looked up using + * DL_GetMapSymbol(). The default DLL resolver will search the hash table for + * external symbols required by DLLs. * - * [DEBUG_INFO...] + * This function is normally not required when loading a map file through + * DL_ParseSymbolMap(), but it can be used alongside DL_AddMapSymbol() to + * implement a custom symbol map parser. * - * The "nm" tool included in the GCC toolchain can be used to generate a map - * file in the appropriate format after building the executable, by using this - * command: + * @param num_entries + * @return 0 or -1 in case of error * - * mipsel-none-elf-nm -f posix -l -n executable.elf >executable.map + * @see DL_AddMapSymbol(), DL_GetMapSymbol() + */ +int DL_InitSymbolMap(int num_entries); + +/** + * @brief Destroys the currently loaded symbol map. * - * @param ptr - * @param size - * @return -1 or number of entries parsed + * @details Frees the internal hash table allocated by DL_InitSymbolMap() or + * DL_ParseSymbolMap(), containing the currently loaded symbol map. Freeing the + * table manually before loading a new symbol map is normally unnecessary as it + * is done automatically, however this function can be useful to recover heap + * space once the map is no longer needed. */ -int32_t DL_ParseSymbolMap(const char *ptr, size_t size); +void DL_UnloadSymbolMap(void); /** - * @brief File wrapper around DL_ParseSymbolMap(). Allocates a temporary buffer - * then loads the specified map file into it (using BIOS APIs) and calls - * DL_ParseSymbolMap() to parse it. The buffer is deallocated immediately after - * parsing. + * @brief Adds a symbol to the currently loaded symbol map. * - * @param filename Must always contain device name, e.g. "cdrom:MODULE.DLL;1" - * @return -1 or number of entries parsed + * @details Registers a new symbol (function or variable) with the given name + * and address, and adds it to the internal hash table. The symbol can then be + * looked up using DL_GetMapSymbol(). The default DLL resolver will search the + * hash table for external symbols required by DLLs. + * + * This function shall only be called after DL_InitSymbolMap() or + * DL_ParseSymbolMap() is called. + * + * @param name + * @param ptr + * + * @see DL_GetMapSymbol() */ -//int32_t DL_LoadSymbolMapFromFile(const char *filename); +void DL_AddMapSymbol(const char *name, void *ptr); /** - * @brief Frees internal buffers containing the currently loaded symbol map. - * This is automatically done before loading a new symbol map so there is no - * need to call this function in most cases, however it can still be useful to - * free up space on the heap once the symbol map is no longer needed. + * @brief Creates a symbol map in memory from a map file in text format. + * + * @details Initializes the internal symbol hash table, then parses entries + * from the provided string buffer (which may or may not be null-terminated) + * and adds each one to the table. The string buffer won't be further + * referenced and can be safely deallocated after parsing. Returns the number + * of entries successfully parsed. + * + * The string buffer shall contain one or more lines, each of which must follow + * this format: + * + * [...] + * + * The "nm" tool included in the GCC toolchain can be used to generate a map + * file in the appropriate format after building the executable: + * + * mipsel-none-elf-nm -f posix -l -n executable.elf >executable.map + * + * @param ptr + * @param size + * @return Number of entries parsed, -1 in case of failure + * + * @see DL_UnloadSymbolMap(), DL_GetMapSymbol() */ -void DL_UnloadSymbolMap(void); +int DL_ParseSymbolMap(const char *ptr, size_t size); /** - * @brief Queries the currently loaded symbol map for the symbol with the given - * name and returns a pointer to it, which can then be used to directly access - * the symbol. If the symbol can't be found, null is returned instead. + * @brief Gets a pointer to a symbol in the currently loaded map by its name. + * + * @details Queries the currently loaded symbol map for the symbol with the + * given name and returns a pointer to it, which can then be used to directly + * access the symbol. If the symbol can't be found, a null pointer is returned. * * @param name - * @return NULL or pointer to symbol (any type) + * @return NULL or pointer to symbol */ -void *DL_GetSymbolByName(const char *name); +void *DL_GetMapSymbol(const char *name); /** - * @brief Sets a custom function to be called for resolving symbols in DLLs. + * @brief Sets a custom handler for resolving symbols in DLLs. + * + * @details Sets a custom function to be called for resolving symbols in DLLs. * The function will be given a pointer to the current DLL and the unresolved * symbol's name, and should return the address of the symbol in the executable * (the dynamic linker will lock up if it returns null). Passing null instead - * of a function resets the default behavior of calling DL_GetSymbolByName() to + * of a function resets the default behavior of calling DL_GetMapSymbol() to * find the symbol in the currently loaded symbol map. - * + * * @param callback NULL or pointer to callback function + * @return Previously set callback or NULL */ -void DL_SetResolveCallback(void *(*callback)(DLL *, const char *)); +void *DL_SetResolveCallback(void *(*callback)(DLL *, const char *)); /** - * @brief Initializes a buffer holding the contents of a dynamically-loaded + * @brief Initializes a DLL structure. + * + * @details Initializes a buffer holding the contents of a dynamically-loaded * library file (compiled with the dll.ld linker script and converted to a raw - * binary) *in-place*. A new DLL struct is allocated to store metadata but, + * binary) *in-place*. Metadata is written to the provided DLL struct but, * unlike DL_ParseSymbolMap(), the DLL's actual code, data and tables are * referenced directly from the provided buffer. The buffer must not be moved * or deallocated, at least not before calling DL_DestroyDLL() on the DLL * struct returned by this function. * * The third argument specifies when symbols in the DLL should be resolved. - * Setting it to RTLD_LAZY defers resolution of undefined functions to when - * they are first called, while RTLD_NOW forces all symbols to be resolved - * immediately. If a custom resolver has been set via DL_SetResolveCallback(), - * it will be called for each symbol to resolve. + * Setting it to DL_LAZY defers resolution of undefined functions to when they + * are first called, while DL_NOW forces all symbols to be resolved + * immediately. Either mode can be OR'd with DL_FREE_ON_DESTROY to + * automatically deallocate the provided buffer when DL_DestroyDLL() is called. * + * If a custom resolver has been set via DL_SetResolveCallback(), it will be + * called for each symbol to resolve. + * + * @param dll * @param ptr * @param size - * @param mode RTLD_LAZY or RTLD_NOW - * @return NULL or pointer to a new DLL struct - */ -DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode); - -/** - * @brief File wrapper around dlinit(). Allocates a new buffer, loads the - * specified file into it (using BIOS APIs) and calls dlinit() on that. When - * calling dlclose() on a DLL loaded from a file, the file buffer is - * automatically destroyed. - * - * @param filename Must always contain device name, e.g. "cdrom:MODULE.DLL;1" - * @param mode RTLD_LAZY or RTLD_NOW + optionally RTLD_FREE_ON_DESTROY - * @return NULL or pointer to a new DLL struct + * @param mode DL_LAZY or DL_NOW, optionally with DL_FREE_ON_DESTROY + * @return Pointer to DLL structure or NULL in case of failure + * + * @see DL_DestroyDLL(), DL_GetDLLSymbol() */ -//DLL *DL_LoadDLLFromFile(const char *filename, DL_ResolveMode mode); +DLL *DL_CreateDLL(DLL *dll, void *ptr, size_t size, DL_ResolveMode mode); /** - * @brief Destroys a loaded DLL by calling its global destructors and freeing - * the buffer it's loaded in. Any pointer passed to DL_DestroyDLL() should no - * longer be used after the call. If the DLL was initialized in-place using - * DL_CreateDLL(), DL_DestroyDLL() will only free the buffer initially passed - * to DL_CreateDLL() if RTLD_FREE_ON_DESTROY was used. + * @brief Destroys a DLL structure. + * + * @details Destroys a loaded DLL by calling its global destructors. If the DLL + * was initialized with the DL_FREE_ON_DESTROY flag, the buffer associated with + * the DLL is also deallocated. Note that the DLL structure itself is *not* + * deallocated. * * @param dll */ void DL_DestroyDLL(DLL *dll); /** - * @brief Returns a pointer to the DLL symbol with the given name, or null if - * it can't be found. If null or RTLD_DEFAULT is passed as first argument, the - * executable itself is searched instead using the symbol map (behaving the - * same as DL_GetSymbolByName()). + * @brief Gets a pointer to a symbol in a DLL by its name. + * + * @details Returns a pointer to the DLL symbol with the given name, or null if + * it can't be found. If a null pointer is passed as first argument, the + * executable itself is searched instead using the symbol map (behaving + * identically to DL_GetMapSymbol()). * - * @param dll DLL struct or RTLD_DEFAULT + * @param dll Pointer to DLL structure or NULL * @param name * @return NULL or pointer to symbol (any type) */ void *DL_GetDLLSymbol(const DLL *dll, const char *name); -/** - * @brief Returns a code describing the last error that occurred, or DL_E_NONE - * if no error has occurred since the last call to dlerror() (i.e. calling this - * also resets the internal error flags). - * - * @return NULL or member of DL_Error enum - */ -DL_Error DL_GetLastError(void); - -/* POSIX "compatibility" macros */ - -#define dlinit(ptr, size, mode) DL_CreateDLL(ptr, size, mode) -//#define dlopen(filename, mode) DL_LoadDLLFromFile(filename, mode) -#define dlsym(dll, name) DL_GetDLLSymbol(dll, name) -#define dlclose(dll) DL_DestroyDLL(dll) -#define dlerror() DL_GetLastError() - #ifdef __cplusplus } #endif diff --git a/libpsn00b/include/stdlib.h b/libpsn00b/include/stdlib.h index f0753c1..049d067 100644 --- a/libpsn00b/include/stdlib.h +++ b/libpsn00b/include/stdlib.h @@ -31,17 +31,19 @@ extern "C" { extern int __argc; extern const char **__argv; +void abort(void); + int rand(void); -void srand(unsigned long seed); +void srand(int seed); int abs(int j); long labs(long i); -long long strtoll(const char *nptr, char **endptr, int base); -long strtol(const char *nptr, char **endptr, int base); -long double strtold(const char *nptr, char **endptr); -double strtod(const char *nptr, char **endptr); +long strtol(const char *nptr, char **endptr, int base); +long long strtoll(const char *nptr, char **endptr, int base); float strtof(const char *nptr, char **endptr); +double strtod(const char *nptr, char **endptr); +long double strtold(const char *nptr, char **endptr); void InitHeap(void *addr, size_t size); void *sbrk(ptrdiff_t incr); diff --git a/libpsn00b/libc/memset.s b/libpsn00b/libc/memset.s index 73f92bd..6ef84ec 100644 --- a/libpsn00b/libc/memset.s +++ b/libpsn00b/libc/memset.s @@ -9,7 +9,6 @@ memset: # If more than 16 bytes have to be written then take the "large" path, # otherwise use the code below. - blez $a2, .Lnull_count addiu $t0, $a2, -16 bgtz $t0, .Llarge_fill move $v0, $a0 # return_value = dest @@ -39,12 +38,9 @@ memset: sb $a1, 0xc($a0) sb $a1, 0xd($a0) sb $a1, 0xe($a0) - jr $ra sb $a1, 0xf($a0) - -.Lnull_count: jr $ra - move $v0, $a0 # return_value = dest + nop .Llarge_fill: # Initialize fast filling by repeating the fill byte 4 times, so it can be diff --git a/libpsn00b/psxetc/dl.c b/libpsn00b/psxetc/dl.c index fa5e74d..ccf7a7c 100644 --- a/libpsn00b/psxetc/dl.c +++ b/libpsn00b/psxetc/dl.c @@ -1,6 +1,6 @@ /* * PSn00bSDK dynamic linker - * (C) 2021 spicyjpeg - MPL licensed + * (C) 2021-2022 spicyjpeg - MPL licensed * * The bulk of this code is MIPS-specific but not PS1-specific, so the whole * dynamic linker could be ported to other MIPS platforms that do not have one @@ -27,8 +27,10 @@ #define SDK_LIBRARY_NAME "psxetc/dl" #include +#include #include #include +#include #include #include #include @@ -36,13 +38,6 @@ #include #include -/* Compile options */ - -// Comment before building to disable functions that rely on BIOS file APIs, -// i.e. DL_LoadSymbolMapFromFile() and DL_LoadDLLFromFile(). -// FIXME: those seem to be broken currently, and shouldn't be used anyway -//#define USE_FILE_API - /* Private types */ typedef struct { @@ -51,17 +46,15 @@ typedef struct { } MapEntry; typedef struct { - uint32_t nbucket; - uint32_t nchain; + int nbucket, nchain, index; MapEntry *entries; uint32_t *bucket; uint32_t *chain; } SymbolMap; -/* Data */ +/* Internal globals */ -static DL_Error _error_code = RTLD_E_NONE; static SymbolMap _symbol_map; // Accessed by _dl_resolve_helper, stores the pointer to the current resolver @@ -70,11 +63,6 @@ void *(*_dl_resolve_callback)(DLL *, const char *) = 0; /* Private utilities */ -#define _ERROR(code, ret) { \ - _error_code = code; \ - return ret; \ -} - void _dl_resolve_wrapper(void); // Called by _dl_resolve_wrapper() (which is in turn called by GCC stubs) to @@ -82,29 +70,28 @@ void _dl_resolve_wrapper(void); void *_dl_resolve_helper(DLL *dll, uint32_t index) { Elf32_Sym *sym = &(dll->symtab[index]); const char *_name = &(dll->strtab[sym->st_name]); - void *address; + void *addr; if (_dl_resolve_callback) - address = _dl_resolve_callback(dll, _name); + addr = _dl_resolve_callback(dll, _name); else - address = DL_GetSymbolByName(_name); + addr = DL_GetMapSymbol(_name); - if (!address) { - _sdk_log("FATAL! can't resolve %s, locking up\n", _name); - while (1) - __asm__ volatile("nop"); + if (!addr) { + _sdk_log("FATAL! can't resolve %s, aborting\n", _name); + abort(); } // Patch the GOT entry to "cache" the resolved address. This can probably // be implemented in a faster way, but this thing is already too complex. - for (uint32_t i = 0; i < dll->got_length; i++) { + for (int i = 0; i < dll->got_length; i++) { if (dll->got[2 + i] == (uint32_t) sym->st_value) { - dll->got[2 + i] = (uint32_t) address; + dll->got[2 + i] = (uint32_t) addr; break; } } - return address; + return addr; } // Implementation of the weird obscure hashing function used in the ELF .hash @@ -127,120 +114,109 @@ static uint32_t _elf_hash(const char *str) { return value; } -#ifdef USE_FILE_API -static uint8_t *_dl_load_file(const char *filename, size_t *size_output) { - int32_t fd = open(filename, 1); - if (fd < 0) { - _sdk_log("can't open %s, error = %d\n", filename, fd); - _ERROR(RTLD_E_FILE_OPEN, 0); - } +/* Symbol map loading/introspection API */ - // Extract file size from the file's associated control block. - // https://problemkaputt.de/psx-spx.htm#biosmemorymap - FCB *fcb = (FCB *) *((FCB **) 0x80000140); - size_t size = fcb[fd].filesize; +int DL_InitSymbolMap(int num_entries) { + if (_symbol_map.entries) + DL_UnloadSymbolMap(); - uint8_t *buffer = malloc(size); - if (!buffer) { - _sdk_log("unable to allocate %d bytes for %s\n", size, filename); - _ERROR(RTLD_E_FILE_ALLOC, 0); - } + // TODO: find a way to calculate the optimal number of hash table "buckets" + // in order to minimize hash table size + _symbol_map.nbucket = num_entries; + _symbol_map.nchain = num_entries; + _symbol_map.index = 0; + _sdk_log( + "allocating nbucket = %d, nchain = %d\n", + _symbol_map.nbucket, num_entries + ); - //_sdk_log("loading %s (%d bytes)..", filename, size); + _symbol_map.entries = malloc(sizeof(MapEntry) * num_entries); + _symbol_map.bucket = malloc(sizeof(uint32_t) * num_entries); + _symbol_map.chain = malloc(sizeof(uint32_t) * num_entries); - for (uint32_t offset = 0; offset < size; ) { - int32_t length = read(fd, &(buffer[offset]), 0x800); + if (!_symbol_map.entries || !_symbol_map.bucket || !_symbol_map.chain) { + _sdk_log("unable to allocate symbol map table\n"); + return -1; + } - if (length <= 0) { - close(fd); - free(buffer); + memset(_symbol_map.bucket, 0xff, sizeof(uint32_t) * num_entries); + memset(_symbol_map.chain, 0xff, sizeof(uint32_t) * num_entries); - _sdk_log("failed, error = %d\n", length); - _ERROR(RTLD_E_FILE_READ, 0); - } + return 0; +} - //_sdk_log("."); - offset += length; - } +void DL_UnloadSymbolMap(void) { + if (!_symbol_map.entries) + return; - close(fd); - _sdk_log(" done\n"); + free(_symbol_map.entries); + free(_symbol_map.bucket); + free(_symbol_map.chain); - if (size_output) - *size_output = size; - return buffer; + _symbol_map.entries = 0; + _symbol_map.bucket = 0; + _symbol_map.chain = 0; } -#endif -/* Symbol map loading/parsing API */ +void DL_AddMapSymbol(const char *name, void *ptr) { + uint32_t hash = _elf_hash(name); + int index = _symbol_map.index; + _symbol_map.index = index + 1; -int32_t DL_ParseSymbolMap(const char *ptr, size_t size) { - DL_UnloadSymbolMap(); + MapEntry *entry = &(_symbol_map.entries[index]); + entry->hash = hash; + entry->ptr = ptr; + + // Append a reference to the entry to the hash table's chain. + uint32_t *hash_entry = &(_symbol_map.bucket[hash % _symbol_map.nbucket]); + while (*hash_entry != 0xffffffff) + hash_entry = &(_symbol_map.chain[*hash_entry]); + + *hash_entry = index; +} + +int DL_ParseSymbolMap(const char *ptr, size_t size) { + int entries = 0; // Perform a quick scan over the entire map text and count the number of // newlines. This allows us to (over)estimate the number of entries and - // allocate a sufficiently large hash/entry table. - uint32_t entries = 0; - for (uint32_t pos = 0; pos < size; pos++) { + // allocate a sufficiently large hash table. + for (int pos = 0; pos < size; pos++) { if (ptr[pos] == '\n') entries++; } - // TODO: find a way to calculate the optimal number of hash table "buckets" - // in order to minimize hash table size - _symbol_map.nbucket = entries; - _symbol_map.nchain = entries; - _sdk_log( - "allocating nbucket = %d, nchain = %d\n", - _symbol_map.nbucket, - entries - ); - - // Allocate an entry table to store parsed symbols in, and an associated - // hash table (same format as .hash section, with 8-byte header). - _symbol_map.entries = malloc(sizeof(MapEntry) * entries); - _symbol_map.bucket = malloc(sizeof(uint32_t) * _symbol_map.nbucket); - _symbol_map.chain = malloc(sizeof(uint32_t) * entries); + int err = DL_InitSymbolMap(entries); + if (err) + return err; - if (!_symbol_map.entries || !_symbol_map.bucket || !_symbol_map.chain) { - _sdk_log("unable to allocate symbol map table\n"); - _ERROR(RTLD_E_MAP_ALLOC, -1); - } + // Go again through the symbol map and fill in the hash table by calling + // DL_AddMapSymbol() for each valid entry. + entries = 0; - for (uint32_t i = 0; i < _symbol_map.nbucket; i++) - _symbol_map.bucket[i] = 0xffffffff; - for (uint32_t i = 0; i < entries; i++) - _symbol_map.chain[i] = 0xffffffff; - - // Go again through the symbol map and fill in the hash table. - uint32_t index = 0; - for (uint32_t pos = 0; (pos < size) && ptr[pos]; pos++) { - char name[64]; - char type_string[2]; - uint64_t address64; + for (int pos = 0; (pos < size) && ptr[pos]; pos++) { + uint64_t full_addr; + char name[64], type_string[4]; size_t _size; // e.g. "main T ffffffff80000000 100 ...\n" - int32_t parsed = sscanf( + int parsed = sscanf( &(ptr[pos]), "%63s %1s %Lx %x", name, type_string, - &address64, + &full_addr, &_size // Optional, unused (yet) ); if (parsed >= 3) { // Drop the upper 32 bits of the address (for some reason MIPS nm - // insists on printing 64-bit addresses... wtf) and normalize the - // type letter to upper case, then check if the entry is valid and - // non-null. - void *address = (void *) ((uint32_t) address64); - char _type = toupper(type_string[0]); - uint32_t hash = _elf_hash(name); - uint32_t hash_mod = hash % _symbol_map.nbucket; - - if (address && ( + // insists on printing 64-bit addresses... wtf) and check if the + // entry is valid and non-null. + void *addr = (void *) ((uint32_t) full_addr); + char _type = toupper(type_string[0]); + + if (addr && ( (_type == 'T') || // .text (_type == 'R') || // .rodata (_type == 'D') || // .data @@ -248,21 +224,11 @@ int32_t DL_ParseSymbolMap(const char *ptr, size_t size) { )) { //_sdk_log( //"map sym: %08x,%08x [%c %s]\n", - //address, _size, _type, name + //addr, _size, _type, name //); - MapEntry *entry = &(_symbol_map.entries[index]); - entry->hash = hash; - entry->ptr = address; - - // Append a reference to the entry to the hash table's chain - // for the current hash_mod. I can't explain this properly. - uint32_t *hash_entry = &(_symbol_map.bucket[hash_mod]); - while (*hash_entry != 0xffffffff) - hash_entry = &(_symbol_map.chain[*hash_entry]); - - *hash_entry = index; - index++; + DL_AddMapSymbol(name, addr); + entries++; } } @@ -273,55 +239,27 @@ int32_t DL_ParseSymbolMap(const char *ptr, size_t size) { } _sdk_log("parsed %d symbols\n", entries); - if (!entries) - _ERROR(RTLD_E_NO_SYMBOLS, -1); - return entries; } -#ifdef USE_FILE_API -int32_t DL_LoadSymbolMapFromFile(const char *filename) { - size_t size; - char *ptr = _dl_load_file(filename, &size); - if (!ptr) - return -1; - - int32_t entries = DL_ParseSymbolMap(ptr, size); - free(ptr); - - return entries; -} -#endif - -void DL_UnloadSymbolMap(void) { - if (!_symbol_map.entries) - return; - - free(_symbol_map.entries); - free(_symbol_map.bucket); - free(_symbol_map.chain); - _symbol_map.entries = 0; -} - -void *DL_GetSymbolByName(const char *name) { +void *DL_GetMapSymbol(const char *name) { if (!_symbol_map.entries) { - _sdk_log("attempted lookup with no map loaded\n"); - _ERROR(RTLD_E_NO_MAP, 0); + _sdk_log("DL_GetMapSymbol() with no map loaded\n"); + return 0; } - // https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-48031.html - uint32_t hash = _elf_hash(name); - uint32_t hash_mod = hash % _symbol_map.nbucket; - // Go through the hash table's chain until the symbol hash matches the one // calculated. - for (uint32_t i = _symbol_map.bucket[hash_mod]; i != 0xffffffff;) { + // https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-48031.html + uint32_t hash = _elf_hash(name); + + for (int i = _symbol_map.bucket[hash % _symbol_map.nbucket]; i != 0xffffffff;) { if (i >= _symbol_map.nchain) { _sdk_log( - "GetSymbolByName() index out of bounds (%d >= %d)\n", + "DL_GetMapSymbol() index out of bounds (%d >= %d)\n", i, _symbol_map.nchain ); - _ERROR(RTLD_E_HASH_LOOKUP, 0); + return 0; } MapEntry *entry = &(_symbol_map.entries[i]); @@ -335,27 +273,24 @@ void *DL_GetSymbolByName(const char *name) { } _sdk_log("map lookup [%s not found]\n", name); - _ERROR(RTLD_E_MAP_SYMBOL, 0); + return 0; } -void DL_SetResolveCallback(void *(*callback)(DLL *, const char *)) { +void *DL_SetResolveCallback(void *(*callback)(DLL *, const char *)) { + void *old_callback = _dl_resolve_callback; _dl_resolve_callback = callback; + + return old_callback; } /* Library loading and linking API */ -DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) { - if (!ptr) - _ERROR(RTLD_E_DLL_NULL, 0); - - DLL *dll = malloc(sizeof(DLL)); - if (!dll) { - _sdk_log("unable to allocate DLL struct\n"); - _ERROR(RTLD_E_DLL_ALLOC, 0); - } +DLL *DL_CreateDLL(DLL *dll, void *ptr, size_t size, DL_ResolveMode mode) { + if (!dll || !ptr) + return 0; dll->ptr = ptr; - dll->malloc_ptr = (mode & RTLD_FREE_ON_DESTROY) ? ptr : 0; + dll->malloc_ptr = (mode & DL_FREE_ON_DESTROY) ? ptr : 0; dll->size = size; _sdk_log("initializing DLL at %08x\n", ptr); @@ -371,47 +306,30 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) { switch (dyn->d_tag) { // Offset of .got section case DT_PLTGOT: - //_sdk_log("[PLTGOT]\n"); - dll->got = (void *) (ptr + dyn->d_un.d_val); break; // Offset of .hash section case DT_HASH: - //_sdk_log("[HASH]\n"); - dll->hash = (void *) (ptr + dyn->d_un.d_val); break; // Offset of .dynstr (NOT .strtab) section case DT_STRTAB: - //_sdk_log("[STRTAB]\n"); - dll->strtab = (void *) (ptr + dyn->d_un.d_val); break; // Offset of .dynsym (NOT .symtab) section case DT_SYMTAB: - //_sdk_log("[SYMTAB]\n"); - dll->symtab = (void *) (ptr + dyn->d_un.d_val); break; - // Length of .dynstr section - //case DT_STRSZ: - //_sdk_log("[STRSZ]\n"); - //break; - // Length of each .dynsym entry case DT_SYMENT: - //_sdk_log("[SYMENT]\n"); - // Only 16-byte symbol table entries are supported. if (dyn->d_un.d_val != sizeof(Elf32_Sym)) { - free(dll); - _sdk_log("invalid DLL symtab entry size %d\n", dyn->d_un.d_val); - _ERROR(RTLD_E_DLL_FORMAT, 0); + return 0; } break; @@ -421,73 +339,44 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) { // Versions other than 1 are unsupported (do they even exist?). if (dyn->d_un.d_val != 1) { - free(dll); - _sdk_log("invalid DLL version %d\n", dyn->d_un.d_val); - _ERROR(RTLD_E_DLL_FORMAT, 0); + return 0; } break; // DLL/ABI flags case DT_MIPS_FLAGS: - //_sdk_log("[MIPS_FLAGS]\n"); - // Shortcut pointers (whatever they are) are not supported. if (dyn->d_un.d_val & RHF_QUICKSTART) { - free(dll); - _sdk_log("invalid DLL flags\n"); - _ERROR(RTLD_E_DLL_FORMAT, 0); + return 0; } break; // Number of local (not to resolve) GOT entries case DT_MIPS_LOCAL_GOTNO: - //_sdk_log("[MIPS_LOCAL_GOTNO]\n"); - local_got_len = dyn->d_un.d_val; break; // Base address DLL was compiled for case DT_MIPS_BASE_ADDRESS: - //_sdk_log("[MIPS_BASE_ADDRESS]\n"); - // Base addresses other than zero are not supported. It would // be easy enough to support them, but why? if (dyn->d_un.d_val) { - free(dll); - _sdk_log("invalid DLL base address %08x\n", dyn->d_un.d_val); - _ERROR(RTLD_E_DLL_FORMAT, 0); + return 0; } break; // Number of symbol table entries case DT_MIPS_SYMTABNO: - //_sdk_log("[MIPS_SYMTABNO]\n"); - dll->symbol_count = dyn->d_un.d_val; break; - // Index of first unresolved symbol table entry - //case DT_MIPS_UNREFEXTNO: - //_sdk_log("[MIPS_UNREFEXTNO]\n"); - //break; - // Index of first symbol table entry which has a matching GOT entry case DT_MIPS_GOTSYM: - //_sdk_log("[MIPS_GOTSYM]\n"); - first_got_sym = dyn->d_un.d_val; break; - - // Number of pages the GOT is split into (does not apply to PS1) - //case DT_MIPS_HIPAGENO: - //_sdk_log("[MIPS_HIPAGENO]\n"); - //break; - - //default: - //_sdk_log("[ignored]\n"); } } @@ -513,14 +402,14 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) { dll->got[0] = (uint32_t) &_dl_resolve_wrapper; dll->got[1] = (uint32_t) dll; - for (uint32_t i = 0; i < dll->got_length; i++) + for (int i = 0; i < dll->got_length; i++) dll->got[2 + i] += (uint32_t) ptr; // Fix addresses in the symbol table. // TODO: clean this shit up uint32_t got_offset = first_got_sym; - for (uint32_t i = 0; i < dll->symbol_count; i++) { + for (int i = 0; i < dll->symbol_count; i++) { Elf32_Sym *sym = &(dll->symtab[i]); const char *_name = &(dll->strtab[sym->st_name]); @@ -533,12 +422,12 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) { //sym->st_value, sym->st_size, _name //); - // If RTLD_NOW was passed, resolve GOT entries ahead of time by + // If DL_NOW was passed, resolve GOT entries ahead of time by // cross-referencing them with the symbol table. - if (!(mode & RTLD_NOW)) + if (!(mode & DL_NOW)) continue; - for (uint32_t j = got_offset; j < dll->got_length; j++) { + for (int j = got_offset; j < dll->got_length; j++) { if (dll->got[2 + j] != (uint32_t) sym->st_value) continue; @@ -553,10 +442,8 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) { )) { dll->got[2 + j] = (uint32_t) _dl_resolve_callback(dll, _name); - if (!dll->got[2 + j]) { - free(dll); - _ERROR(RTLD_E_MAP_SYMBOL, 0); - } + if (!dll->got[2 + j]) + return 0; } break; @@ -573,7 +460,7 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) { // DLL itself. const uint32_t *ctor_list = DL_GetDLLSymbol(dll, "__CTOR_LIST__"); if (ctor_list) { - for (uint32_t i = ((uint32_t) ctor_list[0]); i >= 1; i--) { + for (int i = ((int) ctor_list[0]); i >= 1; i--) { void (*ctor)(void) = (void (*)(void)) ctor_list[i]; DL_PRE_CALL(ctor); ctor(); @@ -583,64 +470,47 @@ DLL *DL_CreateDLL(void *ptr, size_t size, DL_ResolveMode mode) { return dll; } -#ifdef USE_FILE_API -DLL *DL_LoadDLLFromFile(const char *filename, DL_ResolveMode mode) { - size_t size; - char *ptr = _dl_load_file(filename, &size); - if (!ptr) - return 0; - - DLL *dll = DL_CreateDLL(ptr, size, mode | RTLD_FREE_ON_DESTROY); - if (!dll) - free(ptr); - - return dll; -} -#endif - void DL_DestroyDLL(DLL *dll) { - if (dll == RTLD_DEFAULT) + if (!dll) return; if (dll->ptr) { // Call the DLL's global destructors. const uint32_t *dtor_list = DL_GetDLLSymbol(dll, "__DTOR_LIST__"); if (dtor_list) { - for (uint32_t i = 0; i < ((uint32_t) dtor_list[0]); i++) { + for (int i = 0; i < ((int) dtor_list[0]); i++) { void (*dtor)(void) = (void (*)(void)) dtor_list[i + 1]; DL_PRE_CALL(dtor); dtor(); } } + + dll->ptr = 0; } - // If the DLL is associated to a buffer allocated by DL_LoadDLLFromFile(), - // free that buffer. - if (dll->malloc_ptr) + // If the DLL is associated to a buffer, free that buffer. + if (dll->malloc_ptr) { free(dll->malloc_ptr); - - free(dll); + dll->malloc_ptr = 0; + } } void *DL_GetDLLSymbol(const DLL *dll, const char *name) { - if (dll == RTLD_DEFAULT) - return DL_GetSymbolByName(name); - //return _dl_resolve_callback(RTLD_DEFAULT, name); + if (!dll) + return DL_GetMapSymbol(name); + //return _dl_resolve_callback(0, name); - // https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-48031.html uint32_t nbucket = dll->hash[0]; uint32_t nchain = dll->hash[1]; const uint32_t *bucket = &(dll->hash[2]); const uint32_t *chain = &(dll->hash[2 + nbucket]); - uint32_t hash_mod = _elf_hash(name) % nbucket; - // Go through the hash table's chain until the symbol name matches the one // provided. - for (uint32_t i = bucket[hash_mod]; i != 0xffffffff;) { + for (int i = bucket[_elf_hash(name) % nbucket]; i != 0xffffffff;) { if (i >= nchain) { _sdk_log("DL_GetDLLSymbol() index out of bounds (%d >= %d)\n", i, nchain); - _ERROR(RTLD_E_HASH_LOOKUP, 0); + return 0; } Elf32_Sym *sym = &(dll->symtab[i]); @@ -655,12 +525,5 @@ void *DL_GetDLLSymbol(const DLL *dll, const char *name) { } _sdk_log("DLL lookup [%s not found]\n", name); - _ERROR(RTLD_E_DLL_SYMBOL, 0); -} - -DL_Error DL_GetLastError(void) { - DL_Error last = _error_code; - _error_code = RTLD_E_NONE; - - return last; + return 0; } -- cgit v1.2.3