diff --git a/exosphere/src/dbg/fmt.c b/exosphere/src/dbg/fmt.c new file mode 100644 index 000000000..3c6e243b8 --- /dev/null +++ b/exosphere/src/dbg/fmt.c @@ -0,0 +1,291 @@ +/* File : barebones/ee_printf.c + This file contains an implementation of ee_printf that only requires a method to output a char to a UART without pulling in library code. +This code is based on a file that contains the following: + Copyright (C) 2002 Michael Ringgaard. All rights reserved. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the project nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. +*/ + +//TuxSH's changes: add support for 64-bit numbers, remove floating-point code +// (C) AuroraWright, TuxSH + +#include "../utils.h" +#include + +#include "fmt.h" + +#define ZEROPAD (1<<0) //Pad with zero +#define SIGN (1<<1) //Unsigned/signed long +#define PLUS (1<<2) //Show plus +#define SPACE (1<<3) //Spacer +#define LEFT (1<<4) //Left justified +#define HEX_PREP (1<<5) //0x +#define UPPERCASE (1<<6) //'ABCDEF' + +#define IS_DIGIT(c) ((c) >= '0' && (c) <= '9') + +static int32_t skipAtoi(const char **s) +{ + int32_t i = 0; + + while(IS_DIGIT(**s)) i = i * 10 + *((*s)++) - '0'; + + return i; +} + +static char *processNumber(char *str, int64_t num, bool isHex, int32_t size, int32_t precision, uint32_t type) +{ + char sign = 0; + + if(type & SIGN) + { + if(num < 0) + { + sign = '-'; + num = -num; + size--; + } + else if(type & PLUS) + { + sign = '+'; + size--; + } + else if(type & SPACE) + { + sign = ' '; + size--; + } + } + + static const char *lowerDigits = "0123456789abcdef", + *upperDigits = "0123456789ABCDEF"; + + int32_t i = 0; + char tmp[20]; + const char *dig = (type & UPPERCASE) ? upperDigits : lowerDigits; + + if(num == 0) + { + if(precision != 0) tmp[i++] = '0'; + type &= ~HEX_PREP; + } + else + { + while(num != 0) + { + uint64_t base = isHex ? 16ULL : 10ULL; + tmp[i++] = dig[(uint64_t)num % base]; + num = (int64_t)((uint64_t)num / base); + } + } + + if(type & LEFT || precision != -1) type &= ~ZEROPAD; + if(type & HEX_PREP && isHex) size -= 2; + if(i > precision) precision = i; + size -= precision; + if(!(type & (ZEROPAD | LEFT))) while(size-- > 0) *str++ = ' '; + if(sign) *str++ = sign; + + if(type & HEX_PREP && isHex) + { + *str++ = '0'; + *str++ = 'x'; + } + + if(type & ZEROPAD) while(size-- > 0) *str++ = '0'; + while(i < precision--) *str++ = '0'; + while(i-- > 0) *str++ = tmp[i]; + while(size-- > 0) *str++ = ' '; + + return str; +} + +int visprintf(char *buf, const char *fmt, va_list args) +{ + char *str; + + for(str = buf; *fmt; fmt++) + { + if(*fmt != '%') + { + *str++ = *fmt; + continue; + } + + //Process flags + uint32_t flags = 0; //Flags to number() + bool loop = true; + + while(loop) + { + switch(*++fmt) + { + case '-': flags |= LEFT; break; + case '+': flags |= PLUS; break; + case ' ': flags |= SPACE; break; + case '#': flags |= HEX_PREP; break; + case '0': flags |= ZEROPAD; break; + default: loop = false; break; + } + } + + //Get field width + int32_t fieldWidth = -1; //Width of output field + if(IS_DIGIT(*fmt)) fieldWidth = skipAtoi(&fmt); + else if(*fmt == '*') + { + fmt++; + + fieldWidth = va_arg(args, int32_t); + + if(fieldWidth < 0) + { + fieldWidth = -fieldWidth; + flags |= LEFT; + } + } + + //Get the precision + int32_t precision = -1; //Min. # of digits for integers; max number of chars for from string + if(*fmt == '.') + { + fmt++; + + if(IS_DIGIT(*fmt)) precision = skipAtoi(&fmt); + else if(*fmt == '*') + { + fmt++; + precision = va_arg(args, int32_t); + } + + if(precision < 0) precision = 0; + } + + //Get the conversion qualifier + uint32_t integerType = 0; + if(*fmt == 'l') + { + if(*++fmt == 'l') + { + fmt++; + integerType = 1; + } + + } + else if(*fmt == 'h') + { + if(*++fmt == 'h') + { + fmt++; + integerType = 3; + } + else integerType = 2; + } + + bool isHex; + + switch(*fmt) + { + case 'c': + if(!(flags & LEFT)) while(--fieldWidth > 0) *str++ = ' '; + *str++ = (uint8_t)va_arg(args, int32_t); + while(--fieldWidth > 0) *str++ = ' '; + continue; + + case 's': + { + char *s = va_arg(args, char *); + if(!s) s = ""; + uint32_t len = (precision != -1) ? strnlen(s, precision) : strlen(s); + if(!(flags & LEFT)) while((int32_t)len < fieldWidth--) *str++ = ' '; + for(uint32_t i = 0; i < len; i++) *str++ = *s++; + while((int32_t)len < fieldWidth--) *str++ = ' '; + continue; + } + + case 'p': + if(fieldWidth == -1) + { + fieldWidth = 8; + flags |= ZEROPAD; + } + str = processNumber(str, va_arg(args, uint32_t), true, fieldWidth, precision, flags); + continue; + + //Integer number formats - set up the flags and "break" + case 'X': + flags |= UPPERCASE; + //Falls through + case 'x': + isHex = true; + break; + + case 'd': + case 'i': + flags |= SIGN; + //Falls through + case 'u': + isHex = false; + break; + + default: + if(*fmt != '%') *str++ = '%'; + if(*fmt) *str++ = *fmt; + else fmt--; + continue; + } + + int64_t num; + + if(flags & SIGN) + { + if(integerType == 1) num = va_arg(args, int64_t); + else num = va_arg(args, int32_t); + + if(integerType == 2) num = (int16_t)num; + else if(integerType == 3) num = (int8_t)num; + } + else + { + if(integerType == 1) num = va_arg(args, uint64_t); + else num = va_arg(args, uint32_t); + + if(integerType == 2) num = (uint16_t)num; + else if(integerType == 3) num = (uint8_t)num; + } + + str = processNumber(str, num, isHex, fieldWidth, precision, flags); + } + + *str = 0; + return str - buf; +} + +int isprintf(char *buf, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + int res = visprintf(buf, fmt, args); + va_end(args); + return res; +} diff --git a/exosphere/src/dbg/fmt.h b/exosphere/src/dbg/fmt.h new file mode 100644 index 000000000..97391b669 --- /dev/null +++ b/exosphere/src/dbg/fmt.h @@ -0,0 +1,9 @@ +#ifndef EXOSPHERE_DBG_FMT_H +#define EXOSPHERE_DBG_FMT_H + +#include + +int visprintf(char *buf, const char *fmt, va_list args); +int isprintf(char *buf, const char *fmt, ...); + +#endif diff --git a/exosphere/src/dbg/log.c b/exosphere/src/dbg/log.c new file mode 100644 index 000000000..0f33121c5 --- /dev/null +++ b/exosphere/src/dbg/log.c @@ -0,0 +1,47 @@ +#include +#include +#include "log.h" +#include "log_device_null.h" +#include "log_device_uart.h" +#include "fmt.h" +#include "../synchronization.h" + +#ifndef NDEBUG +static atomic_flag g_log_lock = ATOMIC_FLAG_INIT; +static debug_log_device_t *dev; +#endif + +void dbg_log_initialize(DebugLogDevice device) { +#ifndef NDEBUG + static debug_log_device_t *const devs[] = {&g_debug_log_device_null.super, &g_debug_log_device_uart.super}; + dev = device >= DEBUGLOGDEVICE_MAX ? &g_debug_log_device_null.super : devs[device]; +#else + (void)device; +#endif +} + +/* NOTE: no bound checks are done */ +void dbg_log_write(const char *fmt, ...) { +#ifndef NDEBUG + char buf[DBG_LOG_BUF_SIZE]; + int len; + va_list args; + lock_acquire(&g_log_lock); + + va_start(args, fmt); + len = visprintf(buf, fmt, args); + va_end(args); + + dev->write_string(dev, buf, len); + lock_release(&g_log_lock); +#else + (void)fmt; +#endif +} + +void dbg_log_finalize(void) { +#ifndef NDEBUG + dev->finalize(dev); + dev = NULL; +#endif +} diff --git a/exosphere/src/dbg/log.h b/exosphere/src/dbg/log.h new file mode 100644 index 000000000..5ae4fe043 --- /dev/null +++ b/exosphere/src/dbg/log.h @@ -0,0 +1,25 @@ +#ifndef EXOSPHERE_DBG_LOG_H +#define EXOSPHERE_DBG_LOG_H + +#include "../utils.h" + +#define DBG_LOG_BUF_SIZE 256 + +typedef enum { + DEBUGLOGDEVICE_NULL = 0, + DEBUGLOGDEVICE_UART = 1, + + DEBUGLOGDEVICE_MAX = 2, +} DebugLogDevice; + +typedef struct debug_log_device_t { + void (*initialize)(struct debug_log_device_t *this, ...); + void (*write_string)(struct debug_log_device_t *this, const char *str, size_t len); + void (*finalize)(struct debug_log_device_t *this); +} debug_log_device_t; + +void dbg_log_initialize(DebugLogDevice device); +void dbg_log_write(const char *fmt, ...); +void dbg_log_finalize(void); + +#endif diff --git a/exosphere/src/dbg/log_device_null.c b/exosphere/src/dbg/log_device_null.c new file mode 100644 index 000000000..09c29fe73 --- /dev/null +++ b/exosphere/src/dbg/log_device_null.c @@ -0,0 +1,26 @@ +#include "log_device_null.h" + +static void initialize(debug_log_device_null_t *this) { + (void)this; + /* Do nothing */ +} + +static void write_string(debug_log_device_null_t *this, const char *str, size_t len) { + (void)this; + (void)str; + (void)len; + /* Do nothing */ +} + +static void finalize(debug_log_device_null_t *this) { + (void)this; + /* Do nothing */ +} + +debug_log_device_null_t g_debug_log_device_null = { + .super = { + .initialize = (void (*)(debug_log_device_t *, ...))initialize, + .write_string = (void (*)(debug_log_device_t *, const char *, size_t))write_string, + .finalize = (void (*)(debug_log_device_t *))finalize, + }, +}; diff --git a/exosphere/src/dbg/log_device_null.h b/exosphere/src/dbg/log_device_null.h new file mode 100644 index 000000000..900316485 --- /dev/null +++ b/exosphere/src/dbg/log_device_null.h @@ -0,0 +1,13 @@ +#ifndef EXOSPHERE_DBG_LOG_DEVICE_NULL_H +#define EXOSPHERE_DBG_LOG_DEVICE_NULL_H + +#include "log.h" + +typedef struct { + debug_log_device_t super; + /* Additonnal attributes go here */ +} debug_log_device_null_t; + +extern debug_log_device_null_t g_debug_log_device_null; + +#endif diff --git a/exosphere/src/dbg/log_device_uart.c b/exosphere/src/dbg/log_device_uart.c new file mode 100644 index 000000000..41dee69cf --- /dev/null +++ b/exosphere/src/dbg/log_device_uart.c @@ -0,0 +1,30 @@ +#include "log_device_uart.h" +#include "../car.h" +#include "../uart.h" + +static void initialize(debug_log_device_uart_t *this) { + if (!this->is_initialized) { + uart_select(0); /* UART-A */ + clkrst_enable(CARDEVICE_UARTA); + uart_initialize(0 /* I don't know */); + this->is_initialized = true; + } +} + +static void write_string(debug_log_device_uart_t *this, const char *str, size_t len) { + (void)this; + (void)len; + uart_transmit_str(str); +} + +static void finalize(debug_log_device_uart_t *this) { + clkrst_disable(CARDEVICE_UARTA); +} + +debug_log_device_uart_t g_debug_log_device_uart = { + .super = { + .initialize = (void (*)(debug_log_device_t *, ...))initialize, + .write_string = (void (*)(debug_log_device_t *, const char *, size_t))write_string, + .finalize = (void (*)(debug_log_device_t *))finalize, + }, +}; diff --git a/exosphere/src/dbg/log_device_uart.h b/exosphere/src/dbg/log_device_uart.h new file mode 100644 index 000000000..19abc0aef --- /dev/null +++ b/exosphere/src/dbg/log_device_uart.h @@ -0,0 +1,13 @@ +#ifndef EXOSPHERE_DBG_LOG_DEVICE_UART_H +#define EXOSPHERE_DBG_LOG_DEVICE_UART_H + +#include "log.h" + +typedef struct { + debug_log_device_t super; + bool is_initialized; +} debug_log_device_uart_t; + +extern debug_log_device_uart_t g_debug_log_device_uart; + +#endif diff --git a/exosphere/src/misc.h b/exosphere/src/misc.h index 08950aeb7..7d667524c 100644 --- a/exosphere/src/misc.h +++ b/exosphere/src/misc.h @@ -19,4 +19,9 @@ #define PINMUX_AUX_GEN1_I2C_SCL_0 MAKE_MISC_REG(0x30BC) #define PINMUX_AUX_GEN1_I2C_SDA_0 MAKE_MISC_REG(0x30C0) +#define PINMUX_AUX_UARTn_TX_0(n) MAKE_MISC_REG(0x30E4 + 0x10 * (n)) +#define PINMUX_AUX_UARTn_RX_0(n) MAKE_MISC_REG(0x30E8 + 0x10 * (n)) +#define PINMUX_AUX_UARTn_RTS_0(n) MAKE_MISC_REG(0x30EC + 0x10 * (n)) +#define PINMUX_AUX_UARTn_CTS_0(n) MAKE_MISC_REG(0x30F0 + 0x10 * (n)) + #endif diff --git a/exosphere/src/uart.c b/exosphere/src/uart.c index 409d04dce..11f782a24 100644 --- a/exosphere/src/uart.c +++ b/exosphere/src/uart.c @@ -1,4 +1,14 @@ #include "uart.h" +#include "misc.h" + +void uart_select(unsigned int id) { + /* This confirmation is valid for UART-A, I don't know about the other UARTs. */ + /* Official Nintendo code (for UART-A, at least) */ + PINMUX_AUX_UARTn_TX_0(id) = 0; /* UART */ + PINMUX_AUX_UARTn_RX_0(id) = 0x48; /* UART, enable, pull up */ + PINMUX_AUX_UARTn_RTS_0(id) = 0; /* UART */ + PINMUX_AUX_UARTn_CTS_0(id) = 0x44; /* UART, enable, pull down */ +} void uart_initialize(uint16_t divider) { /* Setup UART in 16450 mode. We assume the relevant UART clock has been enabled. */ @@ -33,4 +43,4 @@ void uart_transmit_hex(uint32_t value) { uint32_t nibble = (value >> (28 - i * 4)) & 0xF; uart_transmit_char("0123456789ABCDEF"[nibble]); } -} \ No newline at end of file +} diff --git a/exosphere/src/uart.h b/exosphere/src/uart.h index 54dec7e96..f1c4f7e43 100644 --- a/exosphere/src/uart.h +++ b/exosphere/src/uart.h @@ -20,6 +20,7 @@ static inline uintptr_t get_uarta_base(void) { #define UART_LCR_0 MAKE_REG32(UARTA_BASE + 0xC) #define UART_LSR_0 MAKE_REG32(UARTA_BASE + 0x14) +void uart_select(unsigned int id); void uart_initialize(uint16_t divider); void uart_transmit_char(char ch); void uart_transmit_str(const char *str);