aboutsummaryrefslogtreecommitdiff
path: root/src/debug.c
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2024-05-24 13:24:27 +0300
committerKimplul <kimi.h.kuparinen@gmail.com>2024-05-24 18:13:43 +0300
commitbc600ecc3bdf0f189861dfb840f70c2339a7a853 (patch)
treed9840b1dc1b865442a028c03ad7109ac67894f6e /src/debug.c
parent6a7073e5f262db9a4578ff00b5b28e34335564ce (diff)
downloadkmi-bc600ecc3bdf0f189861dfb840f70c2339a7a853.tar.gz
kmi-bc600ecc3bdf0f189861dfb840f70c2339a7a853.zip
rename common to src
+ I keep starting to type src and wondering why autocomplete won't work, I guess src is just uncounciously a better name
Diffstat (limited to 'src/debug.c')
-rw-r--r--src/debug.c786
1 files changed, 786 insertions, 0 deletions
diff --git a/src/debug.c b/src/debug.c
new file mode 100644
index 0000000..fcec537
--- /dev/null
+++ b/src/debug.c
@@ -0,0 +1,786 @@
+/* SPDX-License-Identifier: copyleft-next-0.3.1 */
+/* Copyright 2021 - 2022, Kim Kuparinen < kimi.h.kuparinen@gmail.com > */
+
+/**
+ * @file debug.c
+ * Handle printing to serial. Note that the serial drivers are only included
+ * when running a debug build to save space in release mode.
+ */
+
+#include <kmi/types.h>
+#include <kmi/debug.h>
+#include <kmi/bits.h>
+#include <kmi/vmem.h>
+#include <kmi/pmem.h>
+#include <arch/vmem.h>
+#include <libfdt.h>
+#include <stdarg.h>
+
+#if defined(DEBUG)
+
+/** Debug context structure. */
+struct dbg_info {
+ /** Address of serial device in virtual memory. Always access registers
+ * through this address rather than \p addr. */
+ pm_t base;
+ /** Shift count between registers of serial device. */
+ size_t shift;
+ /** Type of serial device. */
+ enum serial_dev dev;
+ /** Physical address of serial device. */
+ pm_t addr;
+};
+
+/** Static debugging information. */
+static struct dbg_info dbg_info = (struct dbg_info){ 0 };
+
+/* forward declarations. */
+static struct dbg_info __dbg_from_fdt(const void *fdt);
+
+void init_dbg(const void *fdt)
+{
+ dbg_info = __dbg_from_fdt(fdt);
+}
+
+void setup_dmap_dbg()
+{
+ /* noop with the new 8250 driver, though might still be useful in the
+ * future if I add in some other kind of simple uart */
+}
+
+void setup_io_dbg(struct vmem *b)
+{
+ dbg_info.base = map_io_dbg(b);
+}
+
+vm_t map_io_dbg(struct vmem *b)
+{
+ return setup_kernel_io(b, dbg_info.addr);
+}
+
+/** 8250 data register index. */
+#define UART_8250_DATA 0
+/** 8250 irq register idex. */
+#define UART_8250_IRQ 1
+/** 8250 ird_id register index. */
+#define UART_8250_IRQ_ID 2
+/** 8250 lcr register index. */
+#define UART_8250_LCR 3
+/** 8250 mcr register index. */
+#define UART_8250_MCR 4
+/** 8250 lsr register index. */
+#define UART_8250_LSR 5
+/** 8250 msr register index. */
+#define UART_8250_MSR 6
+/** 8250 scr register index. */
+#define UART_8250_SCR 7
+
+/* if there arises a need for more supported serial drivers, I should probably
+ * try to implement some kind of basic driver subsystem, but this is good enough
+ * for now. */
+
+/** Line status data ready. */
+#define LSR_DR (1 << 0)
+
+/** Line status overrun error. */
+#define LSR_OE (1 << 1)
+
+/** Line status parity error. */
+#define LSR_PE (1 << 2)
+
+/** Line status framing error. */
+#define LSR_FE (1 << 3)
+
+/** Line status break interrupt. */
+#define LSR_BI (1 << 4)
+
+/** Line status transmitter holding register. */
+#define LSR_THRE (1 << 5)
+
+/** Line status transmitter empty. */
+#define LSR_TEMT (1 << 6)
+
+/** Line status error in RCVR FIFO. */
+#define LSR_ERR (1 << 7)
+
+/**
+ * 8250 transmitter empty.
+ *
+ * @return \c 0 if not empty, non-zero otherwise.
+ */
+static int __8250_tx_empty()
+{
+ volatile uint8_t *lsr = (uint8_t *)dbg_info.base +
+ (UART_8250_LSR << dbg_info.shift);
+ return (*lsr) & LSR_THRE;
+}
+
+/**
+ * Put character out onto 8250 serial lines.
+ *
+ * @param c Character to output.
+ */
+static void __8250_putchar(char c)
+{
+ if (!dbg_info.base)
+ return;
+
+ while (__8250_tx_empty() == 0)
+ ;
+
+ volatile uint8_t *data = (uint8_t *)dbg_info.base +
+ (UART_8250_DATA << dbg_info.shift);
+ *data = c;
+}
+
+/**
+ * Put one character out on the serial lines.
+ * Automatically handles newlines.
+ *
+ * @param c Character to put.
+ */
+static void __putchar(char c)
+{
+ if (c == '\n')
+ __putchar('\r');
+
+ switch (dbg_info.dev) {
+ case UART_8250: __8250_putchar(c); return;
+ }
+}
+
+/**
+ * Convert serial device name (from FDT) to serial device enumerator.
+ *
+ * @param dev_name Device name string.
+ * @return Corresponding enumerator value.
+ */
+static enum serial_dev __serial_dev_enum(const char *dev_name)
+{
+ /* qemu, for example */
+ if (strncmp("ns16550", dev_name, 7) == 0)
+ return UART_8250;
+
+ /* mentioned in dtc documentation as an example */
+ if (strncmp("ns8250", dev_name, 7) == 0)
+ return UART_8250;
+
+ /* starfive visionfive 2, for example (hopefully works) */
+ if (strncmp("snps,dw-apb-uart", dev_name, 16) == 0)
+ return UART_8250;
+
+ return -1;
+}
+
+/**
+ * Get debugging info from FDT.
+ *
+ * @param fdt Global FDT pointer.
+ * @return Filled \ref dbg_info structure.
+ */
+static struct dbg_info __dbg_from_fdt(const void *fdt)
+{
+ int chosen_offset = fdt_path_offset(fdt, "/chosen");
+ const char *stdout =
+ fdt_getprop(fdt, chosen_offset, "stdout-path", NULL);
+
+ /* discard options */
+ size_t baselen = strlen(stdout);
+ const char *options = strchr(stdout, ':');
+ if (options)
+ baselen = options - stdout;
+
+ int stdout_offset = fdt_path_offset_namelen(fdt, stdout, baselen);
+
+ /* get serial device type */
+ const char *dev_name = (const char *)fdt_getprop(fdt, stdout_offset,
+ "compatible", NULL);
+
+ enum serial_dev dev = __serial_dev_enum(dev_name);
+
+ /* get serial device address */
+ const void *reg_ptr = fdt_getprop(fdt, stdout_offset, "reg", NULL);
+ struct cell_info ci = get_cellinfo(fdt, stdout_offset);
+ pm_t dbg_ptr = (pm_t)fdt_load_reg_addr(ci, reg_ptr, 0);
+
+ /* get serial device offset if present */
+ size_t shift = 0;
+ const void *shift_ptr = fdt_getprop(fdt, stdout_offset, "reg-shift",
+ NULL);
+
+ if (shift_ptr)
+ shift = (size_t)fdt_load_int32_ptr(shift_ptr);
+
+ /* while in direct map, base == addr, and this changes only when we jump
+ * into virtually mapped io */
+ return (struct dbg_info){ dbg_ptr, shift, dev, dbg_ptr };
+}
+
+/** Printf formatting left align flag. */
+#define LEFT_FLAG (1 << 0)
+
+/** Printf formatting explicit sign flag. */
+#define SIGN_FLAG (1 << 1)
+
+/** Printf formatting hash sign flag. */
+#define HASH_FLAG (1 << 2)
+
+/** Printf formatting zero padding flag. */
+#define ZERO_FLAG (1 << 3)
+
+/** Printf formatting ' flag. */
+#define FMT_FLAG (1 << 4)
+
+/** Printf formatting space flag. */
+#define SPACE_FLAG (1 << 5)
+
+/** Printf formatting long specifier flag. */
+#define LONG_FLAG (1 << 6)
+
+/** Printf formatting long long specifier flag. */
+#define LLONG_FLAG (1 << 7)
+
+/** Printf formatting short flag. */
+#define SHORT_FLAG (1 << 8)
+
+/** Printf formatting char flag. */
+#define CHAR_FLAG (1 << 9)
+
+/** Printf precision flag. */
+#define PRECS_FLAG (1 << 11)
+
+/** Printf unsigned flag. */
+#define UNSIGN_FLAG (1 << 12)
+
+/** Printf width flag. */
+#define WIDTH_FLAG (1 << 13)
+
+/** Printf padding flag. */
+#define PAD_FLAG (1 << 14)
+
+/** Printf continue flag. */
+#define CONT 1
+
+/** Printf stop flag. */
+#define STOP 0
+
+/**
+ * Check if character is ASCII decimal digit.
+ *
+ * @param c Character to check.
+ * @return \c true if character is ASCII decimal digit, \c false otherwise.
+ */
+static bool __is_digit(char c)
+{
+ return (c >= '0') && (c <= '9');
+}
+
+/**
+ * Convert string to corresponding number (assuming int).
+ *
+ * @param s Number string.
+ * @return Corresponding number.
+ */
+static int __atoi(const char *s)
+{
+ unsigned int i = 0;
+ while (__is_digit(*s)) {
+ i = i * 10 + (unsigned int)(*(s++) - '0');
+ }
+
+ return i;
+}
+
+/**
+ * Calculate signed char from value using type interpretation.
+ *
+ * @param x Type to interpret value as.
+ * @param value Value to interpret.
+ * @param base Base to interpret value in.
+ */
+#define handle_type(x, value, base) \
+ c = (x)value % (x)base; \
+ value = (x)value / (x)base;
+/**
+ * Convert number to string length.
+ *
+ * @param value Number to print.
+ * @param base Base to print in.
+ * @param flags Flags to output.
+ * @param print Print number as well.
+ * @return Length of corresponding string.
+ */
+static size_t __integral_val(ssize_t value, size_t base, size_t flags,
+ bool print)
+{
+ /* assume ascii numbers, which is why 'signed char' is probably fine */
+ size_t ret = 0;
+ signed char c = 0;
+
+
+ if (!is_set(flags, UNSIGN_FLAG)) {
+ /* signed values, only with i format */
+ if (is_set(flags, LLONG_FLAG)) {
+ handle_type(signed long long, value, base);
+ } else if (is_set(flags, LONG_FLAG)) {
+ handle_type(signed long, value, base);
+ } else if (is_set(flags, SHORT_FLAG)) {
+ handle_type(signed short, value, base);
+ } else if (is_set(flags, CHAR_FLAG)) {
+ handle_type(signed char, value, base);
+ } else {
+ handle_type(signed int, value, base);
+ }
+
+ /* convert negative results into actual characters */
+ c = c < 0 ? -c : c;
+
+ } else {
+ /* unsigned values, everything else */
+ if (is_set(flags, LLONG_FLAG)) {
+ handle_type(unsigned long long, value, base);
+ } else if (is_set(flags, LONG_FLAG)) {
+ handle_type(unsigned long, value, base);
+ } else if (is_set(flags, SHORT_FLAG)) {
+ handle_type(unsigned short, value, base);
+ } else if (is_set(flags, CHAR_FLAG)) {
+ handle_type(unsigned char, value, base);
+ } else {
+ handle_type(unsigned int, value, base);
+ }
+ }
+
+ if (base == 16)
+ c += c > 9 ? 'a' - 10 : '0';
+ else
+ c += '0';
+
+ if (value != 0)
+ ret = __integral_val(value, base, flags, print);
+
+ if (print)
+ __putchar(c);
+
+ return ret + 1;
+}
+
+/**
+ * Print prefix corresponding to \c base.
+ *
+ * @param base Base to integer.
+ * @return Length of prefix as string.
+ */
+static size_t __print_prefix(size_t base)
+{
+ size_t i = 0;
+
+ const char *hex = "0x";
+ const char *oct = "0";
+ const char *bin = "0b";
+ const char *empty = "";
+
+ const char *prefix;
+
+ if (base == 16)
+ prefix = hex;
+ else if (base == 8)
+ prefix = oct;
+ else if (base == 2)
+ prefix = bin;
+ else
+ prefix = empty;
+
+ for (; *prefix; ++i)
+ __putchar(*prefix++);
+
+ return i;
+}
+
+/**
+ * Print padding.
+ *
+ * @param pad Number of characters to print.
+ * @param pad_char Character to use as padding.
+ * @return Number of characters printed.
+ */
+static size_t __print_padding(size_t pad, char pad_char)
+{
+ size_t i = 0;
+ for (; i < pad; ++i) {
+ __putchar(pad_char);
+ }
+
+ return i;
+}
+
+/**
+ * Print signed value.
+ *
+ * @param value Value to print.
+ * @param flags Flags to printing.
+ * @return Number of characters written.
+ */
+static size_t __print_sign(ssize_t value, size_t flags)
+{
+ if (is_set(flags, LLONG_FLAG))
+ value = (signed long long)value;
+ else if (is_set(flags, LONG_FLAG))
+ value = (signed long)value;
+ else if (is_set(flags, SHORT_FLAG))
+ value = (signed short)value;
+ else if (is_set(flags, CHAR_FLAG))
+ value = (signed char)value;
+ else
+ value = (signed int)value;
+
+ if (value < 0) {
+ __putchar('-');
+ return 1;
+ } else if (flags & SIGN_FLAG) {
+ __putchar('+');
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Length of integral value as string.
+ *
+ * @param value Value to convert to string.
+ * @param base Base to interpret value as.
+ * @param flags Formatting flags.
+ * @return \see __integral_val().
+ */
+#define __integral_len(value, base, flags) __integral_val((value), (base), \
+ (flags), false)
+
+/**
+ * Print integral value as string.
+ *
+ * @param value Value to convert to string.
+ * @param base Base to interpret value as.
+ * @param flags Formatting flags.
+ * @return \see __integral_val().
+ */
+#define __integral_print(value, base, flags) __integral_val((value), (base), \
+ (flags), true)
+
+/**
+ * Print integral value.
+ *
+ * @param value Value to print.
+ * @param base Base to print value in.
+ * @param flags Flags to printing.
+ * @param width Minimum width of printing.
+ * @return Number of characters written.
+ */
+static size_t __print_integral(ssize_t value, size_t base, size_t flags,
+ size_t width)
+{
+ size_t ret = 0;
+ size_t raw_len = __integral_len(value, base, flags);
+ ssize_t pad = is_set(flags, PAD_FLAG) ? width - raw_len : 0;
+
+ /* depending on which flags are set, the prefix, sign and right justify has to
+ * be ordereder differently. */
+ if (is_set(flags, ZERO_FLAG)) {
+ if (!is_set(flags, UNSIGN_FLAG))
+ ret += __print_sign(value, flags);
+
+ if (is_set(flags, HASH_FLAG))
+ ret += __print_prefix(base);
+
+ if (pad > 0 && !is_set(flags, LEFT_FLAG))
+ ret += __print_padding(pad, '0');
+
+ } else if (is_set(flags, SPACE_FLAG)) {
+ if (pad > 0 && !is_set(flags, LEFT_FLAG))
+ ret += __print_padding(pad, ' ');
+
+ if (!is_set(flags, UNSIGN_FLAG))
+ ret += __print_sign(value, flags);
+
+ if (is_set(flags, HASH_FLAG))
+ ret += __print_prefix(base);
+ } else {
+ if (!is_set(flags, UNSIGN_FLAG))
+ ret += __print_sign(value, flags);
+
+ if (is_set(flags, HASH_FLAG))
+ ret += __print_prefix(base);
+ }
+
+ /* print value itself */
+ ret += __integral_print(value, base, flags);
+
+ /* left-justify */
+ if (is_set(flags, ZERO_FLAG)) {
+ if (pad > 0 && is_set(flags, LEFT_FLAG))
+ ret += __print_padding(pad, '0');
+ } else if (is_set(flags, SPACE_FLAG)) {
+ if (pad > 0 && is_set(flags, LEFT_FLAG))
+ ret += __print_padding(pad, ' ');
+ }
+
+ return ret;
+}
+
+void dbg(const char *fmt, ...)
+{
+ /* largely inspired by
+ * https://github.com/mpaland/printf/blob/master/printf.c
+ */
+
+ /* Note that X is binary formatting, because who uses uppercase hex? */
+
+ va_list vl;
+ va_start(vl, fmt);
+
+ size_t chars_written = 0;
+
+ while (*fmt) {
+ if (*fmt != '%') {
+ __putchar(*fmt++);
+ chars_written++;
+ continue;
+ }
+
+ fmt++;
+ if (*fmt == '%') {
+ /* literal percent sign */
+ __putchar('%');
+ chars_written++;
+ fmt++;
+ continue;
+ }
+
+ /* check flags */
+ size_t flags = 0;
+ int a = STOP;
+ do {
+ switch (*fmt) {
+ case ' ':
+ set_bit(flags, SPACE_FLAG);
+ fmt++;
+ a = CONT;
+ break;
+
+ case '-':
+ set_bit(flags, LEFT_FLAG);
+ fmt++;
+ a = CONT;
+ break;
+
+ case '+':
+ set_bit(flags, SIGN_FLAG);
+ fmt++;
+ a = CONT;
+ break;
+
+ case '#':
+ set_bit(flags, HASH_FLAG);
+ fmt++;
+ a = CONT;
+ break;
+
+ case '0':
+ set_bit(flags, ZERO_FLAG);
+ fmt++;
+ a = CONT;
+ break;
+
+ case '\'':
+ set_bit(flags, FMT_FLAG);
+ fmt++;
+ a = CONT;
+ break;
+
+ default:
+ a = STOP;
+ break;
+ }
+ } while (a != STOP);
+
+ /* check width */
+ size_t width = 0;
+ if (__is_digit(*fmt)) {
+ width = __atoi(fmt++);
+ set_bit(flags, WIDTH_FLAG | PAD_FLAG | SPACE_FLAG);
+ } else if (*fmt == '*') {
+ int w = va_arg(vl, int);
+ if (w < 0) {
+ width = -w;
+ set_bit(flags, LEFT_FLAG);
+ } else {
+ width = w;
+ }
+ set_bit(flags, WIDTH_FLAG | PAD_FLAG | SPACE_FLAG);
+ fmt++;
+ }
+
+ /* check precision */
+ size_t precision = 0;
+ if (*fmt == '.') {
+ fmt++;
+ set_bit(flags, PRECS_FLAG | PAD_FLAG | ZERO_FLAG);
+ if (__is_digit(*fmt)) {
+ precision = __atoi(fmt++);
+ } else if (*fmt == '*') {
+ precision = va_arg(vl, int);
+ fmt++;
+ }
+ }
+
+ /* check length */
+ switch (*fmt) {
+ case 'l':
+ fmt++;
+ if (*fmt == 'l') {
+ set_bit(flags, LLONG_FLAG);
+ fmt++;
+ } else {
+ set_bit(flags, LONG_FLAG);
+ }
+ break;
+
+ case 'h':
+ fmt++;
+ if (*fmt == 'h') {
+ set_bit(flags, CHAR_FLAG);
+ fmt++;
+ } else {
+ set_bit(flags, SHORT_FLAG);
+ }
+ break;
+
+ case 'j':
+ fmt++;
+ if (sizeof(intmax_t) == sizeof(long))
+ set_bit(flags, LONG_FLAG);
+ else
+ set_bit(flags, LLONG_FLAG);
+ break;
+
+ case 'z':
+ fmt++;
+ if (sizeof(size_t) == sizeof(long))
+ set_bit(flags, LONG_FLAG);
+ else
+ set_bit(flags, LLONG_FLAG);
+ break;
+
+ case 't':
+ fmt++;
+ if (sizeof(ptrdiff_t) == sizeof(long))
+ set_bit(flags, LONG_FLAG);
+ else
+ set_bit(flags, LLONG_FLAG);
+ break;
+ }
+
+ /* read actual specifier */
+ size_t base = 10;
+ size_t value = 0;
+ int i = -1;
+ const char *s = 0;
+ void *p = 0;
+ int *n = 0;
+ char c = 0;
+
+ switch (*fmt) {
+ case 'd':
+ case 'i':
+ case 'u':
+ case 'x':
+ case 'X':
+ case 'o':
+ case 'b':
+ /* integer handling */
+ switch (*fmt) {
+ case 'x':
+ base = 16;
+ break;
+ case 'X':
+ base = 2;
+ break;
+ case 'o':
+ base = 8;
+ break;
+ default:
+ base = 10;
+ break;
+ }
+
+ if (base == 10)
+ clear_bit(flags, HASH_FLAG);
+
+ /* precision takes precedence */
+ if (is_set(flags, PRECS_FLAG))
+ width = precision;
+
+ /* formatting doesn't apply to decimal integers
+ * */
+ if (*fmt != 'i' && *fmt != 'd') {
+ clear_bit(flags, SIGN_FLAG);
+ set_bit(flags, UNSIGN_FLAG);
+ }
+
+ if (is_set(flags, LLONG_FLAG))
+ value = va_arg(vl, long long);
+ else if (is_set(flags, LONG_FLAG))
+ value = va_arg(vl, long);
+ else
+ value = va_arg(vl, int);
+
+ chars_written +=
+ __print_integral(value, base, flags, width);
+ fmt++;
+ break;
+
+ case 'c':
+ c = va_arg(vl, int);
+ __putchar(c);
+ chars_written++;
+ fmt++;
+ break;
+
+ case 's':
+ s = va_arg(vl, const char *);
+
+ if (is_set(flags, PRECS_FLAG))
+ i = precision;
+
+ for (; *s && i--;) {
+ __putchar(*s++);
+ chars_written++;
+ }
+ fmt++;
+ break;
+
+ case 'p':
+ p = va_arg(vl, void *);
+ set_bit(flags, UNSIGN_FLAG | HASH_FLAG);
+
+ if (sizeof(void *) == sizeof(long))
+ set_bit(flags, LONG_FLAG);
+ else
+ set_bit(flags, LLONG_FLAG);
+
+ chars_written +=
+ __print_integral((ssize_t)p, 16, flags, width);
+ fmt++;
+ break;
+
+ case 'n':
+ n = va_arg(vl, int *);
+ *n = chars_written;
+ fmt++;
+ break;
+ }
+ }
+
+ va_end(vl);
+}
+
+#endif /* DEBUG */