:: commit fb99d3b0abbbcad177adcd068681d86453b1414d

mintsuki <mintsuki@protonmail.com> — 2022-01-25 07:41

parents: a98ba038d4

bios: Move terminal entirely to stage 3

diff --git a/stage23/drivers/vga_textmode.s2.c b/stage23/drivers/vga_textmode.c
similarity index 99%
rename from stage23/drivers/vga_textmode.s2.c
rename to stage23/drivers/vga_textmode.c
index 1982f11b..0f9bbe8c 100644
--- a/stage23/drivers/vga_textmode.s2.c
+++ b/stage23/drivers/vga_textmode.c
@@ -15,7 +15,7 @@
 #define VD_COLS (80 * 2)
 #define VD_ROWS 25
 
-static uint8_t *video_mem = (uint8_t *)0xb8000;
+static volatile uint8_t *video_mem = (uint8_t *)0xb8000;
 
 static uint8_t *back_buffer = NULL;
 static uint8_t *front_buffer = NULL;
diff --git a/stage23/entry.s2.c b/stage23/entry.s2.c
index 7a1a13b9..46c1dfbf 100644
--- a/stage23/entry.s2.c
+++ b/stage23/entry.s2.c
@@ -49,7 +49,6 @@ static bool stage3_init(struct volume *part) {
     stage3_found = true;
 
     if (stage3->size != (size_t)limine_sys_size) {
-        term_textmode();
         print("limine.sys size incorrect.\n");
         return false;
     }
@@ -61,7 +60,6 @@ static bool stage3_init(struct volume *part) {
     fclose(stage3);
 
     if (memcmp(build_id_s2 + 16, build_id_s3 + 16, 20) != 0) {
-        term_textmode();
         print("limine.sys build ID mismatch.\n");
         return false;
     }
@@ -82,8 +80,6 @@ noreturn void entry(uint8_t boot_drive, int boot_from) {
     if (!a20_enable())
         panic(false, "Could not enable A20 line");
 
-    term_notready();
-
     struct rm_regs r = {0};
     r.eax = 0x0003;
     rm_int(0x10, &r, &r);
@@ -112,7 +108,6 @@ noreturn void entry(uint8_t boot_drive, int boot_from) {
     );
 
     if (!stage3_found) {
-        term_textmode();
         print("\n"
               "!! Stage 3 file not found!\n"
               "!! Have you copied limine.sys to the root or /boot directories of\n"
diff --git a/stage23/entry.s3.c b/stage23/entry.s3.c
index 875d6266..ce630cfd 100644
--- a/stage23/entry.s3.c
+++ b/stage23/entry.s3.c
@@ -127,6 +127,8 @@ noreturn void uefi_entry(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
 #endif
 
 noreturn void stage3_common(void) {
+    term_notready();
+
     init_flush_irqs();
     init_io_apics();
 
diff --git a/stage23/lib/panic.s2.c b/stage23/lib/panic.s2.c
index 70c2ebdf..71cf3d43 100644
--- a/stage23/lib/panic.s2.c
+++ b/stage23/lib/panic.s2.c
@@ -21,7 +21,11 @@ noreturn void panic(bool allow_menu, const char *fmt, ...) {
 
     quiet = false;
 
-    if (term_backend == NOT_READY) {
+    if (
+#if bios == 1
+      stage3_loaded == true &&
+#endif
+      term_backend == NOT_READY) {
 #if bios == 1
         term_textmode();
 #elif uefi == 1
@@ -29,11 +33,19 @@ noreturn void panic(bool allow_menu, const char *fmt, ...) {
 #endif
     }
 
-    if (term_backend == NOT_READY) {
+    if (
+#if bios == 1
+      stage3_loaded == true &&
+#endif
+      term_backend == NOT_READY) {
         term_fallback();
     }
 
-    print("\033[31mPANIC\033[37;1m\033[0m: ");
+    if (stage3_loaded) {
+        print("\033[31mPANIC\033[37;1m\033[0m: ");
+    } else {
+        print("PANIC: ");
+    }
     vprint(fmt, args);
 
     va_end(args);
diff --git a/stage23/lib/print.s2.c b/stage23/lib/print.s2.c
index ca314e4a..4b0d5dc1 100644
--- a/stage23/lib/print.s2.c
+++ b/stage23/lib/print.s2.c
@@ -5,8 +5,34 @@
 #include <lib/blib.h>
 #include <lib/term.h>
 #include <lib/libc.h>
+#if bios == 1
+#include <lib/real.h>
+#endif
 #include <sys/cpu.h>
 
+#if bios == 1
+static void s2_print(const char *s, size_t len) {
+    for (size_t i = 0; i < len; i++) {
+        struct rm_regs r = {0};
+        char c = s[i];
+
+        switch (c) {
+            case '\n':
+                r.eax = 0x0e00 | '\r';
+                rm_int(0x10, &r, &r);
+                r = (struct rm_regs){0};
+                r.eax = 0x0e00 | '\n';
+                rm_int(0x10, &r, &r);
+                break;
+            default:
+                r.eax = 0x0e00 | s[i];
+                rm_int(0x10, &r, &r);
+                break;
+        }
+    }
+}
+#endif
+
 static const char *base_digits = "0123456789abcdef";
 
 #define PRINT_BUF_MAX 4096
@@ -187,7 +213,15 @@ void vprint(const char *fmt, va_list args) {
     }
 
 out:
-    term_write((uint64_t)(uintptr_t)print_buf, print_buf_i);
+#if bios == 1
+    if (stage3_loaded) {
+#endif
+        term_write((uint64_t)(uintptr_t)print_buf, print_buf_i);
+#if bios == 1
+    } else {
+        s2_print(print_buf, print_buf_i);
+    }
+#endif
 
     for (size_t i = 0; i < print_buf_i; i++) {
         if (E9_OUTPUT) {
diff --git a/stage23/lib/term.c b/stage23/lib/term.c
index 91e612ee..a3fe07f0 100644
--- a/stage23/lib/term.c
+++ b/stage23/lib/term.c
@@ -2,9 +2,12 @@
 #include <stddef.h>
 #include <stdbool.h>
 #include <lib/term.h>
+#include <lib/real.h>
 #include <lib/image.h>
 #include <lib/blib.h>
 #include <lib/gterm.h>
+#include <drivers/vga_textmode.h>
+#include <lib/print.h>
 #include <mm/pmm.h>
 
 bool early_term = false;
@@ -65,3 +68,1137 @@ void term_vbe(size_t width, size_t height) {
 
     term_backend = VBE;
 }
+
+// Tries to implement this standard for terminfo
+// https://man7.org/linux/man-pages/man4/console_codes.4.html
+
+no_unwind int current_video_mode = -1;
+int term_backend = NOT_READY;
+size_t term_rows, term_cols;
+bool term_runtime = false;
+
+static void notready_raw_putchar(uint8_t c) {
+    (void)c;
+}
+static void notready_clear(bool move) {
+    (void)move;
+}
+static void notready_void(void) {}
+static void notready_set_cursor_pos(size_t x, size_t y) {
+    (void)x; (void)y;
+}
+static void notready_get_cursor_pos(size_t *x, size_t *y) {
+    *x = 0;
+    *y = 0;
+}
+static void notready_size_t(size_t n) {
+    (void)n;
+}
+static bool notready_disable(void) {
+    return false;
+}
+static void notready_move_character(size_t a, size_t b, size_t c, size_t d) {
+    (void)a; (void)b; (void)c; (void)d;
+}
+static uint64_t notready_context_size(void) {
+    return 0;
+}
+static void notready_uint64_t(uint64_t n) {
+    (void)n;
+}
+
+void term_notready(void) {
+    term_backend = NOT_READY;
+
+    raw_putchar = notready_raw_putchar;
+    clear = notready_clear;
+    enable_cursor = notready_void;
+    disable_cursor = notready_disable;
+    set_cursor_pos = notready_set_cursor_pos;
+    get_cursor_pos = notready_get_cursor_pos;
+    set_text_fg = notready_size_t;
+    set_text_bg = notready_size_t;
+    set_text_fg_bright = notready_size_t;
+    set_text_bg_bright = notready_size_t;
+    set_text_fg_default = notready_void;
+    set_text_bg_default = notready_void;
+    scroll_disable = notready_disable;
+    scroll_enable = notready_void;
+    term_move_character = notready_move_character;
+    term_scroll = notready_void;
+    term_revscroll = notready_void;
+    term_swap_palette = notready_void;
+    term_save_state = notready_void;
+    term_restore_state = notready_void;
+    term_double_buffer_flush = notready_void;
+    term_context_size = notready_context_size;
+    term_context_save = notready_uint64_t;
+    term_context_restore = notready_uint64_t;
+    term_full_refresh = notready_void;
+
+    term_rows = 100;
+    term_cols = 100;
+}
+
+#if bios == 1
+void fallback_raw_putchar(uint8_t c) {
+    struct rm_regs r = {0};
+    r.eax = 0x0e00 | c;
+    rm_int(0x10, &r, &r);
+}
+
+void fallback_clear(bool move) {
+    (void)move;
+    struct rm_regs r = {0};
+    rm_int(0x11, &r, &r);
+    switch ((r.eax >> 4) & 3) {
+        case 0:
+            r.eax = 3;
+            break;
+        case 1:
+            r.eax = 1;
+            break;
+        case 2:
+            r.eax = 3;
+            break;
+        case 3:
+            r.eax = 7;
+            break;
+    }
+    rm_int(0x10, &r, &r);
+}
+
+void fallback_set_cursor_pos(size_t x, size_t y) {
+    struct rm_regs r = {0};
+    r.eax = 0x0200;
+    r.ebx = 0;
+    r.edx = (y << 8) + x;
+    rm_int(0x10, &r, &r);
+}
+
+void fallback_get_cursor_pos(size_t *x, size_t *y) {
+    struct rm_regs r = {0};
+    r.eax = 0x0300;
+    r.ebx = 0;
+    rm_int(0x10, &r, &r);
+    *x = r.edx & 0xff;
+    *y = r.edx >> 8;
+}
+
+#elif uefi == 1
+static int cursor_x = 0, cursor_y = 0;
+
+void fallback_raw_putchar(uint8_t c) {
+    CHAR16 string[2];
+    string[0] = c;
+    string[1] = 0;
+    gST->ConOut->OutputString(gST->ConOut, string);
+    switch (c) {
+        case 0x08:
+            if (cursor_x > 0)
+                cursor_x--;
+            break;
+        case 0x0A:
+            cursor_x = 0;
+            break;
+        case 0x0D:
+            if (cursor_y < 24)
+                cursor_y++;
+            break;
+        default:
+            if (++cursor_x > 80) {
+                cursor_x = 0;
+                if (cursor_y < 24)
+                    cursor_y++;
+            }
+    }
+}
+
+void fallback_clear(bool move) {
+    (void)move;
+    gST->ConOut->ClearScreen(gST->ConOut);
+    cursor_x = cursor_y = 0;
+}
+
+void fallback_set_cursor_pos(size_t x, size_t y) {
+    if (x >= 80 || y >= 25)
+        return;
+    gST->ConOut->SetCursorPosition(gST->ConOut, x, y);
+    cursor_x = x;
+    cursor_y = y;
+}
+
+void fallback_get_cursor_pos(size_t *x, size_t *y) {
+    *x = cursor_x;
+    *y = cursor_y;
+}
+#endif
+
+void term_fallback(void) {
+#if uefi == 1
+    if (!efi_boot_services_exited) {
+        gST->ConOut->Reset(gST->ConOut, false);
+        gST->ConOut->SetMode(gST->ConOut, 0);
+        cursor_x = cursor_y = 0;
+#elif bios == 1
+        fallback_clear(true);
+#endif
+        term_notready();
+        raw_putchar = fallback_raw_putchar;
+        clear = fallback_clear;
+        set_cursor_pos = fallback_set_cursor_pos;
+        get_cursor_pos = fallback_get_cursor_pos;
+        term_backend = FALLBACK;
+#if uefi == 1
+    }
+#endif
+}
+
+void (*raw_putchar)(uint8_t c);
+void (*clear)(bool move);
+void (*enable_cursor)(void);
+bool (*disable_cursor)(void);
+void (*set_cursor_pos)(size_t x, size_t y);
+void (*get_cursor_pos)(size_t *x, size_t *y);
+void (*set_text_fg)(size_t fg);
+void (*set_text_bg)(size_t bg);
+void (*set_text_fg_bright)(size_t fg);
+void (*set_text_bg_bright)(size_t bg);
+void (*set_text_fg_default)(void);
+void (*set_text_bg_default)(void);
+bool (*scroll_disable)(void);
+void (*scroll_enable)(void);
+void (*term_move_character)(size_t new_x, size_t new_y, size_t old_x, size_t old_y);
+void (*term_scroll)(void);
+void (*term_revscroll)(void);
+void (*term_swap_palette)(void);
+void (*term_save_state)(void);
+void (*term_restore_state)(void);
+
+void (*term_double_buffer_flush)(void);
+
+uint64_t (*term_context_size)(void);
+void (*term_context_save)(uint64_t ptr);
+void (*term_context_restore)(uint64_t ptr);
+void (*term_full_refresh)(void);
+
+void (*term_callback)(uint64_t, uint64_t, uint64_t, uint64_t) = NULL;
+
+struct term_context term_context;
+
+#define escape_offset term_context.escape_offset
+#define control_sequence term_context.control_sequence
+#define csi term_context.csi
+#define escape term_context.escape
+#define rrr term_context.rrr
+#define discard_next term_context.discard_next
+#define bold term_context.bold
+#define reverse_video term_context.reverse_video
+#define dec_private term_context.dec_private
+#define esc_values term_context.esc_values
+#define esc_values_i term_context.esc_values_i
+#define saved_cursor_x term_context.saved_cursor_x
+#define saved_cursor_y term_context.saved_cursor_y
+#define current_primary term_context.current_primary
+#define insert_mode term_context.insert_mode
+#define scroll_top_margin term_context.scroll_top_margin
+#define scroll_bottom_margin term_context.scroll_bottom_margin
+#define current_charset term_context.current_charset
+#define charsets term_context.charsets
+#define g_select term_context.g_select
+
+#define saved_state_bold term_context.saved_state_bold
+#define saved_state_reverse_video term_context.saved_state_reverse_video
+#define saved_state_current_charset term_context.saved_state_current_charset
+#define saved_state_current_primary term_context.saved_state_current_primary
+
+#define CHARSET_DEFAULT 0
+#define CHARSET_DEC_SPECIAL 1
+
+void term_reinit(void) {
+    escape_offset = 0;
+    control_sequence = false;
+    csi = false;
+    escape = false;
+    rrr = false;
+    discard_next = false;
+    bold = false;
+    reverse_video = false;
+    dec_private = false;
+    esc_values_i = 0;
+    saved_cursor_x = 0;
+    saved_cursor_y = 0;
+    current_primary = (size_t)-1;
+    insert_mode = false;
+    scroll_top_margin = 0;
+    scroll_bottom_margin = term_rows;
+    current_charset = 0;
+    g_select = 0;
+    charsets[0] = CHARSET_DEFAULT;
+    charsets[1] = CHARSET_DEC_SPECIAL;
+    term_autoflush = true;
+}
+
+#if bios == 1
+void term_textmode(void) {
+    term_notready();
+
+    if (quiet || allocations_disallowed) {
+        return;
+    }
+
+    init_vga_textmode(&term_rows, &term_cols, true);
+
+    term_reinit();
+
+    raw_putchar    = text_putchar;
+    clear          = text_clear;
+    enable_cursor  = text_enable_cursor;
+    disable_cursor = text_disable_cursor;
+    set_cursor_pos = text_set_cursor_pos;
+    get_cursor_pos = text_get_cursor_pos;
+    set_text_fg    = text_set_text_fg;
+    set_text_bg    = text_set_text_bg;
+    set_text_fg_bright = text_set_text_fg_bright;
+    set_text_bg_bright = text_set_text_bg_bright;
+    set_text_fg_default = text_set_text_fg_default;
+    set_text_bg_default = text_set_text_bg_default;
+    scroll_disable = text_scroll_disable;
+    scroll_enable  = text_scroll_enable;
+    term_move_character = text_move_character;
+    term_scroll = text_scroll;
+    term_revscroll = text_revscroll;
+    term_swap_palette = text_swap_palette;
+    term_save_state = text_save_state;
+    term_restore_state = text_restore_state;
+
+    term_double_buffer_flush = text_double_buffer_flush;
+
+    term_context_size = text_context_size;
+    term_context_save = text_context_save;
+    term_context_restore = text_context_restore;
+    term_full_refresh = text_full_refresh;
+
+    term_backend = TEXTMODE;
+}
+#endif
+
+static uint64_t context_size(void) {
+    uint64_t ret = 0;
+
+    ret += sizeof(struct term_context);
+    ret += term_context_size();
+
+    return ret;
+}
+
+static void context_save(uint64_t ptr) {
+    memcpy32to64(ptr, (uint64_t)(uintptr_t)&term_context, sizeof(struct term_context));
+    ptr += sizeof(struct term_context);
+
+    term_context_save(ptr);
+}
+
+static void context_restore(uint64_t ptr) {
+    memcpy32to64((uint64_t)(uintptr_t)&term_context, ptr, sizeof(struct term_context));
+    ptr += sizeof(struct term_context);
+
+    term_context_restore(ptr);
+}
+
+#if defined (__i386__)
+#define TERM_XFER_CHUNK 8192
+
+static uint8_t xfer_buf[TERM_XFER_CHUNK];
+#endif
+
+bool term_autoflush = true;
+
+void term_write(uint64_t buf, uint64_t count) {
+    if (term_backend == NOT_READY)
+        return;
+
+    switch (count) {
+        case TERM_CTX_SIZE: {
+            uint64_t ret = context_size();
+            memcpy32to64(buf, (uint64_t)(uintptr_t)&ret, sizeof(uint64_t));
+            return;
+        }
+        case TERM_CTX_SAVE: {
+            context_save(buf);
+            return;
+        }
+        case TERM_CTX_RESTORE: {
+            context_restore(buf);
+            return;
+        }
+        case TERM_FULL_REFRESH: {
+            term_full_refresh();
+            return;
+        }
+    }
+
+    bool native = false;
+#if defined (__x86_64__)
+    native = true;
+#endif
+
+    if (!term_runtime || native) {
+        const char *s = (const char *)(uintptr_t)buf;
+
+        for (size_t i = 0; i < count; i++)
+            term_putchar(s[i]);
+    } else {
+#if defined (__i386__)
+        while (count != 0) {
+            uint64_t chunk;
+            if (count > TERM_XFER_CHUNK) {
+                chunk = TERM_XFER_CHUNK;
+            } else {
+                chunk = count;
+            }
+
+            memcpy32to64((uint64_t)(uintptr_t)xfer_buf, buf, chunk);
+
+            for (size_t i = 0; i < chunk; i++)
+                term_putchar(xfer_buf[i]);
+
+            count -= chunk;
+            buf += chunk;
+        }
+#endif
+    }
+
+    if (term_autoflush) {
+        term_double_buffer_flush();
+    }
+}
+
+static void sgr(void) {
+    size_t i = 0;
+
+    if (!esc_values_i)
+        goto def;
+
+    for (; i < esc_values_i; i++) {
+        size_t offset;
+
+        if (esc_values[i] == 0) {
+def:
+            if (reverse_video) {
+                reverse_video = false;
+                term_swap_palette();
+            }
+            bold = false;
+            current_primary = (size_t)-1;
+            set_text_bg_default();
+            set_text_fg_default();
+            continue;
+        }
+
+        else if (esc_values[i] == 1) {
+            bold = true;
+            if (current_primary != (size_t)-1) {
+                if (!reverse_video) {
+                    set_text_fg_bright(current_primary);
+                } else {
+                    set_text_bg_bright(current_primary);
+                }
+            }
+            continue;
+        }
+
+        else if (esc_values[i] == 22) {
+            bold = false;
+            if (current_primary != (size_t)-1) {
+                if (!reverse_video) {
+                    set_text_fg(current_primary);
+                } else {
+                    set_text_bg(current_primary);
+                }
+            }
+            continue;
+        }
+
+        else if (esc_values[i] >= 30 && esc_values[i] <= 37) {
+            offset = 30;
+            current_primary = esc_values[i] - offset;
+
+            if (reverse_video) {
+                goto set_bg;
+            }
+
+set_fg:
+            if (bold && !reverse_video) {
+                set_text_fg_bright(esc_values[i] - offset);
+            } else {
+                set_text_fg(esc_values[i] - offset);
+            }
+            continue;
+        }
+
+        else if (esc_values[i] >= 40 && esc_values[i] <= 47) {
+            offset = 40;
+            if (reverse_video) {
+                goto set_fg;
+            }
+
+set_bg:
+            if (bold && reverse_video) {
+                set_text_bg_bright(esc_values[i] - offset);
+            } else {
+                set_text_bg(esc_values[i] - offset);
+            }
+            continue;
+        }
+
+        else if (esc_values[i] >= 90 && esc_values[i] <= 97) {
+            offset = 90;
+            current_primary = esc_values[i] - offset;
+
+            if (reverse_video) {
+                goto set_bg_bright;
+            }
+
+set_fg_bright:
+            set_text_fg_bright(esc_values[i] - offset);
+            continue;
+        }
+
+        else if (esc_values[i] >= 100 && esc_values[i] <= 107) {
+            offset = 100;
+            if (reverse_video) {
+                goto set_fg_bright;
+            }
+
+set_bg_bright:
+            set_text_bg_bright(esc_values[i] - offset);
+            continue;
+        }
+
+        else if (esc_values[i] == 39) {
+            current_primary = (size_t)-1;
+
+            if (reverse_video) {
+                term_swap_palette();
+            }
+
+            set_text_fg_default();
+
+            if (reverse_video) {
+                term_swap_palette();
+            }
+
+            continue;
+        }
+
+        else if (esc_values[i] == 49) {
+            if (reverse_video) {
+                term_swap_palette();
+            }
+
+            set_text_bg_default();
+
+            if (reverse_video) {
+                term_swap_palette();
+            }
+
+            continue;
+        }
+
+        else if (esc_values[i] == 7) {
+            if (!reverse_video) {
+                reverse_video = true;
+                term_swap_palette();
+            }
+            continue;
+        }
+
+        else if (esc_values[i] == 27) {
+            if (reverse_video) {
+                reverse_video = false;
+                term_swap_palette();
+            }
+            continue;
+        }
+    }
+}
+
+static void dec_private_parse(uint8_t c) {
+    dec_private = false;
+
+    if (esc_values_i == 0) {
+        return;
+    }
+
+    bool set;
+
+    switch (c) {
+        case 'h':
+            set = true; break;
+        case 'l':
+            set = false; break;
+        default:
+            return;
+    }
+
+    switch (esc_values[0]) {
+        case 25: {
+            if (set) {
+                enable_cursor();
+            } else {
+                disable_cursor();
+            }
+            return;
+        }
+    }
+
+    if (term_callback != NULL) {
+        term_callback(TERM_CB_DEC, esc_values_i, (uintptr_t)esc_values, c);
+    }
+}
+
+static void linux_private_parse(void) {
+    if (esc_values_i == 0) {
+        return;
+    }
+
+    if (term_callback != NULL) {
+        term_callback(TERM_CB_LINUX, esc_values_i, (uintptr_t)esc_values, 0);
+    }
+}
+
+static void mode_toggle(uint8_t c) {
+    if (esc_values_i == 0) {
+        return;
+    }
+
+    bool set;
+
+    switch (c) {
+        case 'h':
+            set = true; break;
+        case 'l':
+            set = false; break;
+        default:
+            return;
+    }
+
+    switch (esc_values[0]) {
+        case 4:
+            insert_mode = set; return;
+    }
+
+    if (term_callback != NULL) {
+        term_callback(TERM_CB_MODE, esc_values_i, (uintptr_t)esc_values, c);
+    }
+}
+
+static void control_sequence_parse(uint8_t c) {
+    if (escape_offset == 2) {
+        switch (c) {
+            case '[':
+                discard_next = true;
+                goto cleanup;
+            case '?':
+                dec_private = true;
+                return;
+        }
+    }
+
+    if (c >= '0' && c <= '9') {
+        if (esc_values_i == MAX_ESC_VALUES) {
+            return;
+        }
+        rrr = true;
+        esc_values[esc_values_i] *= 10;
+        esc_values[esc_values_i] += c - '0';
+        return;
+    }
+
+    if (rrr == true) {
+        esc_values_i++;
+        rrr = false;
+        if (c == ';')
+            return;
+    } else if (c == ';') {
+        if (esc_values_i == MAX_ESC_VALUES) {
+            return;
+        }
+        esc_values[esc_values_i] = 0;
+        esc_values_i++;
+        return;
+    }
+
+    size_t esc_default;
+    switch (c) {
+        case 'J': case 'K': case 'q':
+            esc_default = 0; break;
+        default:
+            esc_default = 1; break;
+    }
+
+    for (size_t i = esc_values_i; i < MAX_ESC_VALUES; i++) {
+        esc_values[i] = esc_default;
+    }
+
+    if (dec_private == true) {
+        dec_private_parse(c);
+        goto cleanup;
+    }
+
+    bool r = scroll_disable();
+    size_t x, y;
+    get_cursor_pos(&x, &y);
+
+    switch (c) {
+        case 'F':
+            x = 0;
+            // FALLTHRU
+        case 'A': {
+            if (esc_values[0] > y)
+                esc_values[0] = y;
+            size_t orig_y = y;
+            size_t dest_y = y - esc_values[0];
+            bool will_be_in_scroll_region = false;
+            if ((scroll_top_margin >= dest_y && scroll_top_margin <= orig_y)
+             || (scroll_bottom_margin >= dest_y && scroll_bottom_margin <= orig_y)) {
+                will_be_in_scroll_region = true;
+            }
+            if (will_be_in_scroll_region && dest_y < scroll_top_margin) {
+                dest_y = scroll_top_margin;
+            }
+            set_cursor_pos(x, dest_y);
+            break;
+        }
+        case 'E':
+            x = 0;
+            // FALLTHRU
+        case 'e':
+        case 'B': {
+            if (y + esc_values[0] > term_rows - 1)
+                esc_values[0] = (term_rows - 1) - y;
+            size_t orig_y = y;
+            size_t dest_y = y + esc_values[0];
+            bool will_be_in_scroll_region = false;
+            if ((scroll_top_margin >= orig_y && scroll_top_margin <= dest_y)
+             || (scroll_bottom_margin >= orig_y && scroll_bottom_margin <= dest_y)) {
+                will_be_in_scroll_region = true;
+            }
+            if (will_be_in_scroll_region && dest_y >= scroll_bottom_margin) {
+                dest_y = scroll_bottom_margin - 1;
+            }
+            set_cursor_pos(x, dest_y);
+            break;
+        }
+        case 'a':
+        case 'C':
+            if (x + esc_values[0] > term_cols - 1)
+                esc_values[0] = (term_cols - 1) - x;
+            set_cursor_pos(x + esc_values[0], y);
+            break;
+        case 'D':
+            if (esc_values[0] > x)
+                esc_values[0] = x;
+            set_cursor_pos(x - esc_values[0], y);
+            break;
+        case 'c':
+            if (term_callback != NULL) {
+                term_callback(TERM_CB_PRIVATE_ID, 0, 0, 0);
+            }
+            break;
+        case 'd':
+            esc_values[0] -= 1;
+            if (esc_values[0] >= term_rows)
+                esc_values[0] = term_rows - 1;
+            set_cursor_pos(x, esc_values[0]);
+            break;
+        case 'G':
+        case '`':
+            esc_values[0] -= 1;
+            if (esc_values[0] >= term_cols)
+                esc_values[0] = term_cols - 1;
+            set_cursor_pos(esc_values[0], y);
+            break;
+        case 'H':
+        case 'f':
+            esc_values[0] -= 1;
+            esc_values[1] -= 1;
+            if (esc_values[1] >= term_cols)
+                esc_values[1] = term_cols - 1;
+            if (esc_values[0] >= term_rows)
+                esc_values[0] = term_rows - 1;
+            set_cursor_pos(esc_values[1], esc_values[0]);
+            break;
+        case 'n':
+            switch (esc_values[0]) {
+                case 5:
+                    if (term_callback != NULL) {
+                        term_callback(TERM_CB_STATUS_REPORT, 0, 0, 0);
+                    }
+                    break;
+                case 6:
+                    if (term_callback != NULL) {
+                        term_callback(TERM_CB_POS_REPORT, x + 1, y + 1, 0);
+                    }
+                    break;
+            }
+            break;
+        case 'q':
+            if (term_callback != NULL) {
+                term_callback(TERM_CB_KBD_LEDS, esc_values[0], 0, 0);
+            }
+            break;
+        case 'J':
+            switch (esc_values[0]) {
+                case 0: {
+                    size_t rows_remaining = term_rows - (y + 1);
+                    size_t cols_diff = term_cols - (x + 1);
+                    size_t to_clear = rows_remaining * term_cols + cols_diff;
+                    for (size_t i = 0; i < to_clear; i++) {
+                        raw_putchar(' ');
+                    }
+                    set_cursor_pos(x, y);
+                    break;
+                }
+                case 1: {
+                    set_cursor_pos(0, 0);
+                    bool b = false;
+                    for (size_t yc = 0; yc < term_rows; yc++) {
+                        for (size_t xc = 0; xc < term_cols; xc++) {
+                            raw_putchar(' ');
+                            if (xc == x && yc == y) {
+                                set_cursor_pos(x, y);
+                                b = true;
+                                break;
+                            }
+                        }
+                        if (b == true)
+                            break;
+                    }
+                    break;
+                }
+                case 2:
+                case 3:
+                    clear(false);
+                    break;
+            }
+            break;
+        case '@':
+            for (size_t i = term_cols - 1; ; i--) {
+                term_move_character(i + esc_values[0], y, i, y);
+                set_cursor_pos(i, y);
+                raw_putchar(' ');
+                if (i == x) {
+                    break;
+                }
+            }
+            set_cursor_pos(x, y);
+            break;
+        case 'P':
+            for (size_t i = x + esc_values[0]; i < term_cols; i++)
+                term_move_character(i - esc_values[0], y, i, y);
+            set_cursor_pos(term_cols - esc_values[0], y);
+            // FALLTHRU
+        case 'X':
+            for (size_t i = 0; i < esc_values[0]; i++)
+                raw_putchar(' ');
+            set_cursor_pos(x, y);
+            break;
+        case 'm':
+            sgr();
+            break;
+        case 's':
+            get_cursor_pos(&saved_cursor_x, &saved_cursor_y);
+            break;
+        case 'u':
+            set_cursor_pos(saved_cursor_x, saved_cursor_y);
+            break;
+        case 'K':
+            switch (esc_values[0]) {
+                case 0: {
+                    for (size_t i = x; i < term_cols; i++)
+                        raw_putchar(' ');
+                    set_cursor_pos(x, y);
+                    break;
+                }
+                case 1: {
+                    set_cursor_pos(0, y);
+                    for (size_t i = 0; i < x; i++)
+                        raw_putchar(' ');
+                    break;
+                }
+                case 2: {
+                    set_cursor_pos(0, y);
+                    for (size_t i = 0; i < term_cols; i++)
+                        raw_putchar(' ');
+                    set_cursor_pos(x, y);
+                    break;
+                }
+            }
+            break;
+        case 'r':
+            scroll_top_margin = 0;
+            scroll_bottom_margin = term_rows;
+            if (esc_values_i > 0) {
+                scroll_top_margin = esc_values[0] - 1;
+            }
+            if (esc_values_i > 1) {
+                scroll_bottom_margin = esc_values[1];
+            }
+            if (scroll_top_margin >= term_rows
+             || scroll_bottom_margin > term_rows
+             || scroll_top_margin >= (scroll_bottom_margin - 1)) {
+                scroll_top_margin = 0;
+                scroll_bottom_margin = term_rows;
+            }
+            set_cursor_pos(0, 0);
+            break;
+        case 'l':
+        case 'h':
+            mode_toggle(c);
+            break;
+        case ']':
+            linux_private_parse();
+            break;
+    }
+
+    if (r)
+        scroll_enable();
+
+cleanup:
+    control_sequence = false;
+    escape = false;
+}
+
+static void restore_state(void) {
+    bold = saved_state_bold;
+    reverse_video = saved_state_reverse_video;
+    current_charset = saved_state_current_charset;
+    current_primary = saved_state_current_primary;
+
+    term_restore_state();
+}
+
+static void save_state(void) {
+    term_save_state();
+
+    saved_state_bold = bold;
+    saved_state_reverse_video = reverse_video;
+    saved_state_current_charset = current_charset;
+    saved_state_current_primary = current_primary;
+}
+
+static void escape_parse(uint8_t c) {
+    escape_offset++;
+
+    if (control_sequence == true) {
+        control_sequence_parse(c);
+        return;
+    }
+
+    if (csi == true) {
+        csi = false;
+        goto is_csi;
+    }
+
+    size_t x, y;
+    get_cursor_pos(&x, &y);
+
+    switch (c) {
+        case '[':
+is_csi:
+            for (size_t i = 0; i < MAX_ESC_VALUES; i++)
+                esc_values[i] = 0;
+            esc_values_i = 0;
+            rrr = false;
+            control_sequence = true;
+            return;
+        case '7':
+            save_state();
+            break;
+        case '8':
+            restore_state();
+            break;
+        case 'c':
+            term_reinit();
+            clear(true);
+            break;
+        case 'D':
+            if (y == scroll_bottom_margin - 1) {
+                term_scroll();
+                set_cursor_pos(x, y);
+            } else {
+                set_cursor_pos(x, y + 1);
+            }
+            break;
+        case 'E':
+            if (y == scroll_bottom_margin - 1) {
+                term_scroll();
+                set_cursor_pos(0, y);
+            } else {
+                set_cursor_pos(0, y + 1);
+            }
+            break;
+        case 'M':
+            // "Reverse linefeed"
+            if (y == scroll_top_margin) {
+                term_revscroll();
+                set_cursor_pos(0, y);
+            } else {
+                set_cursor_pos(0, y - 1);
+            }
+            break;
+        case 'Z':
+            if (term_callback != NULL) {
+                term_callback(TERM_CB_PRIVATE_ID, 0, 0, 0);
+            }
+            break;
+        case '(':
+        case ')':
+            g_select = c - '\'';
+            break;
+        case '\e':
+            if (term_runtime == false) {
+                raw_putchar(c);
+            }
+            break;
+    }
+
+    escape = false;
+}
+
+static uint8_t dec_special_to_cp437(uint8_t c) {
+    switch (c) {
+        case '`': return 0x04;
+        case '0': return 0xdb;
+        case '-': return 0x18;
+        case ',': return 0x1b;
+        case '.': return 0x19;
+        case 'a': return 0xb1;
+        case 'f': return 0xf8;
+        case 'g': return 0xf1;
+        case 'h': return 0xb0;
+        case 'j': return 0xd9;
+        case 'k': return 0xbf;
+        case 'l': return 0xda;
+        case 'm': return 0xc0;
+        case 'n': return 0xc5;
+        case 'q': return 0xc4;
+        case 's': return 0x5f;
+        case 't': return 0xc3;
+        case 'u': return 0xb4;
+        case 'v': return 0xc1;
+        case 'w': return 0xc2;
+        case 'x': return 0xb3;
+        case 'y': return 0xf3;
+        case 'z': return 0xf2;
+        case '~': return 0xfa;
+        case '_': return 0xff;
+        case '+': return 0x1a;
+        case '{': return 0xe3;
+        case '}': return 0x9c;
+    }
+
+    return c;
+}
+
+void term_putchar(uint8_t c) {
+    if (discard_next || (term_runtime == true && (c == 0x18 || c == 0x1a))) {
+        discard_next = false;
+        escape = false;
+        csi = false;
+        control_sequence = false;
+        g_select = 0;
+        return;
+    }
+
+    if (escape == true) {
+        escape_parse(c);
+        return;
+    }
+
+    if (g_select) {
+        g_select--;
+        switch (c) {
+            case 'B':
+                charsets[g_select] = CHARSET_DEFAULT; break;
+            case '0':
+                charsets[g_select] = CHARSET_DEC_SPECIAL; break;
+        }
+        g_select = 0;
+        return;
+    }
+
+    size_t x, y;
+    get_cursor_pos(&x, &y);
+
+    switch (c) {
+        case 0x00:
+        case 0x7f:
+            return;
+        case 0x9b:
+            csi = true;
+            // FALLTHRU
+        case '\e':
+            escape_offset = 0;
+            escape = true;
+            return;
+        case '\t':
+            if ((x / TERM_TABSIZE + 1) >= term_cols) {
+                set_cursor_pos(term_cols - 1, y);
+                return;
+            }
+            set_cursor_pos((x / TERM_TABSIZE + 1) * TERM_TABSIZE, y);
+            return;
+        case 0x0b:
+        case 0x0c:
+        case '\n':
+            if (y == scroll_bottom_margin - 1) {
+                term_scroll();
+                set_cursor_pos(0, y);
+            } else {
+                set_cursor_pos(0, y + 1);
+            }
+            return;
+        case '\b':
+            set_cursor_pos(x - 1, y);
+            return;
+        case '\r':
+            set_cursor_pos(0, y);
+            return;
+        case '\a':
+            // The bell is handled by the kernel
+            if (term_callback != NULL) {
+                term_callback(TERM_CB_BELL, 0, 0, 0);
+            }
+            return;
+        case 14:
+            // Move to G1 set
+            current_charset = 1;
+            return;
+        case 15:
+            // Move to G0 set
+            current_charset = 0;
+            return;
+    }
+
+    if (insert_mode == true) {
+        for (size_t i = term_cols - 1; ; i--) {
+            term_move_character(i + 1, y, i, y);
+            if (i == x) {
+                break;
+            }
+        }
+    }
+
+    // Translate character set
+    switch (charsets[current_charset]) {
+        case CHARSET_DEFAULT:
+            break;
+        case CHARSET_DEC_SPECIAL:
+            c = dec_special_to_cp437(c);
+    }
+
+    raw_putchar(c);
+}
diff --git a/stage23/lib/term.s2.c b/stage23/lib/term.s2.c
deleted file mode 100644
index 4622877c..00000000
--- a/stage23/lib/term.s2.c
+++ /dev/null
@@ -1,1144 +0,0 @@
-#include <stdint.h>
-#include <stddef.h>
-#include <stdbool.h>
-#include <lib/term.h>
-#include <lib/real.h>
-#include <lib/image.h>
-#include <lib/blib.h>
-#include <drivers/vga_textmode.h>
-#include <lib/print.h>
-#include <mm/pmm.h>
-
-// Tries to implement this standard for terminfo
-// https://man7.org/linux/man-pages/man4/console_codes.4.html
-
-no_unwind int current_video_mode = -1;
-int term_backend = NOT_READY;
-size_t term_rows, term_cols;
-bool term_runtime = false;
-
-static void notready_raw_putchar(uint8_t c) {
-    (void)c;
-}
-static void notready_clear(bool move) {
-    (void)move;
-}
-static void notready_void(void) {}
-static void notready_set_cursor_pos(size_t x, size_t y) {
-    (void)x; (void)y;
-}
-static void notready_get_cursor_pos(size_t *x, size_t *y) {
-    *x = 0;
-    *y = 0;
-}
-static void notready_size_t(size_t n) {
-    (void)n;
-}
-static bool notready_disable(void) {
-    return false;
-}
-static void notready_move_character(size_t a, size_t b, size_t c, size_t d) {
-    (void)a; (void)b; (void)c; (void)d;
-}
-static uint64_t notready_context_size(void) {
-    return 0;
-}
-static void notready_uint64_t(uint64_t n) {
-    (void)n;
-}
-
-void term_notready(void) {
-    term_backend = NOT_READY;
-
-    raw_putchar = notready_raw_putchar;
-    clear = notready_clear;
-    enable_cursor = notready_void;
-    disable_cursor = notready_disable;
-    set_cursor_pos = notready_set_cursor_pos;
-    get_cursor_pos = notready_get_cursor_pos;
-    set_text_fg = notready_size_t;
-    set_text_bg = notready_size_t;
-    set_text_fg_bright = notready_size_t;
-    set_text_bg_bright = notready_size_t;
-    set_text_fg_default = notready_void;
-    set_text_bg_default = notready_void;
-    scroll_disable = notready_disable;
-    scroll_enable = notready_void;
-    term_move_character = notready_move_character;
-    term_scroll = notready_void;
-    term_revscroll = notready_void;
-    term_swap_palette = notready_void;
-    term_save_state = notready_void;
-    term_restore_state = notready_void;
-    term_double_buffer_flush = notready_void;
-    term_context_size = notready_context_size;
-    term_context_save = notready_uint64_t;
-    term_context_restore = notready_uint64_t;
-    term_full_refresh = notready_void;
-
-    term_rows = 100;
-    term_cols = 100;
-}
-
-#if bios == 1
-void fallback_raw_putchar(uint8_t c) {
-    struct rm_regs r = {0};
-    r.eax = 0x0e00 | c;
-    rm_int(0x10, &r, &r);
-}
-
-void fallback_clear(bool move) {
-    (void)move;
-    struct rm_regs r = {0};
-    rm_int(0x11, &r, &r);
-    switch ((r.eax >> 4) & 3) {
-        case 0:
-            r.eax = 3;
-            break;
-        case 1:
-            r.eax = 1;
-            break;
-        case 2:
-            r.eax = 3;
-            break;
-        case 3:
-            r.eax = 7;
-            break;
-    }
-    rm_int(0x10, &r, &r);
-}
-
-void fallback_set_cursor_pos(size_t x, size_t y) {
-    struct rm_regs r = {0};
-    r.eax = 0x0200;
-    r.ebx = 0;
-    r.edx = (y << 8) + x;
-    rm_int(0x10, &r, &r);
-}
-
-void fallback_get_cursor_pos(size_t *x, size_t *y) {
-    struct rm_regs r = {0};
-    r.eax = 0x0300;
-    r.ebx = 0;
-    rm_int(0x10, &r, &r);
-    *x = r.edx & 0xff;
-    *y = r.edx >> 8;
-}
-
-#elif uefi == 1
-static int cursor_x = 0, cursor_y = 0;
-
-void fallback_raw_putchar(uint8_t c) {
-    CHAR16 string[2];
-    string[0] = c;
-    string[1] = 0;
-    gST->ConOut->OutputString(gST->ConOut, string);
-    switch (c) {
-        case 0x08:
-            if (cursor_x > 0)
-                cursor_x--;
-            break;
-        case 0x0A:
-            cursor_x = 0;
-            break;
-        case 0x0D:
-            if (cursor_y < 24)
-                cursor_y++;
-            break;
-        default:
-            if (++cursor_x > 80) {
-                cursor_x = 0;
-                if (cursor_y < 24)
-                    cursor_y++;
-            }
-    }
-}
-
-void fallback_clear(bool move) {
-    (void)move;
-    gST->ConOut->ClearScreen(gST->ConOut);
-    cursor_x = cursor_y = 0;
-}
-
-void fallback_set_cursor_pos(size_t x, size_t y) {
-    if (x >= 80 || y >= 25)
-        return;
-    gST->ConOut->SetCursorPosition(gST->ConOut, x, y);
-    cursor_x = x;
-    cursor_y = y;
-}
-
-void fallback_get_cursor_pos(size_t *x, size_t *y) {
-    *x = cursor_x;
-    *y = cursor_y;
-}
-#endif
-
-void term_fallback(void) {
-#if uefi == 1
-    if (!efi_boot_services_exited) {
-        gST->ConOut->Reset(gST->ConOut, false);
-        gST->ConOut->SetMode(gST->ConOut, 0);
-        cursor_x = cursor_y = 0;
-#elif bios == 1
-        fallback_clear(true);
-#endif
-        term_notready();
-        raw_putchar = fallback_raw_putchar;
-        clear = fallback_clear;
-        set_cursor_pos = fallback_set_cursor_pos;
-        get_cursor_pos = fallback_get_cursor_pos;
-        term_backend = FALLBACK;
-#if uefi == 1
-    }
-#endif
-}
-
-void (*raw_putchar)(uint8_t c);
-void (*clear)(bool move);
-void (*enable_cursor)(void);
-bool (*disable_cursor)(void);
-void (*set_cursor_pos)(size_t x, size_t y);
-void (*get_cursor_pos)(size_t *x, size_t *y);
-void (*set_text_fg)(size_t fg);
-void (*set_text_bg)(size_t bg);
-void (*set_text_fg_bright)(size_t fg);
-void (*set_text_bg_bright)(size_t bg);
-void (*set_text_fg_default)(void);
-void (*set_text_bg_default)(void);
-bool (*scroll_disable)(void);
-void (*scroll_enable)(void);
-void (*term_move_character)(size_t new_x, size_t new_y, size_t old_x, size_t old_y);
-void (*term_scroll)(void);
-void (*term_revscroll)(void);
-void (*term_swap_palette)(void);
-void (*term_save_state)(void);
-void (*term_restore_state)(void);
-
-void (*term_double_buffer_flush)(void);
-
-uint64_t (*term_context_size)(void);
-void (*term_context_save)(uint64_t ptr);
-void (*term_context_restore)(uint64_t ptr);
-void (*term_full_refresh)(void);
-
-void (*term_callback)(uint64_t, uint64_t, uint64_t, uint64_t) = NULL;
-
-struct term_context term_context;
-
-#define escape_offset term_context.escape_offset
-#define control_sequence term_context.control_sequence
-#define csi term_context.csi
-#define escape term_context.escape
-#define rrr term_context.rrr
-#define discard_next term_context.discard_next
-#define bold term_context.bold
-#define reverse_video term_context.reverse_video
-#define dec_private term_context.dec_private
-#define esc_values term_context.esc_values
-#define esc_values_i term_context.esc_values_i
-#define saved_cursor_x term_context.saved_cursor_x
-#define saved_cursor_y term_context.saved_cursor_y
-#define current_primary term_context.current_primary
-#define insert_mode term_context.insert_mode
-#define scroll_top_margin term_context.scroll_top_margin
-#define scroll_bottom_margin term_context.scroll_bottom_margin
-#define current_charset term_context.current_charset
-#define charsets term_context.charsets
-#define g_select term_context.g_select
-
-#define saved_state_bold term_context.saved_state_bold
-#define saved_state_reverse_video term_context.saved_state_reverse_video
-#define saved_state_current_charset term_context.saved_state_current_charset
-#define saved_state_current_primary term_context.saved_state_current_primary
-
-#define CHARSET_DEFAULT 0
-#define CHARSET_DEC_SPECIAL 1
-
-void term_reinit(void) {
-    escape_offset = 0;
-    control_sequence = false;
-    csi = false;
-    escape = false;
-    rrr = false;
-    discard_next = false;
-    bold = false;
-    reverse_video = false;
-    dec_private = false;
-    esc_values_i = 0;
-    saved_cursor_x = 0;
-    saved_cursor_y = 0;
-    current_primary = (size_t)-1;
-    insert_mode = false;
-    scroll_top_margin = 0;
-    scroll_bottom_margin = term_rows;
-    current_charset = 0;
-    g_select = 0;
-    charsets[0] = CHARSET_DEFAULT;
-    charsets[1] = CHARSET_DEC_SPECIAL;
-    term_autoflush = true;
-}
-
-#if bios == 1
-void term_textmode(void) {
-    term_notready();
-
-    if (quiet || allocations_disallowed) {
-        return;
-    }
-
-    init_vga_textmode(&term_rows, &term_cols, true);
-
-    term_reinit();
-
-    raw_putchar    = text_putchar;
-    clear          = text_clear;
-    enable_cursor  = text_enable_cursor;
-    disable_cursor = text_disable_cursor;
-    set_cursor_pos = text_set_cursor_pos;
-    get_cursor_pos = text_get_cursor_pos;
-    set_text_fg    = text_set_text_fg;
-    set_text_bg    = text_set_text_bg;
-    set_text_fg_bright = text_set_text_fg_bright;
-    set_text_bg_bright = text_set_text_bg_bright;
-    set_text_fg_default = text_set_text_fg_default;
-    set_text_bg_default = text_set_text_bg_default;
-    scroll_disable = text_scroll_disable;
-    scroll_enable  = text_scroll_enable;
-    term_move_character = text_move_character;
-    term_scroll = text_scroll;
-    term_revscroll = text_revscroll;
-    term_swap_palette = text_swap_palette;
-    term_save_state = text_save_state;
-    term_restore_state = text_restore_state;
-
-    term_double_buffer_flush = text_double_buffer_flush;
-
-    term_context_size = text_context_size;
-    term_context_save = text_context_save;
-    term_context_restore = text_context_restore;
-    term_full_refresh = text_full_refresh;
-
-    term_backend = TEXTMODE;
-}
-#endif
-
-static uint64_t context_size(void) {
-    uint64_t ret = 0;
-
-    ret += sizeof(struct term_context);
-    ret += term_context_size();
-
-    return ret;
-}
-
-static void context_save(uint64_t ptr) {
-    memcpy32to64(ptr, (uint64_t)(uintptr_t)&term_context, sizeof(struct term_context));
-    ptr += sizeof(struct term_context);
-
-    term_context_save(ptr);
-}
-
-static void context_restore(uint64_t ptr) {
-    memcpy32to64((uint64_t)(uintptr_t)&term_context, ptr, sizeof(struct term_context));
-    ptr += sizeof(struct term_context);
-
-    term_context_restore(ptr);
-}
-
-#if defined (__i386__)
-#define TERM_XFER_CHUNK 8192
-
-static uint8_t xfer_buf[TERM_XFER_CHUNK];
-#endif
-
-bool term_autoflush = true;
-
-void term_write(uint64_t buf, uint64_t count) {
-    if (term_backend == NOT_READY)
-        return;
-
-    switch (count) {
-        case TERM_CTX_SIZE: {
-            uint64_t ret = context_size();
-            memcpy32to64(buf, (uint64_t)(uintptr_t)&ret, sizeof(uint64_t));
-            return;
-        }
-        case TERM_CTX_SAVE: {
-            context_save(buf);
-            return;
-        }
-        case TERM_CTX_RESTORE: {
-            context_restore(buf);
-            return;
-        }
-        case TERM_FULL_REFRESH: {
-            term_full_refresh();
-            return;
-        }
-    }
-
-    bool native = false;
-#if defined (__x86_64__)
-    native = true;
-#endif
-
-    if (!term_runtime || native) {
-        const char *s = (const char *)(uintptr_t)buf;
-
-        for (size_t i = 0; i < count; i++)
-            term_putchar(s[i]);
-    } else {
-#if defined (__i386__)
-        while (count != 0) {
-            uint64_t chunk;
-            if (count > TERM_XFER_CHUNK) {
-                chunk = TERM_XFER_CHUNK;
-            } else {
-                chunk = count;
-            }
-
-            memcpy32to64((uint64_t)(uintptr_t)xfer_buf, buf, chunk);
-
-            for (size_t i = 0; i < chunk; i++)
-                term_putchar(xfer_buf[i]);
-
-            count -= chunk;
-            buf += chunk;
-        }
-#endif
-    }
-
-    if (term_autoflush) {
-        term_double_buffer_flush();
-    }
-}
-
-static void sgr(void) {
-    size_t i = 0;
-
-    if (!esc_values_i)
-        goto def;
-
-    for (; i < esc_values_i; i++) {
-        size_t offset;
-
-        if (esc_values[i] == 0) {
-def:
-            if (reverse_video) {
-                reverse_video = false;
-                term_swap_palette();
-            }
-            bold = false;
-            current_primary = (size_t)-1;
-            set_text_bg_default();
-            set_text_fg_default();
-            continue;
-        }
-
-        else if (esc_values[i] == 1) {
-            bold = true;
-            if (current_primary != (size_t)-1) {
-                if (!reverse_video) {
-                    set_text_fg_bright(current_primary);
-                } else {
-                    set_text_bg_bright(current_primary);
-                }
-            }
-            continue;
-        }
-
-        else if (esc_values[i] == 22) {
-            bold = false;
-            if (current_primary != (size_t)-1) {
-                if (!reverse_video) {
-                    set_text_fg(current_primary);
-                } else {
-                    set_text_bg(current_primary);
-                }
-            }
-            continue;
-        }
-
-        else if (esc_values[i] >= 30 && esc_values[i] <= 37) {
-            offset = 30;
-            current_primary = esc_values[i] - offset;
-
-            if (reverse_video) {
-                goto set_bg;
-            }
-
-set_fg:
-            if (bold && !reverse_video) {
-                set_text_fg_bright(esc_values[i] - offset);
-            } else {
-                set_text_fg(esc_values[i] - offset);
-            }
-            continue;
-        }
-
-        else if (esc_values[i] >= 40 && esc_values[i] <= 47) {
-            offset = 40;
-            if (reverse_video) {
-                goto set_fg;
-            }
-
-set_bg:
-            if (bold && reverse_video) {
-                set_text_bg_bright(esc_values[i] - offset);
-            } else {
-                set_text_bg(esc_values[i] - offset);
-            }
-            continue;
-        }
-
-        else if (esc_values[i] >= 90 && esc_values[i] <= 97) {
-            offset = 90;
-            current_primary = esc_values[i] - offset;
-
-            if (reverse_video) {
-                goto set_bg_bright;
-            }
-
-set_fg_bright:
-            set_text_fg_bright(esc_values[i] - offset);
-            continue;
-        }
-
-        else if (esc_values[i] >= 100 && esc_values[i] <= 107) {
-            offset = 100;
-            if (reverse_video) {
-                goto set_fg_bright;
-            }
-
-set_bg_bright:
-            set_text_bg_bright(esc_values[i] - offset);
-            continue;
-        }
-
-        else if (esc_values[i] == 39) {
-            current_primary = (size_t)-1;
-
-            if (reverse_video) {
-                term_swap_palette();
-            }
-
-            set_text_fg_default();
-
-            if (reverse_video) {
-                term_swap_palette();
-            }
-
-            continue;
-        }
-
-        else if (esc_values[i] == 49) {
-            if (reverse_video) {
-                term_swap_palette();
-            }
-
-            set_text_bg_default();
-
-            if (reverse_video) {
-                term_swap_palette();
-            }
-
-            continue;
-        }
-
-        else if (esc_values[i] == 7) {
-            if (!reverse_video) {
-                reverse_video = true;
-                term_swap_palette();
-            }
-            continue;
-        }
-
-        else if (esc_values[i] == 27) {
-            if (reverse_video) {
-                reverse_video = false;
-                term_swap_palette();
-            }
-            continue;
-        }
-    }
-}
-
-static void dec_private_parse(uint8_t c) {
-    dec_private = false;
-
-    if (esc_values_i == 0) {
-        return;
-    }
-
-    bool set;
-
-    switch (c) {
-        case 'h':
-            set = true; break;
-        case 'l':
-            set = false; break;
-        default:
-            return;
-    }
-
-    switch (esc_values[0]) {
-        case 25: {
-            if (set) {
-                enable_cursor();
-            } else {
-                disable_cursor();
-            }
-            return;
-        }
-    }
-
-    if (term_callback != NULL) {
-        term_callback(TERM_CB_DEC, esc_values_i, (uintptr_t)esc_values, c);
-    }
-}
-
-static void linux_private_parse(void) {
-    if (esc_values_i == 0) {
-        return;
-    }
-
-    if (term_callback != NULL) {
-        term_callback(TERM_CB_LINUX, esc_values_i, (uintptr_t)esc_values, 0);
-    }
-}
-
-static void mode_toggle(uint8_t c) {
-    if (esc_values_i == 0) {
-        return;
-    }
-
-    bool set;
-
-    switch (c) {
-        case 'h':
-            set = true; break;
-        case 'l':
-            set = false; break;
-        default:
-            return;
-    }
-
-    switch (esc_values[0]) {
-        case 4:
-            insert_mode = set; return;
-    }
-
-    if (term_callback != NULL) {
-        term_callback(TERM_CB_MODE, esc_values_i, (uintptr_t)esc_values, c);
-    }
-}
-
-static void control_sequence_parse(uint8_t c) {
-    if (escape_offset == 2) {
-        switch (c) {
-            case '[':
-                discard_next = true;
-                goto cleanup;
-            case '?':
-                dec_private = true;
-                return;
-        }
-    }
-
-    if (c >= '0' && c <= '9') {
-        if (esc_values_i == MAX_ESC_VALUES) {
-            return;
-        }
-        rrr = true;
-        esc_values[esc_values_i] *= 10;
-        esc_values[esc_values_i] += c - '0';
-        return;
-    }
-
-    if (rrr == true) {
-        esc_values_i++;
-        rrr = false;
-        if (c == ';')
-            return;
-    } else if (c == ';') {
-        if (esc_values_i == MAX_ESC_VALUES) {
-            return;
-        }
-        esc_values[esc_values_i] = 0;
-        esc_values_i++;
-        return;
-    }
-
-    size_t esc_default;
-    switch (c) {
-        case 'J': case 'K': case 'q':
-            esc_default = 0; break;
-        default:
-            esc_default = 1; break;
-    }
-
-    for (size_t i = esc_values_i; i < MAX_ESC_VALUES; i++) {
-        esc_values[i] = esc_default;
-    }
-
-    if (dec_private == true) {
-        dec_private_parse(c);
-        goto cleanup;
-    }
-
-    bool r = scroll_disable();
-    size_t x, y;
-    get_cursor_pos(&x, &y);
-
-    switch (c) {
-        case 'F':
-            x = 0;
-            // FALLTHRU
-        case 'A': {
-            if (esc_values[0] > y)
-                esc_values[0] = y;
-            size_t orig_y = y;
-            size_t dest_y = y - esc_values[0];
-            bool will_be_in_scroll_region = false;
-            if ((scroll_top_margin >= dest_y && scroll_top_margin <= orig_y)
-             || (scroll_bottom_margin >= dest_y && scroll_bottom_margin <= orig_y)) {
-                will_be_in_scroll_region = true;
-            }
-            if (will_be_in_scroll_region && dest_y < scroll_top_margin) {
-                dest_y = scroll_top_margin;
-            }
-            set_cursor_pos(x, dest_y);
-            break;
-        }
-        case 'E':
-            x = 0;
-            // FALLTHRU
-        case 'e':
-        case 'B': {
-            if (y + esc_values[0] > term_rows - 1)
-                esc_values[0] = (term_rows - 1) - y;
-            size_t orig_y = y;
-            size_t dest_y = y + esc_values[0];
-            bool will_be_in_scroll_region = false;
-            if ((scroll_top_margin >= orig_y && scroll_top_margin <= dest_y)
-             || (scroll_bottom_margin >= orig_y && scroll_bottom_margin <= dest_y)) {
-                will_be_in_scroll_region = true;
-            }
-            if (will_be_in_scroll_region && dest_y >= scroll_bottom_margin) {
-                dest_y = scroll_bottom_margin - 1;
-            }
-            set_cursor_pos(x, dest_y);
-            break;
-        }
-        case 'a':
-        case 'C':
-            if (x + esc_values[0] > term_cols - 1)
-                esc_values[0] = (term_cols - 1) - x;
-            set_cursor_pos(x + esc_values[0], y);
-            break;
-        case 'D':
-            if (esc_values[0] > x)
-                esc_values[0] = x;
-            set_cursor_pos(x - esc_values[0], y);
-            break;
-        case 'c':
-            if (term_callback != NULL) {
-                term_callback(TERM_CB_PRIVATE_ID, 0, 0, 0);
-            }
-            break;
-        case 'd':
-            esc_values[0] -= 1;
-            if (esc_values[0] >= term_rows)
-                esc_values[0] = term_rows - 1;
-            set_cursor_pos(x, esc_values[0]);
-            break;
-        case 'G':
-        case '`':
-            esc_values[0] -= 1;
-            if (esc_values[0] >= term_cols)
-                esc_values[0] = term_cols - 1;
-            set_cursor_pos(esc_values[0], y);
-            break;
-        case 'H':
-        case 'f':
-            esc_values[0] -= 1;
-            esc_values[1] -= 1;
-            if (esc_values[1] >= term_cols)
-                esc_values[1] = term_cols - 1;
-            if (esc_values[0] >= term_rows)
-                esc_values[0] = term_rows - 1;
-            set_cursor_pos(esc_values[1], esc_values[0]);
-            break;
-        case 'n':
-            switch (esc_values[0]) {
-                case 5:
-                    if (term_callback != NULL) {
-                        term_callback(TERM_CB_STATUS_REPORT, 0, 0, 0);
-                    }
-                    break;
-                case 6:
-                    if (term_callback != NULL) {
-                        term_callback(TERM_CB_POS_REPORT, x + 1, y + 1, 0);
-                    }
-                    break;
-            }
-            break;
-        case 'q':
-            if (term_callback != NULL) {
-                term_callback(TERM_CB_KBD_LEDS, esc_values[0], 0, 0);
-            }
-            break;
-        case 'J':
-            switch (esc_values[0]) {
-                case 0: {
-                    size_t rows_remaining = term_rows - (y + 1);
-                    size_t cols_diff = term_cols - (x + 1);
-                    size_t to_clear = rows_remaining * term_cols + cols_diff;
-                    for (size_t i = 0; i < to_clear; i++) {
-                        raw_putchar(' ');
-                    }
-                    set_cursor_pos(x, y);
-                    break;
-                }
-                case 1: {
-                    set_cursor_pos(0, 0);
-                    bool b = false;
-                    for (size_t yc = 0; yc < term_rows; yc++) {
-                        for (size_t xc = 0; xc < term_cols; xc++) {
-                            raw_putchar(' ');
-                            if (xc == x && yc == y) {
-                                set_cursor_pos(x, y);
-                                b = true;
-                                break;
-                            }
-                        }
-                        if (b == true)
-                            break;
-                    }
-                    break;
-                }
-                case 2:
-                case 3:
-                    clear(false);
-                    break;
-            }
-            break;
-        case '@':
-            for (size_t i = term_cols - 1; ; i--) {
-                term_move_character(i + esc_values[0], y, i, y);
-                set_cursor_pos(i, y);
-                raw_putchar(' ');
-                if (i == x) {
-                    break;
-                }
-            }
-            set_cursor_pos(x, y);
-            break;
-        case 'P':
-            for (size_t i = x + esc_values[0]; i < term_cols; i++)
-                term_move_character(i - esc_values[0], y, i, y);
-            set_cursor_pos(term_cols - esc_values[0], y);
-            // FALLTHRU
-        case 'X':
-            for (size_t i = 0; i < esc_values[0]; i++)
-                raw_putchar(' ');
-            set_cursor_pos(x, y);
-            break;
-        case 'm':
-            sgr();
-            break;
-        case 's':
-            get_cursor_pos(&saved_cursor_x, &saved_cursor_y);
-            break;
-        case 'u':
-            set_cursor_pos(saved_cursor_x, saved_cursor_y);
-            break;
-        case 'K':
-            switch (esc_values[0]) {
-                case 0: {
-                    for (size_t i = x; i < term_cols; i++)
-                        raw_putchar(' ');
-                    set_cursor_pos(x, y);
-                    break;
-                }
-                case 1: {
-                    set_cursor_pos(0, y);
-                    for (size_t i = 0; i < x; i++)
-                        raw_putchar(' ');
-                    break;
-                }
-                case 2: {
-                    set_cursor_pos(0, y);
-                    for (size_t i = 0; i < term_cols; i++)
-                        raw_putchar(' ');
-                    set_cursor_pos(x, y);
-                    break;
-                }
-            }
-            break;
-        case 'r':
-            scroll_top_margin = 0;
-            scroll_bottom_margin = term_rows;
-            if (esc_values_i > 0) {
-                scroll_top_margin = esc_values[0] - 1;
-            }
-            if (esc_values_i > 1) {
-                scroll_bottom_margin = esc_values[1];
-            }
-            if (scroll_top_margin >= term_rows
-             || scroll_bottom_margin > term_rows
-             || scroll_top_margin >= (scroll_bottom_margin - 1)) {
-                scroll_top_margin = 0;
-                scroll_bottom_margin = term_rows;
-            }
-            set_cursor_pos(0, 0);
-            break;
-        case 'l':
-        case 'h':
-            mode_toggle(c);
-            break;
-        case ']':
-            linux_private_parse();
-            break;
-    }
-
-    if (r)
-        scroll_enable();
-
-cleanup:
-    control_sequence = false;
-    escape = false;
-}
-
-static void restore_state(void) {
-    bold = saved_state_bold;
-    reverse_video = saved_state_reverse_video;
-    current_charset = saved_state_current_charset;
-    current_primary = saved_state_current_primary;
-
-    term_restore_state();
-}
-
-static void save_state(void) {
-    term_save_state();
-
-    saved_state_bold = bold;
-    saved_state_reverse_video = reverse_video;
-    saved_state_current_charset = current_charset;
-    saved_state_current_primary = current_primary;
-}
-
-static void escape_parse(uint8_t c) {
-    escape_offset++;
-
-    if (control_sequence == true) {
-        control_sequence_parse(c);
-        return;
-    }
-
-    if (csi == true) {
-        csi = false;
-        goto is_csi;
-    }
-
-    size_t x, y;
-    get_cursor_pos(&x, &y);
-
-    switch (c) {
-        case '[':
-is_csi:
-            for (size_t i = 0; i < MAX_ESC_VALUES; i++)
-                esc_values[i] = 0;
-            esc_values_i = 0;
-            rrr = false;
-            control_sequence = true;
-            return;
-        case '7':
-            save_state();
-            break;
-        case '8':
-            restore_state();
-            break;
-        case 'c':
-            term_reinit();
-            clear(true);
-            break;
-        case 'D':
-            if (y == scroll_bottom_margin - 1) {
-                term_scroll();
-                set_cursor_pos(x, y);
-            } else {
-                set_cursor_pos(x, y + 1);
-            }
-            break;
-        case 'E':
-            if (y == scroll_bottom_margin - 1) {
-                term_scroll();
-                set_cursor_pos(0, y);
-            } else {
-                set_cursor_pos(0, y + 1);
-            }
-            break;
-        case 'M':
-            // "Reverse linefeed"
-            if (y == scroll_top_margin) {
-                term_revscroll();
-                set_cursor_pos(0, y);
-            } else {
-                set_cursor_pos(0, y - 1);
-            }
-            break;
-        case 'Z':
-            if (term_callback != NULL) {
-                term_callback(TERM_CB_PRIVATE_ID, 0, 0, 0);
-            }
-            break;
-        case '(':
-        case ')':
-            g_select = c - '\'';
-            break;
-        case '\e':
-            if (term_runtime == false) {
-                raw_putchar(c);
-            }
-            break;
-    }
-
-    escape = false;
-}
-
-static uint8_t dec_special_to_cp437(uint8_t c) {
-    switch (c) {
-        case '`': return 0x04;
-        case '0': return 0xdb;
-        case '-': return 0x18;
-        case ',': return 0x1b;
-        case '.': return 0x19;
-        case 'a': return 0xb1;
-        case 'f': return 0xf8;
-        case 'g': return 0xf1;
-        case 'h': return 0xb0;
-        case 'j': return 0xd9;
-        case 'k': return 0xbf;
-        case 'l': return 0xda;
-        case 'm': return 0xc0;
-        case 'n': return 0xc5;
-        case 'q': return 0xc4;
-        case 's': return 0x5f;
-        case 't': return 0xc3;
-        case 'u': return 0xb4;
-        case 'v': return 0xc1;
-        case 'w': return 0xc2;
-        case 'x': return 0xb3;
-        case 'y': return 0xf3;
-        case 'z': return 0xf2;
-        case '~': return 0xfa;
-        case '_': return 0xff;
-        case '+': return 0x1a;
-        case '{': return 0xe3;
-        case '}': return 0x9c;
-    }
-
-    return c;
-}
-
-void term_putchar(uint8_t c) {
-    if (discard_next || (term_runtime == true && (c == 0x18 || c == 0x1a))) {
-        discard_next = false;
-        escape = false;
-        csi = false;
-        control_sequence = false;
-        g_select = 0;
-        return;
-    }
-
-    if (escape == true) {
-        escape_parse(c);
-        return;
-    }
-
-    if (g_select) {
-        g_select--;
-        switch (c) {
-            case 'B':
-                charsets[g_select] = CHARSET_DEFAULT; break;
-            case '0':
-                charsets[g_select] = CHARSET_DEC_SPECIAL; break;
-        }
-        g_select = 0;
-        return;
-    }
-
-    size_t x, y;
-    get_cursor_pos(&x, &y);
-
-    switch (c) {
-        case 0x00:
-        case 0x7f:
-            return;
-        case 0x9b:
-            csi = true;
-            // FALLTHRU
-        case '\e':
-            escape_offset = 0;
-            escape = true;
-            return;
-        case '\t':
-            if ((x / TERM_TABSIZE + 1) >= term_cols) {
-                set_cursor_pos(term_cols - 1, y);
-                return;
-            }
-            set_cursor_pos((x / TERM_TABSIZE + 1) * TERM_TABSIZE, y);
-            return;
-        case 0x0b:
-        case 0x0c:
-        case '\n':
-            if (y == scroll_bottom_margin - 1) {
-                term_scroll();
-                set_cursor_pos(0, y);
-            } else {
-                set_cursor_pos(0, y + 1);
-            }
-            return;
-        case '\b':
-            set_cursor_pos(x - 1, y);
-            return;
-        case '\r':
-            set_cursor_pos(0, y);
-            return;
-        case '\a':
-            // The bell is handled by the kernel
-            if (term_callback != NULL) {
-                term_callback(TERM_CB_BELL, 0, 0, 0);
-            }
-            return;
-        case 14:
-            // Move to G1 set
-            current_charset = 1;
-            return;
-        case 15:
-            // Move to G0 set
-            current_charset = 0;
-            return;
-    }
-
-    if (insert_mode == true) {
-        for (size_t i = term_cols - 1; ; i--) {
-            term_move_character(i + 1, y, i, y);
-            if (i == x) {
-                break;
-            }
-        }
-    }
-
-    // Translate character set
-    switch (charsets[current_charset]) {
-        case CHARSET_DEFAULT:
-            break;
-        case CHARSET_DEC_SPECIAL:
-            c = dec_special_to_cp437(c);
-    }
-
-    raw_putchar(c);
-}
diff --git a/stage23/linker_stage2only.ld b/stage23/linker_stage2only.ld
index 60ad1431..7ec516e5 100644
--- a/stage23/linker_stage2only.ld
+++ b/stage23/linker_stage2only.ld
@@ -36,6 +36,9 @@ SECTIONS
         getchar_internal = .;
         getchar = .;
         menu = .;
+        term_write = .;
+        term_textmode = .;
+        term_fallback = .;
         stage3_addr = .;
         data_begin = .;
     }
tab: 248 wrap: offon