:: commit 08b349e513d673502fc1f344831552a88ad6f9a0

mintsuki <mintsuki@protonmail.com> — 2023-02-16 03:11

parents: fe9f8d17c6

misc: Backport multiple framebuffer support from trunk

diff --git a/common/drivers/edid.c b/common/drivers/edid.c
index 3ee60ea7..25cd9f0b 100644
--- a/common/drivers/edid.c
+++ b/common/drivers/edid.c
@@ -50,11 +50,7 @@ success:
 
 #include <efi.h>
 
-struct edid_info_struct *get_edid_info(void) {
-    if (!gop_ready) {
-        goto fail;
-    }
-
+struct edid_info_struct *get_edid_info(EFI_HANDLE gop_handle) {
     struct edid_info_struct *buf = ext_mem_alloc(sizeof(struct edid_info_struct));
 
     EFI_STATUS status;
diff --git a/common/drivers/edid.h b/common/drivers/edid.h
index 1b89a6d1..ad19ebac 100644
--- a/common/drivers/edid.h
+++ b/common/drivers/edid.h
@@ -30,6 +30,14 @@ struct edid_info_struct {
     uint8_t checksum;
 } __attribute__((packed));
 
+#if defined (UEFI)
+#include <efi.h>
+
+struct edid_info_struct *get_edid_info(EFI_HANDLE gop_handle);
+#endif
+
+#if defined (BIOS)
 struct edid_info_struct *get_edid_info(void);
+#endif
 
 #endif
diff --git a/common/drivers/gop.c b/common/drivers/gop.c
index 5bf871a9..c970efa1 100644
--- a/common/drivers/gop.c
+++ b/common/drivers/gop.c
@@ -37,7 +37,7 @@ static void linear_mask_to_mask_shift(
 
 // Most of this code taken from https://wiki.osdev.org/GOP
 
-static bool mode_to_fb_info(struct fb_info *ret, size_t mode) {
+static bool mode_to_fb_info(struct fb_info *ret, EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, size_t mode) {
     EFI_STATUS status;
 
     EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *mode_info;
@@ -98,10 +98,12 @@ static bool mode_to_fb_info(struct fb_info *ret, size_t mode) {
 
 bool gop_force_16 = false;
 
-static bool try_mode(struct fb_info *ret, size_t mode, uint64_t width, uint64_t height, int bpp) {
+static bool try_mode(struct fb_info *ret, EFI_GRAPHICS_OUTPUT_PROTOCOL *gop,
+                     size_t mode, uint64_t width, uint64_t height, int bpp,
+                     struct fb_info *fbs, size_t fbs_count) {
     EFI_STATUS status;
 
-    if (!mode_to_fb_info(ret, mode)) {
+    if (!mode_to_fb_info(ret, gop, mode)) {
         return false;
     }
 
@@ -121,22 +123,25 @@ static bool try_mode(struct fb_info *ret, size_t mode, uint64_t width, uint64_t
         }
     }
 
+    for (size_t i = 0; i < fbs_count; i++) {
+        if (gop->Mode->FrameBufferBase == fbs[i].framebuffer_addr) {
+            return false;
+        }
+    }
+
     printv("gop: Found matching mode %x, attempting to set...\n", mode);
 
-    if ((int)mode == current_video_mode) {
+    if (mode == gop->Mode->Mode) {
         printv("gop: Mode was already set, perfect!\n");
     } else {
         status = gop->SetMode(gop, mode);
 
         if (status) {
-            current_video_mode = -1;
             printv("gop: Failed to set video mode %x, moving on...\n", mode);
             return false;
         }
     }
 
-    current_video_mode = mode;
-
     ret->framebuffer_addr = gop->Mode->FrameBufferBase;
 
     fb_clear(ret);
@@ -144,18 +149,14 @@ static bool try_mode(struct fb_info *ret, size_t mode, uint64_t width, uint64_t
     return true;
 }
 
-struct fb_info *gop_get_mode_list(size_t *count) {
-    if (!gop_ready) {
-        return NULL;
-    }
-
+static struct fb_info *get_mode_list(size_t *count, EFI_GRAPHICS_OUTPUT_PROTOCOL *gop) {
     UINTN modes_count = gop->Mode->MaxMode;
 
     struct fb_info *ret = ext_mem_alloc(modes_count * sizeof(struct fb_info));
 
     size_t actual_count = 0;
     for (size_t i = 0; i < modes_count; i++) {
-        if (mode_to_fb_info(&ret[actual_count], i)) {
+        if (mode_to_fb_info(&ret[actual_count], gop, i)) {
             actual_count++;
         }
     }
@@ -170,21 +171,8 @@ struct fb_info *gop_get_mode_list(size_t *count) {
     return ret;
 }
 
-#define INVALID_PRESET_MODE 0xffffffff
-
-static no_unwind size_t preset_mode = INVALID_PRESET_MODE;
-static no_unwind EFI_GRAPHICS_OUTPUT_MODE_INFORMATION preset_mode_info;
-
-bool gop_ready = false;
-EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
-EFI_HANDLE gop_handle;
-
-bool init_gop(struct fb_info *ret,
+void init_gop(struct fb_info **ret, size_t *_fbs_count,
               uint64_t target_width, uint64_t target_height, uint16_t target_bpp) {
-    gop_ready = false;
-
-    ret->default_res = false;
-
     EFI_STATUS status;
 
     EFI_HANDLE tmp_handles[1];
@@ -196,7 +184,8 @@ bool init_gop(struct fb_info *ret,
     status = gBS->LocateHandle(ByProtocol, &gop_guid, NULL, &handles_size, handles);
 
     if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) {
-        return false;
+        *_fbs_count = 0;
+        return;
     }
 
     handles = ext_mem_alloc(handles_size);
@@ -204,45 +193,15 @@ bool init_gop(struct fb_info *ret,
     status = gBS->LocateHandle(ByProtocol, &gop_guid, NULL, &handles_size, handles);
     if (status != EFI_SUCCESS) {
         pmm_free(handles, handles_size);
-        return false;
-    }
-
-    gop_handle = handles[0];
-    pmm_free(handles, handles_size);
-
-    status = gBS->HandleProtocol(gop_handle, &gop_guid, (void **)&gop);
-    if (status != EFI_SUCCESS) {
-        return false;
+        *_fbs_count = 0;
+        return;
     }
 
-    gop_ready = true;
+    size_t handles_count = handles_size / sizeof(EFI_HANDLE);
 
-    EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *mode_info;
-    UINTN mode_info_size;
-
-    status = gop->QueryMode(gop, gop->Mode == NULL ? 0 : gop->Mode->Mode,
-                            &mode_info_size, &mode_info);
-
-    if (status == EFI_NOT_STARTED) {
-        status = gop->SetMode(gop, 0);
-        if (status) {
-            panic(false, "gop: Initialisation failed");
-        }
-        status = gop->QueryMode(gop, gop->Mode == NULL ? 0 : gop->Mode->Mode,
-                                &mode_info_size, &mode_info);
-    }
-
-    if (status) {
-        panic(false, "gop: Initialisation failed");
-    }
-
-    if (preset_mode == INVALID_PRESET_MODE) {
-        preset_mode = gop->Mode->Mode;
-        memcpy(&preset_mode_info, mode_info, mode_info_size);
-        current_video_mode = preset_mode;
-    }
+    *ret = ext_mem_alloc(handles_count * sizeof(struct fb_info));
 
-    struct resolution fallback_resolutions[] = {
+    const struct resolution fallback_resolutions[] = {
         { 0,    0,   0  },   // Overridden by EDID
         { 0,    0,   0  },   // Overridden by preset
         { 1024, 768, 32 },
@@ -256,67 +215,113 @@ bool init_gop(struct fb_info *ret,
         { 640,  480, 16 }
     };
 
-    UINTN modes_count = gop->Mode->MaxMode;
+    size_t fbs_count = 0;
+    for (size_t i = 0; i < handles_count; i++) {
+        struct fb_info *fb = &(*ret)[fbs_count];
 
-    size_t current_fallback = 0;
+        uint64_t _target_width = target_width;
+        uint64_t _target_height = target_height;
+        uint64_t _target_bpp = target_bpp;
 
-    if (!target_width || !target_height || !target_bpp) {
-        goto fallback;
-    } else {
-        printv("gop: Requested resolution of %ux%ux%u\n",
-               target_width, target_height, target_bpp);
-    }
+        EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
+
+        status = gBS->HandleProtocol(handles[i], &gop_guid, (void **)&gop);
+        if (status != EFI_SUCCESS) {
+            continue;
+        }
+
+        EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *mode_info;
+        UINTN mode_info_size;
+
+        status = gop->QueryMode(gop, gop->Mode == NULL ? 0 : gop->Mode->Mode,
+                                &mode_info_size, &mode_info);
+
+        if (status == EFI_NOT_STARTED) {
+            status = gop->SetMode(gop, 0);
+            if (status) {
+                continue;
+            }
+            status = gop->QueryMode(gop, gop->Mode == NULL ? 0 : gop->Mode->Mode,
+                                    &mode_info_size, &mode_info);
+        }
+
+        if (status) {
+            continue;
+        }
+
+        int preset_mode = gop->Mode->Mode;
+
+        fb->edid = get_edid_info(handles[i]);
+
+        UINTN modes_count = gop->Mode->MaxMode;
+
+        size_t current_fallback = 0;
+
+        if (!_target_width || !_target_height || !_target_bpp) {
+            goto fallback;
+        } else {
+            printv("gop: Requested resolution of %ux%ux%u\n",
+                   _target_width, _target_height, _target_bpp);
+        }
 
 retry:
-    for (size_t i = 0; i < modes_count; i++) {
-        if (try_mode(ret, i, target_width, target_height, target_bpp)) {
-            gop_force_16 = false;
-            return true;
+        for (size_t j = 0; j < modes_count; j++) {
+            if (try_mode(fb, gop, j, _target_width, _target_height, _target_bpp, *ret, fbs_count)) {
+                goto success;
+            }
         }
-    }
 
 fallback:
-    ret->default_res = true;
-
-    if (current_fallback == 0) {
-        current_fallback++;
-
-        struct edid_info_struct *edid_info = get_edid_info();
-        if (edid_info != NULL) {
-            uint64_t edid_width = (uint64_t)edid_info->det_timing_desc1[2];
-                     edid_width += ((uint64_t)edid_info->det_timing_desc1[4] & 0xf0) << 4;
-            uint64_t edid_height = (uint64_t)edid_info->det_timing_desc1[5];
-                     edid_height += ((uint64_t)edid_info->det_timing_desc1[7] & 0xf0) << 4;
-            if (edid_width >= preset_mode_info.HorizontalResolution
-             && edid_height >= preset_mode_info.VerticalResolution) {
-                target_width  = edid_width;
-                target_height = edid_height;
-                target_bpp    = 32;
-                goto retry;
+        if (current_fallback == 0) {
+            current_fallback++;
+
+            if (fb->edid != NULL) {
+                uint64_t edid_width = (uint64_t)fb->edid->det_timing_desc1[2];
+                         edid_width += ((uint64_t)fb->edid->det_timing_desc1[4] & 0xf0) << 4;
+                uint64_t edid_height = (uint64_t)fb->edid->det_timing_desc1[5];
+                         edid_height += ((uint64_t)fb->edid->det_timing_desc1[7] & 0xf0) << 4;
+                if (edid_width >= mode_info->HorizontalResolution
+                 && edid_height >= mode_info->VerticalResolution) {
+                    _target_width = edid_width;
+                    _target_height = edid_height;
+                    _target_bpp = 32;
+                    goto retry;
+                }
             }
         }
-    }
 
-    if (current_fallback == 1) {
-        current_fallback++;
+        if (current_fallback == 1) {
+            current_fallback++;
 
-        if (try_mode(ret, preset_mode, 0, 0, 0)) {
-            gop_force_16 = false;
-            return true;
+            if (try_mode(fb, gop, preset_mode, 0, 0, 0, *ret, fbs_count)) {
+                goto success;
+            }
+        }
+
+        if (current_fallback < SIZEOF_ARRAY(fallback_resolutions)) {
+            current_fallback++;
+
+            _target_width = fallback_resolutions[current_fallback].width;
+            _target_height = fallback_resolutions[current_fallback].height;
+            _target_bpp = fallback_resolutions[current_fallback].bpp;
+            goto retry;
         }
-    }
 
-    if (current_fallback < SIZEOF_ARRAY(fallback_resolutions)) {
-        current_fallback++;
+        continue;
 
-        target_width  = fallback_resolutions[current_fallback].width;
-        target_height = fallback_resolutions[current_fallback].height;
-        target_bpp    = fallback_resolutions[current_fallback].bpp;
-        goto retry;
+success:;
+        size_t mode_count;
+        fb->mode_list = get_mode_list(&mode_count, gop);
+        fb->mode_count = mode_count;
+
+        fbs_count++;
     }
 
+    pmm_free(handles, handles_size);
+
     gop_force_16 = false;
-    return false;
+
+    *_fbs_count = fbs_count;
 }
 
 #endif
diff --git a/common/drivers/gop.h b/common/drivers/gop.h
index 32440be4..100c6bcf 100644
--- a/common/drivers/gop.h
+++ b/common/drivers/gop.h
@@ -6,20 +6,13 @@
 #include <stdint.h>
 #include <stddef.h>
 #include <stdbool.h>
-#include <efi.h>
 #include <lib/fb.h>
 
-bool init_gop(struct fb_info *ret,
+void init_gop(struct fb_info **ret, size_t *_fbs_count,
               uint64_t target_width, uint64_t target_height, uint16_t target_bpp);
 
-struct fb_info *gop_get_mode_list(size_t *count);
-
 extern bool gop_force_16;
 
-extern bool gop_ready;
-extern EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
-extern EFI_HANDLE gop_handle;
-
 #endif
 
 #endif
diff --git a/common/drivers/vbe.c b/common/drivers/vbe.c
index 2ecae69d..2b84ab80 100644
--- a/common/drivers/vbe.c
+++ b/common/drivers/vbe.c
@@ -187,8 +187,6 @@ bool init_vbe(struct fb_info *ret,
               uint16_t target_width, uint16_t target_height, uint16_t target_bpp) {
     printv("vbe: Initialising...\n");
 
-    ret->default_res = false;
-
     size_t current_fallback = 0;
 
     struct vbe_info_struct vbe_info;
@@ -216,8 +214,6 @@ bool init_vbe(struct fb_info *ret,
     };
 
     if (!target_width || !target_height || !target_bpp) {
-        ret->default_res = true;
-
         struct edid_info_struct *edid_info = get_edid_info();
         if (edid_info != NULL) {
             int edid_width   = (int)edid_info->det_timing_desc1[2];
@@ -293,8 +289,6 @@ retry:
     }
 
 fallback:
-    ret->default_res = true;
-
     if (current_fallback < SIZEOF_ARRAY(fallback_resolutions)) {
         target_width  = fallback_resolutions[current_fallback].width;
         target_height = fallback_resolutions[current_fallback].height;
diff --git a/common/drivers/vga_textmode.c b/common/drivers/vga_textmode.c
index eeab80ee..d49007e2 100644
--- a/common/drivers/vga_textmode.c
+++ b/common/drivers/vga_textmode.c
@@ -244,15 +244,12 @@ static void text_deinit(struct term_context *_ctx, void (*_free)(void *, size_t)
         _free(ctx->front_buffer, VD_ROWS * VD_COLS);
         ctx->front_buffer = NULL;
     }
-}
 
-static struct textmode_context term_local_struct;
+    pmm_free(ctx, sizeof(struct textmode_context));
+}
 
 void vga_textmode_init(bool managed) {
-    if (term != NULL) {
-        term->deinit(term, pmm_free);
-        term = NULL;
-    }
+    term_notready();
 
     if (quiet) {
         return;
@@ -266,8 +263,13 @@ void vga_textmode_init(bool managed) {
         current_video_mode = 0x3;
     }
 
-    struct textmode_context *ctx = &term_local_struct;
-    term = &term_local_struct.term;
+    terms = ext_mem_alloc(sizeof(void *));
+    terms_i = 1;
+
+    terms[0] = ext_mem_alloc(sizeof(struct textmode_context));
+
+    struct term_context *term = terms[0];
+    struct textmode_context *ctx = (void *)term;
 
     if (ctx->back_buffer == NULL) {
         ctx->back_buffer = ext_mem_alloc(VD_ROWS * VD_COLS);
diff --git a/common/entry.s3.c b/common/entry.s3.c
index 28f0ff6a..a4a0bb27 100644
--- a/common/entry.s3.c
+++ b/common/entry.s3.c
@@ -38,6 +38,8 @@ noreturn void uefi_entry(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
 
     gST->ConOut->EnableCursor(gST->ConOut, false);
 
+    init_memmap();
+
     term_fallback();
 
     status = gBS->SetWatchdogTimer(0, 0x10000, 0, NULL);
@@ -45,8 +47,6 @@ noreturn void uefi_entry(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
         print("WARNING: Failed to disable watchdog timer!\n");
     }
 
-    init_memmap();
-
 #if defined (__x86_64__) || defined (__i386__)
     init_gdt();
 #endif
@@ -130,5 +130,7 @@ noreturn void stage3_common(void) {
     init_io_apics();
 #endif
 
+    term_notready();
+
     menu(true);
 }
diff --git a/common/lib/fb.c b/common/lib/fb.c
index 95057e1c..5d6f32e1 100644
--- a/common/lib/fb.c
+++ b/common/lib/fb.c
@@ -6,24 +6,23 @@
 #include <drivers/gop.h>
 #include <mm/pmm.h>
 
-bool fb_init(struct fb_info *ret,
+void fb_init(struct fb_info **ret, size_t *_fbs_count,
              uint64_t target_width, uint64_t target_height, uint16_t target_bpp) {
-    bool r;
-
 #if defined (BIOS)
-    r = init_vbe(ret, target_width, target_height, target_bpp);
-#elif defined (UEFI)
-    r = init_gop(ret, target_width, target_height, target_bpp);
-#endif
+    *ret = ext_mem_alloc(sizeof(struct fb_info));
+    if (init_vbe(*ret, target_width, target_height, target_bpp)) {
+        *_fbs_count = 1;
 
-    return r;
-}
-
-struct fb_info *fb_get_mode_list(size_t *count) {
-#if defined (BIOS)
-    return vbe_get_mode_list(count);
+        (*ret)->edid = get_edid_info();
+        size_t mode_count;
+        (*ret)->mode_list = vbe_get_mode_list(&mode_count);
+        (*ret)->mode_count = mode_count;
+    } else {
+        *_fbs_count = 0;
+        pmm_free(*ret, sizeof(struct fb_info));
+    }
 #elif defined (UEFI)
-    return gop_get_mode_list(count);
+    init_gop(ret, _fbs_count, target_width, target_height, target_bpp);
 #endif
 }
 
diff --git a/common/lib/fb.h b/common/lib/fb.h
index 83c65170..61f914fa 100644
--- a/common/lib/fb.h
+++ b/common/lib/fb.h
@@ -3,6 +3,7 @@
 
 #include <stdint.h>
 #include <stddef.h>
+#include <drivers/edid.h>
 
 struct resolution {
     uint64_t width;
@@ -15,23 +16,25 @@ struct fb_info {
     uint64_t framebuffer_width;
     uint64_t framebuffer_height;
     uint16_t framebuffer_bpp;
-    uint8_t  memory_model;
-    uint8_t  red_mask_size;
-    uint8_t  red_mask_shift;
-    uint8_t  green_mask_size;
-    uint8_t  green_mask_shift;
-    uint8_t  blue_mask_size;
-    uint8_t  blue_mask_shift;
-
-    bool default_res;
+    uint8_t memory_model;
+    uint8_t red_mask_size;
+    uint8_t red_mask_shift;
+    uint8_t green_mask_size;
+    uint8_t green_mask_shift;
+    uint8_t blue_mask_size;
+    uint8_t blue_mask_shift;
+
     uint64_t framebuffer_addr;
+
+    struct edid_info_struct *edid;
+
+    uint64_t mode_count;
+    struct fb_info *mode_list;
 };
 
-bool fb_init(struct fb_info *ret,
+void fb_init(struct fb_info **ret, size_t *_fbs_count,
              uint64_t target_width, uint64_t target_height, uint16_t target_bpp);
 
-struct fb_info *fb_get_mode_list(size_t *count);
-
 void fb_clear(struct fb_info *fb);
 
 #endif
diff --git a/common/lib/gterm.c b/common/lib/gterm.c
index 4d288702..ec3b7e7f 100644
--- a/common/lib/gterm.c
+++ b/common/lib/gterm.c
@@ -360,8 +360,6 @@ static const uint8_t builtin_font[] = {
   0x00, 0x00, 0x00, 0x00
 };
 
-struct fb_info fbinfo;
-
 static struct image *background;
 
 static size_t margin = 64;
@@ -390,10 +388,10 @@ static inline uint32_t colour_blend(uint32_t fg, uint32_t bg) {
     return ARGB(0, r, g, b);
 }
 
-static uint32_t blend_gradient_from_box(size_t x, size_t y, uint32_t bg_px, uint32_t hex) {
+static uint32_t blend_gradient_from_box(struct fb_info *fb, size_t x, size_t y, uint32_t bg_px, uint32_t hex) {
     size_t distance, x_distance, y_distance;
-    size_t gradient_stop_x = fbinfo.framebuffer_width - margin;
-    size_t gradient_stop_y = fbinfo.framebuffer_height - margin;
+    size_t gradient_stop_x = fb->framebuffer_width - margin;
+    size_t gradient_stop_y = fb->framebuffer_height - margin;
 
     if (x < margin)
         x_distance = margin - x;
@@ -428,7 +426,7 @@ static size_t fixedp6_to_int(fixedp6 value) { return value / 64; }
 static fixedp6 int_to_fixedp6(size_t value) { return value * 64; }
 
 // Draw rect at coordinates, copying from the image to the fb and canvas, applying fn on every pixel
-__attribute__((always_inline)) static inline void genloop(size_t xstart, size_t xend, size_t ystart, size_t yend, uint32_t (*blend)(size_t x, size_t y, uint32_t orig)) {
+__attribute__((always_inline)) static inline void genloop(struct fb_info *fb, size_t xstart, size_t xend, size_t ystart, size_t yend, uint32_t (*blend)(struct fb_info *fb, size_t x, size_t y, uint32_t orig)) {
     uint8_t *img = background->img;
     const size_t img_width = background->img_width, img_height = background->img_height, img_pitch = background->pitch, colsize = background->bpp / 8;
 
@@ -437,10 +435,10 @@ __attribute__((always_inline)) static inline void genloop(size_t xstart, size_t
         for (size_t y = ystart; y < yend; y++) {
             size_t image_y = y % img_height, image_x = xstart % img_width;
             const size_t off = img_pitch * (img_height - 1 - image_y);
-            size_t canvas_off = fbinfo.framebuffer_width * y;
+            size_t canvas_off = fb->framebuffer_width * y;
             for (size_t x = xstart; x < xend; x++) {
                 uint32_t img_pixel = *(uint32_t*)(img + image_x * colsize + off);
-                uint32_t i = blend(x, y, img_pixel);
+                uint32_t i = blend(fb, x, y, img_pixel);
                 bg_canvas[canvas_off + x] = i;
                 if (image_x++ == img_width) image_x = 0; // image_x = x % img_width, but modulo is too expensive
             }
@@ -451,10 +449,10 @@ __attribute__((always_inline)) static inline void genloop(size_t xstart, size_t
         for (size_t y = ystart; y < yend; y++) {
             size_t image_y = y - background->y_displacement;
             const size_t off = img_pitch * (img_height - 1 - image_y);
-            size_t canvas_off = fbinfo.framebuffer_width * y;
+            size_t canvas_off = fb->framebuffer_width * y;
             if (image_y >= background->y_size) { /* external part */
                 for (size_t x = xstart; x < xend; x++) {
-                    uint32_t i = blend(x, y, background->back_colour);
+                    uint32_t i = blend(fb, x, y, background->back_colour);
                     bg_canvas[canvas_off + x] = i;
                 }
             }
@@ -463,7 +461,7 @@ __attribute__((always_inline)) static inline void genloop(size_t xstart, size_t
                     size_t image_x = (x - background->x_displacement);
                     bool x_external = image_x >= background->x_size;
                     uint32_t img_pixel = *(uint32_t*)(img + image_x * colsize + off);
-                    uint32_t i = blend(x, y, x_external ? background->back_colour : img_pixel);
+                    uint32_t i = blend(fb, x, y, x_external ? background->back_colour : img_pixel);
                     bg_canvas[canvas_off + x] = i;
                 }
             }
@@ -474,15 +472,15 @@ __attribute__((always_inline)) static inline void genloop(size_t xstart, size_t
     // so you can set x = xstart * ratio, and increment by ratio at each iteration
     case IMAGE_STRETCHED:
         for (size_t y = ystart; y < yend; y++) {
-            size_t img_y = (y * img_height) / fbinfo.framebuffer_height; // calculate Y with full precision
+            size_t img_y = (y * img_height) / fb->framebuffer_height; // calculate Y with full precision
             size_t off = img_pitch * (img_height - 1 - img_y);
-            size_t canvas_off = fbinfo.framebuffer_width * y;
+            size_t canvas_off = fb->framebuffer_width * y;
 
-            size_t ratio = int_to_fixedp6(img_width) / fbinfo.framebuffer_width;
+            size_t ratio = int_to_fixedp6(img_width) / fb->framebuffer_width;
             fixedp6 img_x = ratio * xstart;
             for (size_t x = xstart; x < xend; x++) {
                 uint32_t img_pixel = *(uint32_t*)(img + fixedp6_to_int(img_x) * colsize + off);
-                uint32_t i = blend(x, y, img_pixel);
+                uint32_t i = blend(fb, x, y, img_pixel);
                 bg_canvas[canvas_off + x] = i;
                 img_x += ratio;
             }
@@ -491,17 +489,17 @@ __attribute__((always_inline)) static inline void genloop(size_t xstart, size_t
     }
 }
 
-static uint32_t blend_external(size_t x, size_t y, uint32_t orig) { (void)x; (void)y; return orig; }
-static uint32_t blend_internal(size_t x, size_t y, uint32_t orig) { (void)x; (void)y; return colour_blend(default_bg, orig); }
-static uint32_t blend_margin(size_t x, size_t y, uint32_t orig) { return blend_gradient_from_box(x, y, orig, default_bg); }
+static uint32_t blend_external(struct fb_info *fb, size_t x, size_t y, uint32_t orig) { (void)fb; (void)x; (void)y; return orig; }
+static uint32_t blend_internal(struct fb_info *fb, size_t x, size_t y, uint32_t orig) { (void)fb; (void)x; (void)y; return colour_blend(default_bg, orig); }
+static uint32_t blend_margin(struct fb_info *fb, size_t x, size_t y, uint32_t orig) { return blend_gradient_from_box(fb, x, y, orig, default_bg); }
 
-static void loop_external(size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(xstart, xend, ystart, yend, blend_external); }
-static void loop_margin(size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(xstart, xend, ystart, yend, blend_margin); }
-static void loop_internal(size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(xstart, xend, ystart, yend, blend_internal); }
+static void loop_external(struct fb_info *fb, size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(fb, xstart, xend, ystart, yend, blend_external); }
+static void loop_margin(struct fb_info *fb, size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(fb, xstart, xend, ystart, yend, blend_margin); }
+static void loop_internal(struct fb_info *fb, size_t xstart, size_t xend, size_t ystart, size_t yend) { genloop(fb, xstart, xend, ystart, yend, blend_internal); }
 
-static void generate_canvas(void) {
+static void generate_canvas(struct fb_info *fb) {
     if (background) {
-        bg_canvas_size = fbinfo.framebuffer_width * fbinfo.framebuffer_height * sizeof(uint32_t);
+        bg_canvas_size = fb->framebuffer_width * fb->framebuffer_height * sizeof(uint32_t);
         bg_canvas = ext_mem_alloc(bg_canvas_size);
 
         int64_t margin_no_gradient = (int64_t)margin - margin_gradient;
@@ -510,99 +508,76 @@ static void generate_canvas(void) {
             margin_no_gradient = 0;
         }
 
-        size_t scan_stop_x = fbinfo.framebuffer_width - margin_no_gradient;
-        size_t scan_stop_y = fbinfo.framebuffer_height - margin_no_gradient;
+        size_t scan_stop_x = fb->framebuffer_width - margin_no_gradient;
+        size_t scan_stop_y = fb->framebuffer_height - margin_no_gradient;
 
-        loop_external(0, fbinfo.framebuffer_width, 0, margin_no_gradient);
-        loop_external(0, fbinfo.framebuffer_width, scan_stop_y, fbinfo.framebuffer_height);
-        loop_external(0, margin_no_gradient, margin_no_gradient, scan_stop_y);
-        loop_external(scan_stop_x, fbinfo.framebuffer_width, margin_no_gradient, scan_stop_y);
+        loop_external(fb, 0, fb->framebuffer_width, 0, margin_no_gradient);
+        loop_external(fb, 0, fb->framebuffer_width, scan_stop_y, fb->framebuffer_height);
+        loop_external(fb, 0, margin_no_gradient, margin_no_gradient, scan_stop_y);
+        loop_external(fb, scan_stop_x, fb->framebuffer_width, margin_no_gradient, scan_stop_y);
 
-        size_t gradient_stop_x = fbinfo.framebuffer_width - margin;
-        size_t gradient_stop_y = fbinfo.framebuffer_height - margin;
+        size_t gradient_stop_x = fb->framebuffer_width - margin;
+        size_t gradient_stop_y = fb->framebuffer_height - margin;
 
         if (margin_gradient) {
-            loop_margin(margin_no_gradient, scan_stop_x, margin_no_gradient, margin);
-            loop_margin(margin_no_gradient, scan_stop_x, gradient_stop_y, scan_stop_y);
-            loop_margin(margin_no_gradient, margin, margin, gradient_stop_y);
-            loop_margin(gradient_stop_x, scan_stop_x, margin, gradient_stop_y);
+            loop_margin(fb, margin_no_gradient, scan_stop_x, margin_no_gradient, margin);
+            loop_margin(fb, margin_no_gradient, scan_stop_x, gradient_stop_y, scan_stop_y);
+            loop_margin(fb, margin_no_gradient, margin, margin, gradient_stop_y);
+            loop_margin(fb, gradient_stop_x, scan_stop_x, margin, gradient_stop_y);
         }
 
-        loop_internal(margin, gradient_stop_x, margin, gradient_stop_y);
+        loop_internal(fb, margin, gradient_stop_x, margin, gradient_stop_y);
     } else {
         bg_canvas = NULL;
     }
 }
 
-static bool last_serial = false;
-static char *last_config = NULL;
+bool gterm_init(struct fb_info **_fbs, size_t *_fbs_count,
+                char *config, size_t width, size_t height) {
+    static struct fb_info *fbs;
+    static size_t fbs_count;
+
+    static bool prev_valid = false;
+    static char *prev_config;
+    static size_t prev_width, prev_height;
+
+    if (prev_valid && config == prev_config && width == prev_width && height == prev_height) {
+        *_fbs = fbs;
+        *_fbs_count = fbs_count;
+        reset_term();
+        return true;
+    }
+
+    prev_valid = false;
 
-bool gterm_init(char *config, size_t width, size_t height) {
     if (quiet) {
-        if (term != NULL) {
-            term->deinit(term, pmm_free);
-            term = NULL;
-        }
+        term_notready();
         return false;
     }
 
 #if defined (UEFI)
     if (serial || COM_OUTPUT) {
-        if (term != NULL) {
-            term->deinit(term, pmm_free);
-            term = NULL;
-        }
         term_fallback();
         return true;
     }
 #endif
 
-    if (term != NULL
-     && term_backend == GTERM
-     && fbinfo.default_res == true
-     && width == 0
-     && height == 0
-     && fbinfo.framebuffer_bpp == 32
-     && serial == last_serial
-     && config == last_config) {
-        term->clear(term, true);
-        return true;
-    }
+    term_notready();
 
-    if (term != NULL
-     && term_backend == GTERM
-     && fbinfo.framebuffer_width == width
-     && fbinfo.framebuffer_height == height
-     && fbinfo.framebuffer_bpp == 32
-     && serial == last_serial
-     && config == last_config) {
-        term->clear(term, true);
-        return true;
-    }
+    // We force bpp to 32
+    fb_init(&fbs, &fbs_count, width, height, 32);
 
-    if (term != NULL) {
-        term->deinit(term, pmm_free);
-        term = NULL;
+    if (_fbs != NULL) {
+        *_fbs = fbs;
     }
-
-    // We force bpp to 32
-    if (!fb_init(&fbinfo, width, height, 32)) {
-        return false;
+    if (_fbs_count != NULL) {
+        *_fbs_count = fbs_count;
     }
 
-    // Ensure this is xRGB8888, we only support that for the menu
-    if (fbinfo.red_mask_size    != 8
-     || fbinfo.red_mask_shift   != 16
-     || fbinfo.green_mask_size  != 8
-     || fbinfo.green_mask_shift != 8
-     || fbinfo.blue_mask_size   != 8
-     || fbinfo.blue_mask_shift  != 0) {
+    if (fbs_count == 0) {
         return false;
     }
 
-    last_serial = serial;
-    last_config = config;
-
     // default scheme
     margin = 64;
     margin_gradient = 4;
@@ -716,20 +691,6 @@ bool gterm_init(char *config, size_t width, size_t height) {
         margin_gradient = strtoui(theme_margin_gradient, NULL, 10);
     }
 
-    if (background != NULL) {
-        char *background_layout = config_get_value(config, 0, "TERM_WALLPAPER_STYLE");
-        if (background_layout != NULL && strcmp(background_layout, "centered") == 0) {
-            char *background_colour = config_get_value(config, 0, "TERM_BACKDROP");
-            if (background_colour == NULL)
-                background_colour = "0";
-            uint32_t bg_col = strtoui(background_colour, NULL, 16);
-            image_make_centered(background, fbinfo.framebuffer_width, fbinfo.framebuffer_height, bg_col);
-        } else if (background_layout != NULL && strcmp(background_layout, "tiled") == 0) {
-        } else {
-            image_make_stretched(background, fbinfo.framebuffer_width, fbinfo.framebuffer_height);
-        }
-    }
-
     size_t font_width = 8;
     size_t font_height = 16;
     size_t font_size = (font_width * font_height * FBTERM_FONT_GLYPHS) / 8;
@@ -789,38 +750,105 @@ no_load_font:;
         }
     }
 
-    generate_canvas();
+    terms_i = 0;
+    terms = ext_mem_alloc(fbs_count * sizeof(void *));
 
-    term = fbterm_init(ext_mem_alloc,
-                (void *)(uintptr_t)fbinfo.framebuffer_addr,
-                fbinfo.framebuffer_width, fbinfo.framebuffer_height, fbinfo.framebuffer_pitch,
-                bg_canvas,
-                ansi_colours, ansi_bright_colours,
-                &default_bg, &default_fg,
-                &default_bg_bright, &default_fg_bright,
-                font, font_width, font_height, font_spacing,
-                font_scale_x, font_scale_y,
-                margin);
+    for (size_t i = 0; i < fbs_count; i++) {
+        struct fb_info *fb = &fbs[i];
 
-    pmm_free(font, FONT_MAX);
-    if (bg_canvas != NULL) {
-        pmm_free(bg_canvas, bg_canvas_size);
+        // Ensure this is xRGB8888, we only support that for the menu
+        if (fb->red_mask_size    != 8
+         || fb->red_mask_shift   != 16
+         || fb->green_mask_size  != 8
+         || fb->green_mask_shift != 8
+         || fb->blue_mask_size   != 8
+         || fb->blue_mask_shift  != 0) {
+            continue;
+        }
+
+        if (background != NULL) {
+            char *background_layout = config_get_value(config, 0, "TERM_WALLPAPER_STYLE");
+            if (background_layout != NULL && strcmp(background_layout, "centered") == 0) {
+                char *background_colour = config_get_value(config, 0, "TERM_BACKDROP");
+                if (background_colour == NULL)
+                    background_colour = "0";
+                uint32_t bg_col = strtoui(background_colour, NULL, 16);
+                image_make_centered(background, fb->framebuffer_width, fb->framebuffer_height, bg_col);
+            } else if (background_layout != NULL && strcmp(background_layout, "tiled") == 0) {
+            } else {
+                image_make_stretched(background, fb->framebuffer_width, fb->framebuffer_height);
+            }
+        }
+
+        generate_canvas(fb);
+
+        terms[terms_i] = fbterm_init(ext_mem_alloc,
+                            (void *)(uintptr_t)fb->framebuffer_addr,
+                            fb->framebuffer_width, fb->framebuffer_height, fb->framebuffer_pitch,
+                            bg_canvas,
+                            ansi_colours, ansi_bright_colours,
+                            &default_bg, &default_fg,
+                            &default_bg_bright, &default_fg_bright,
+                            font, font_width, font_height, font_spacing,
+                            font_scale_x, font_scale_y,
+                            margin);
+
+        if (terms[terms_i] != NULL) {
+            terms_i++;
+        }
+
+        if (bg_canvas != NULL) {
+            pmm_free(bg_canvas, bg_canvas_size);
+        }
     }
 
-    if (term == NULL) {
+    pmm_free(font, FONT_MAX);
+
+    if (terms_i == 0) {
         return false;
     }
 
-    if (serial) {
-        term->cols = term->cols > 80 ? 80 : term->cols;
-        term->rows = term->rows > 24 ? 24 : term->rows;
+    for (size_t i = 0; i < terms_i; i++) {
+        struct term_context *term = terms[i];
+
+        if (serial) {
+            term->cols = term->cols > 80 ? 80 : term->cols;
+            term->rows = term->rows > 24 ? 24 : term->rows;
+        }
+    }
+
+    size_t min_cols = (size_t)-1;
+    size_t min_rows = (size_t)-1;
+
+    for (size_t i = 0; i < terms_i; i++) {
+        struct term_context *term = terms[i];
+
+        if (term->cols < min_cols) {
+            min_cols = term->cols;
+        }
+
+        if (term->rows < min_rows) {
+            min_rows = term->rows;
+        }
     }
 
-    term->in_bootloader = true;
+    for (size_t i = 0; i < terms_i; i++) {
+        struct term_context *term = terms[i];
+
+        term->cols = min_cols;
+        term->rows = min_rows;
 
-    term_context_reinit(term);
+        term->in_bootloader = true;
+
+        term_context_reinit(term);
+    }
 
     term_backend = GTERM;
 
+    prev_config = config;
+    prev_height = height;
+    prev_width = width;
+    prev_valid = true;
+
     return true;
 }
diff --git a/common/lib/gterm.h b/common/lib/gterm.h
index a680e7f2..891aea68 100644
--- a/common/lib/gterm.h
+++ b/common/lib/gterm.h
@@ -5,8 +5,7 @@
 #include <stdbool.h>
 #include <lib/fb.h>
 
-extern struct fb_info fbinfo;
-
-bool gterm_init(char *config, size_t width, size_t height);
+bool gterm_init(struct fb_info **ret, size_t *_fbs_count,
+                char *config, size_t width, size_t height);
 
 #endif
diff --git a/common/lib/panic.s2.c b/common/lib/panic.s2.c
index 901e968b..23634504 100644
--- a/common/lib/panic.s2.c
+++ b/common/lib/panic.s2.c
@@ -54,6 +54,9 @@ noreturn void panic(bool allow_menu, const char *fmt, ...) {
 
         getchar();
 
+        // This fixes a crash
+        term_notready();
+
         menu(false);
 /*
         fb_clear(&fbinfo);
diff --git a/common/lib/print.s2.c b/common/lib/print.s2.c
index 5464958d..9d89e044 100644
--- a/common/lib/print.s2.c
+++ b/common/lib/print.s2.c
@@ -222,9 +222,7 @@ out:
 #if defined (BIOS)
         if (stage3_loaded) {
 #endif
-            if (term != NULL) {
-                term_write(term, print_buf, print_buf_i);
-            }
+            FOR_TERM(term_write(TERM, print_buf, print_buf_i));
 #if defined (BIOS)
         } else {
             s2_print(print_buf, print_buf_i);
diff --git a/common/lib/readline.c b/common/lib/readline.c
index 6f9f91e6..1a03d9dc 100644
--- a/common/lib/readline.c
+++ b/common/lib/readline.c
@@ -352,34 +352,34 @@ again:
 
 static void reprint_string(int x, int y, const char *s) {
     size_t orig_x, orig_y;
-    term->cursor_enabled = false;
-    term->get_cursor_pos(term, &orig_x, &orig_y);
+    FOR_TERM(TERM->cursor_enabled = false);
+    terms[0]->get_cursor_pos(terms[0], &orig_x, &orig_y);
     set_cursor_pos_helper(x, y);
     print("%s", s);
     set_cursor_pos_helper(orig_x, orig_y);
-    term->cursor_enabled = true;
+    FOR_TERM(TERM->cursor_enabled = true);
 }
 
 static void cursor_back(void) {
     size_t x, y;
-    term->get_cursor_pos(term, &x, &y);
+    terms[0]->get_cursor_pos(terms[0], &x, &y);
     if (x) {
         x--;
     } else if (y) {
         y--;
-        x = term->cols - 1;
+        x = terms[0]->cols - 1;
     }
     set_cursor_pos_helper(x, y);
 }
 
 static void cursor_fwd(void) {
     size_t x, y;
-    term->get_cursor_pos(term, &x, &y);
-    if (x < term->cols - 1) {
+    terms[0]->get_cursor_pos(terms[0], &x, &y);
+    if (x < terms[0]->cols - 1) {
         x++;
     } else {
         x = 0;
-        if (y < term->rows - 1) {
+        if (y < terms[0]->rows - 1) {
             y++;
         }
     }
@@ -387,20 +387,20 @@ static void cursor_fwd(void) {
 }
 
 void readline(const char *orig_str, char *buf, size_t limit) {
-    bool prev_autoflush = term->autoflush;
-    term->autoflush = false;
+    bool prev_autoflush = terms[0]->autoflush;
+    FOR_TERM(TERM->autoflush = false);
 
     size_t orig_str_len = strlen(orig_str);
     memmove(buf, orig_str, orig_str_len);
     buf[orig_str_len] = 0;
 
     size_t orig_x, orig_y;
-    term->get_cursor_pos(term, &orig_x, &orig_y);
+    terms[0]->get_cursor_pos(terms[0], &orig_x, &orig_y);
 
     print("%s", orig_str);
 
     for (size_t i = orig_str_len; ; ) {
-        term->double_buffer_flush(term);
+        FOR_TERM(TERM->double_buffer_flush(TERM));
         int c = getchar();
         switch (c) {
             case GETCHAR_CURSOR_LEFT:
@@ -460,11 +460,11 @@ void readline(const char *orig_str, char *buf, size_t limit) {
                     buf[i] = c;
                     i++;
                     size_t prev_x, prev_y;
-                    term->get_cursor_pos(term, &prev_x, &prev_y);
+                    terms[0]->get_cursor_pos(terms[0], &prev_x, &prev_y);
                     cursor_fwd();
                     reprint_string(orig_x, orig_y, buf);
                     // If cursor has wrapped around, move the line start position up one row
-                    if (prev_x == term->cols - 1 && prev_y == term->rows - 1) {
+                    if (prev_x == terms[0]->cols - 1 && prev_y == terms[0]->rows - 1) {
                         orig_y--;
                         print("\n\e[J");  // Clear the bottom line
                     }
@@ -474,6 +474,6 @@ void readline(const char *orig_str, char *buf, size_t limit) {
     }
 
 out:
-    term->double_buffer_flush(term);
-    term->autoflush = prev_autoflush;
+    FOR_TERM(TERM->double_buffer_flush(TERM));
+    FOR_TERM(TERM->autoflush = prev_autoflush);
 }
diff --git a/common/lib/term.c b/common/lib/term.c
index e0d802e5..66a23f3f 100644
--- a/common/lib/term.c
+++ b/common/lib/term.c
@@ -8,89 +8,28 @@
 #include <drivers/vga_textmode.h>
 #include <term/backends/framebuffer.h>
 
+#if defined (BIOS)
 int current_video_mode = -1;
-int term_backend = _NOT_READY;
+#endif
 
-struct term_context *term;
+struct term_context **terms = NULL;
+size_t terms_i = 0;
 
-static struct term_context term_local_struct;
+int term_backend = _NOT_READY;
 
-// --- notready ---
+void term_notready(void) {
+    for (size_t i = 0; i < terms_i; i++) {
+        struct term_context *term = terms[i];
 
-static void notready_raw_putchar(struct term_context *ctx, uint8_t c) {
-    (void)ctx;
-    (void)c;
-}
-static void notready_clear(struct term_context *ctx, bool move) {
-    (void)ctx;
-    (void)move;
-}
-static void notready_void(struct term_context *ctx) {
-    (void)ctx;
-}
-static void notready_set_cursor_pos(struct term_context *ctx, size_t x, size_t y) {
-    (void)ctx;
-    (void)x; (void)y;
-}
-static void notready_get_cursor_pos(struct term_context *ctx, size_t *x, size_t *y) {
-    (void)ctx;
-    *x = 0;
-    *y = 0;
-}
-static void notready_size_t(struct term_context *ctx, size_t n) {
-    (void)ctx;
-    (void)n;
-}
-static void notready_move_character(struct term_context *ctx, size_t a, size_t b, size_t c, size_t d) {
-    (void)ctx;
-    (void)a; (void)b; (void)c; (void)d;
-}
-static void notready_uint32_t(struct term_context *ctx, uint32_t n) {
-    (void)ctx;
-    (void)n;
-}
-static void notready_deinit(struct term_context *ctx, void (*_free)(void *, size_t)) {
-    (void)ctx;
-    (void)_free;
-}
-
-static void term_notready(void) {
-    if (term != NULL) {
         term->deinit(term, pmm_free);
-        term = NULL;
     }
 
-    term = &term_local_struct;
-
-    term->raw_putchar = notready_raw_putchar;
-    term->clear = notready_clear;
-    term->set_cursor_pos = notready_set_cursor_pos;
-    term->get_cursor_pos = notready_get_cursor_pos;
-    term->set_text_fg = notready_size_t;
-    term->set_text_bg = notready_size_t;
-    term->set_text_fg_bright = notready_size_t;
-    term->set_text_bg_bright = notready_size_t;
-    term->set_text_fg_rgb = notready_uint32_t;
-    term->set_text_bg_rgb = notready_uint32_t;
-    term->set_text_fg_default = notready_void;
-    term->set_text_bg_default = 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->full_refresh = notready_void;
-    term->deinit = notready_deinit;
-
-    term->cols = 80;
-    term->rows = 24;
+    pmm_free(terms, terms_i * sizeof(void *));
 
-    term_backend = _NOT_READY;
-    term_context_reinit(term);
+    terms_i = 0;
+    terms = NULL;
 
-    term->in_bootloader = true;
+    term_backend = _NOT_READY;
 }
 
 // --- fallback ---
@@ -156,7 +95,7 @@ static void fallback_scroll(struct term_context *ctx) {
     (void)ctx;
     size_t x, y;
     fallback_get_cursor_pos(NULL, &x, &y);
-    fallback_set_cursor_pos(NULL, term->cols - 1, term->rows - 1);
+    fallback_set_cursor_pos(NULL, ctx->cols - 1, ctx->rows - 1);
     fallback_raw_putchar(NULL, ' ');
     fallback_set_cursor_pos(NULL, x, y);
 }
@@ -167,7 +106,7 @@ static size_t cursor_x = 0, cursor_y = 0;
 
 static void fallback_scroll(struct term_context *ctx) {
     (void)ctx;
-    gST->ConOut->SetCursorPosition(gST->ConOut, term->cols - 1, term->rows - 1);
+    gST->ConOut->SetCursorPosition(gST->ConOut, ctx->cols - 1, ctx->rows - 1);
     CHAR16 string[2];
     string[0] = ' ';
     string[1] = 0;
@@ -176,7 +115,7 @@ static void fallback_scroll(struct term_context *ctx) {
 }
 
 static void fallback_raw_putchar(struct term_context *ctx, uint8_t c) {
-    if (!ctx->scroll_enabled && cursor_x == term->cols - 1 && cursor_y == term->rows - 1) {
+    if (!ctx->scroll_enabled && cursor_x == ctx->cols - 1 && cursor_y == ctx->rows - 1) {
         return;
     }
     gST->ConOut->EnableCursor(gST->ConOut, true);
@@ -184,9 +123,9 @@ static void fallback_raw_putchar(struct term_context *ctx, uint8_t c) {
     string[0] = c;
     string[1] = 0;
     gST->ConOut->OutputString(gST->ConOut, string);
-    if (++cursor_x >= term->cols) {
+    if (++cursor_x >= ctx->cols) {
         cursor_x = 0;
-        if (++cursor_y >= term->rows) {
+        if (++cursor_y >= ctx->rows) {
             cursor_y--;
         }
     }
@@ -204,7 +143,7 @@ static void fallback_clear(struct term_context *ctx, bool move) {
 
 static void fallback_set_cursor_pos(struct term_context *ctx, size_t x, size_t y) {
     (void)ctx;
-    if (x >= term->cols || y >= term->rows) {
+    if (x >= ctx->cols || y >= ctx->rows) {
         return;
     }
     gST->ConOut->SetCursorPosition(gST->ConOut, x, y);
@@ -219,13 +158,43 @@ static void fallback_get_cursor_pos(struct term_context *ctx, size_t *x, size_t
 }
 #endif
 
+static bool dummy_handle(void) {
+    return true;
+}
+
 void term_fallback(void) {
     term_notready();
 
 #if defined (UEFI)
     if (!efi_boot_services_exited) {
 #endif
+
+        terms = ext_mem_alloc(sizeof(void *));
+        terms_i = 1;
+
+        terms[0] = ext_mem_alloc(sizeof(struct term_context));
+
+        struct term_context *term = terms[0];
+
         fallback_clear(NULL, true);
+
+        term->set_text_fg = (void *)dummy_handle;
+        term->set_text_bg = (void *)dummy_handle;
+        term->set_text_fg_bright = (void *)dummy_handle;
+        term->set_text_bg_bright = (void *)dummy_handle;
+        term->set_text_fg_rgb = (void *)dummy_handle;
+        term->set_text_bg_rgb = (void *)dummy_handle;
+        term->set_text_fg_default = (void *)dummy_handle;
+        term->set_text_bg_default = (void *)dummy_handle;
+        term->move_character = (void *)dummy_handle;
+        term->revscroll = (void *)dummy_handle;
+        term->swap_palette = (void *)dummy_handle;
+        term->save_state = (void *)dummy_handle;
+        term->restore_state = (void *)dummy_handle;
+        term->double_buffer_flush = (void *)dummy_handle;
+        term->full_refresh = (void *)dummy_handle;
+        term->deinit = (void *)dummy_handle;
+
         term->raw_putchar = fallback_raw_putchar;
         term->clear = fallback_clear;
         term->set_cursor_pos = fallback_set_cursor_pos;
@@ -258,7 +227,7 @@ extern void set_cursor_pos_helper(size_t x, size_t y);
 static uint8_t xfer_buf[TERM_XFER_CHUNK];
 #endif
 
-static uint64_t context_size(void) {
+static uint64_t context_size(struct term_context *term) {
     switch (term_backend) {
 #if defined (BIOS)
         case TEXTMODE:
@@ -279,7 +248,7 @@ static uint64_t context_size(void) {
     }
 }
 
-static void context_save(uint64_t buf) {
+static void context_save(struct term_context *term, uint64_t buf) {
     switch (term_backend) {
 #if defined (BIOS)
         case TEXTMODE: {
@@ -314,7 +283,7 @@ static void context_save(uint64_t buf) {
     }
 }
 
-static void context_restore(uint64_t buf) {
+static void context_restore(struct term_context *term, uint64_t buf) {
     switch (term_backend) {
 #if defined (BIOS)
         case TEXTMODE: {
@@ -349,7 +318,7 @@ static void context_restore(uint64_t buf) {
     }
 }
 
-void _term_write(uint64_t buf, uint64_t count) {
+void _term_write(struct term_context *term, uint64_t buf, uint64_t count) {
     switch (count) {
         case TERM_OOB_OUTPUT_GET: {
             memcpy32to64(buf, (uint64_t)(uintptr_t)&term->oob_output, sizeof(uint64_t));
@@ -360,16 +329,16 @@ void _term_write(uint64_t buf, uint64_t count) {
             return;
         }
         case TERM_CTX_SIZE: {
-            uint64_t ret = context_size();
+            uint64_t ret = context_size(term);
             memcpy32to64(buf, (uint64_t)(uintptr_t)&ret, sizeof(uint64_t));
             return;
         }
         case TERM_CTX_SAVE: {
-            context_save(buf);
+            context_save(term, buf);
             return;
         }
         case TERM_CTX_RESTORE: {
-            context_restore(buf);
+            context_restore(term, buf);
             return;
         }
         case TERM_FULL_REFRESH: {
diff --git a/common/lib/term.h b/common/lib/term.h
index d9bc2553..d0cbd5b4 100644
--- a/common/lib/term.h
+++ b/common/lib/term.h
@@ -13,10 +13,14 @@ enum {
     FALLBACK
 };
 
+#if defined (BIOS)
 extern int current_video_mode;
-extern int term_backend;
+#endif
 
-extern struct term_context *term;
+extern struct term_context **terms;
+extern size_t terms_i;
+
+extern int term_backend;
 
 #define TERM_CTX_SIZE ((uint64_t)(-1))
 #define TERM_CTX_SAVE ((uint64_t)(-2))
@@ -25,18 +29,32 @@ extern struct term_context *term;
 #define TERM_OOB_OUTPUT_GET ((uint64_t)(-10))
 #define TERM_OOB_OUTPUT_SET ((uint64_t)(-11))
 
+#define FOR_TERM(...) do { \
+    for (size_t FOR_TERM_i = 0; FOR_TERM_i < terms_i; FOR_TERM_i++) { \
+        struct term_context *TERM = terms[FOR_TERM_i]; \
+        __VA_ARGS__ \
+        ; \
+    } \
+} while (0)
+
 inline void reset_term(void) {
-    term->autoflush = true;
-    term->cursor_enabled = true;;
-    print("\e[2J\e[H");
-    term->double_buffer_flush(term);
+    for (size_t i = 0; i < terms_i; i++) {
+        struct term_context *term = terms[i];
+
+        print("\e[2J\e[H");
+        term_context_reinit(term);
+        term->in_bootloader = true;
+        term->cursor_enabled = true;
+        term->double_buffer_flush(term);
+    }
 }
 
 inline void set_cursor_pos_helper(size_t x, size_t y) {
     print("\e[%u;%uH", (int)y + 1, (int)x + 1);
 }
 
+void term_notready(void);
 void term_fallback(void);
-void _term_write(uint64_t buf, uint64_t count);
+void _term_write(struct term_context *term, uint64_t buf, uint64_t count);
 
 #endif
diff --git a/common/linker_bios.ld.in b/common/linker_bios.ld.in
index 3e0132b4..882be123 100644
--- a/common/linker_bios.ld.in
+++ b/common/linker_bios.ld.in
@@ -43,7 +43,9 @@ SECTIONS
         term_write = .;
         term_backend = .;
         term_fallback = .;
-        term = .;
+        term_notready = .;
+        terms = .;
+        terms_i = .;
         stage3_addr = .;
 #else
 #ifdef LINKER_NOS2MAP
diff --git a/common/menu.c b/common/menu.c
index 60a40d1e..f4040d45 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -168,17 +168,17 @@ static void putchar_tokencol(int type, char c) {
 static bool editor_no_term_reset = false;
 
 char *config_entry_editor(const char *title, const char *orig_entry) {
-    term->autoflush = false;
+    FOR_TERM(TERM->autoflush = false);
 
-    term->cursor_enabled = true;
+    FOR_TERM(TERM->cursor_enabled = true);
 
     print("\e[2J\e[H");
 
     size_t cursor_offset  = 0;
     size_t entry_size     = strlen(orig_entry);
-    size_t _window_size   = term->rows - 8;
+    size_t _window_size   = terms[0]->rows - 8;
     size_t window_offset  = 0;
-    size_t line_size      = term->cols - 2;
+    size_t line_size      = terms[0]->cols - 2;
 
     bool display_overflow_error = false;
 
@@ -214,12 +214,12 @@ refresh:
     invalid_syntax = false;
 
     print("\e[2J\e[H");
-    term->cursor_enabled = false;
+    FOR_TERM(TERM->cursor_enabled = false);
     {
         size_t x, y;
         print("\n");
-        term->get_cursor_pos(term, &x, &y);
-        set_cursor_pos_helper(term->cols / 2 - DIV_ROUNDUP(strlen(menu_branding), 2), y);
+        terms[0]->get_cursor_pos(terms[0], &x, &y);
+        set_cursor_pos_helper(terms[0]->cols / 2 - DIV_ROUNDUP(strlen(menu_branding), 2), y);
         print("\e[3%sm%s\e[37m", menu_branding_colour, menu_branding);
         print("\n\n");
     }
@@ -227,7 +227,7 @@ refresh:
     print("    \e[32mESC\e[0m Discard and Exit    \e[32mF10\e[0m Boot\n\n");
 
     print(serial ? "/" : "\xda");
-    for (size_t i = 0; i < term->cols - 2; i++) {
+    for (size_t i = 0; i < terms[0]->cols - 2; i++) {
         switch (i) {
             case 1: case 2: case 3:
                 if (window_offset > 0) {
@@ -237,7 +237,7 @@ refresh:
                 // FALLTHRU
             default: {
                 size_t title_length = strlen(title);
-                if (i == (term->cols / 2) - DIV_ROUNDUP(title_length, 2) - 1) {
+                if (i == (terms[0]->cols / 2) - DIV_ROUNDUP(title_length, 2) - 1) {
                     print("%s", title);
                     i += title_length - 1;
                 } else {
@@ -248,7 +248,7 @@ refresh:
     }
     size_t tmpx, tmpy;
 
-    term->get_cursor_pos(term, &tmpx, &tmpy);
+    terms[0]->get_cursor_pos(terms[0], &tmpx, &tmpy);
     print(serial ? "\\" : "\xbf");
     set_cursor_pos_helper(0, tmpy + 1);
     print(serial ? "|" : "\xb3");
@@ -264,20 +264,20 @@ refresh:
          && current_line <  window_offset + window_size
          && current_line >= window_offset) {
             size_t x, y;
-            term->get_cursor_pos(term, &x, &y);
+            terms[0]->get_cursor_pos(terms[0], &x, &y);
             if (i == cursor_offset) {
                 cursor_x = x;
                 cursor_y = y;
                 printed_cursor = true;
             }
-            set_cursor_pos_helper(term->cols - 1, y);
+            set_cursor_pos_helper(terms[0]->cols - 1, y);
             if (current_line == window_offset + window_size - 1) {
-                term->get_cursor_pos(term, &tmpx, &tmpy);
+                terms[0]->get_cursor_pos(terms[0], &tmpx, &tmpy);
                 print(serial ? "|" : "\xb3");
                 set_cursor_pos_helper(0, tmpy + 1);
                 print(serial ? "\\" : "\xc0");
             } else {
-                term->get_cursor_pos(term, &tmpx, &tmpy);
+                terms[0]->get_cursor_pos(terms[0], &tmpx, &tmpy);
                 print(serial ? "|" : "\xb3");
                 set_cursor_pos_helper(0, tmpy + 1);
                 print(serial ? "|" : "\xb3");
@@ -295,7 +295,7 @@ refresh:
             if (current_line <  window_offset + window_size
              && current_line >= window_offset) {
                 if (i == cursor_offset) {
-                    term->get_cursor_pos(term, &cursor_x, &cursor_y);
+                    terms[0]->get_cursor_pos(terms[0], &cursor_x, &cursor_y);
                     printed_cursor = true;
                 }
                 if (syntax_highlighting_enabled) {
@@ -305,8 +305,8 @@ refresh:
                 }
                 printed_early = true;
                 size_t x, y;
-                term->get_cursor_pos(term, &x, &y);
-                if (y == term->rows - 3) {
+                terms[0]->get_cursor_pos(terms[0], &x, &y);
+                if (y == terms[0]->rows - 3) {
                     print(serial ? ">" : "\x1a");
                     set_cursor_pos_helper(0, y + 1);
                     print(serial ? "\\" : "\xc0");
@@ -323,7 +323,7 @@ refresh:
          && current_line <  window_offset + window_size
          && current_line >= window_offset
          && !printed_cursor) {
-            term->get_cursor_pos(term, &cursor_x, &cursor_y);
+            terms[0]->get_cursor_pos(terms[0], &cursor_x, &cursor_y);
             printed_cursor = true;
         }
 
@@ -370,35 +370,35 @@ refresh:
     // syntax error alert
     if (validation_enabled) {
         size_t x, y;
-        term->get_cursor_pos(term, &x, &y);
-        set_cursor_pos_helper(0, term->rows - 1);
-        term->scroll_enabled = false;
+        terms[0]->get_cursor_pos(terms[0], &x, &y);
+        set_cursor_pos_helper(0, terms[0]->rows - 1);
+        FOR_TERM(TERM->scroll_enabled = false);
         if (invalid_syntax) {
             print("\e[31mConfiguration is INVALID.\e[0m");
         } else {
             print("\e[32mConfiguration is valid.\e[0m");
         }
-        term->scroll_enabled = true;
+        FOR_TERM(TERM->scroll_enabled = true);
         set_cursor_pos_helper(x, y);
     }
 
     if (current_line - window_offset < window_size) {
         size_t x, y;
         for (size_t i = 0; i < (window_size - (current_line - window_offset)) - 1; i++) {
-            term->get_cursor_pos(term, &x, &y);
-            set_cursor_pos_helper(term->cols - 1, y);
+            terms[0]->get_cursor_pos(terms[0], &x, &y);
+            set_cursor_pos_helper(terms[0]->cols - 1, y);
             print(serial ? "|" : "\xb3");
             set_cursor_pos_helper(0, y + 1);
             print(serial ? "|" : "\xb3");
         }
-        term->get_cursor_pos(term, &x, &y);
-        set_cursor_pos_helper(term->cols - 1, y);
+        terms[0]->get_cursor_pos(terms[0], &x, &y);
+        set_cursor_pos_helper(terms[0]->cols - 1, y);
         print(serial ? "|" : "\xb3");
         set_cursor_pos_helper(0, y + 1);
         print(serial ? "\\" : "\xc0");
     }
 
-    for (size_t i = 0; i < term->cols - 2; i++) {
+    for (size_t i = 0; i < terms[0]->cols - 2; i++) {
         switch (i) {
             case 1: case 2: case 3:
                 if (current_line - window_offset >= window_size) {
@@ -410,22 +410,22 @@ refresh:
                 print(serial ? "-" : "\xc4");
         }
     }
-    term->get_cursor_pos(term, &tmpx, &tmpy);
+    terms[0]->get_cursor_pos(terms[0], &tmpx, &tmpy);
     print(serial ? "/" : "\xd9");
     set_cursor_pos_helper(0, tmpy + 1);
 
     if (display_overflow_error) {
-        term->scroll_enabled = false;
+        FOR_TERM(TERM->scroll_enabled = false);
         print("\e[31mText buffer not big enough, delete something instead.");
-        term->scroll_enabled = true;
+        FOR_TERM(TERM->scroll_enabled = true);
         display_overflow_error = false;
     }
 
     // Hack to redraw the cursor
     set_cursor_pos_helper(cursor_x, cursor_y);
-    term->cursor_enabled = true;
+    FOR_TERM(TERM->cursor_enabled = true);
 
-    term->double_buffer_flush(term);
+    FOR_TERM(TERM->double_buffer_flush(TERM));
 
     int c = getchar();
     size_t buffer_len = strlen(buffer);
@@ -584,7 +584,7 @@ static void menu_init_term(void) {
         if (menu_resolution != NULL)
             parse_resolution(&req_width, &req_height, &req_bpp, menu_resolution);
 
-        if (!quiet && !gterm_init(NULL, req_width, req_height)) {
+        if (!quiet && !gterm_init(NULL, NULL, NULL, req_width, req_height)) {
 #if defined (BIOS)
             vga_textmode_init(true);
 #elif defined (UEFI)
@@ -625,10 +625,6 @@ noreturn void _menu(bool first_run) {
 #endif
     }
 
-    if (!first_run) {
-        term_fallback();
-    }
-
     if (bad_config == false) {
 #if defined (UEFI)
         if (init_config_disk(boot_volume)) {
@@ -721,23 +717,23 @@ noreturn void _menu(bool first_run) {
     size_t tree_offset = 0;
 
 refresh:
-    if (selected_entry >= tree_offset + term->rows - 10) {
-        tree_offset = selected_entry - (term->rows - 11);
+    if (selected_entry >= tree_offset + terms[0]->rows - 10) {
+        tree_offset = selected_entry - (terms[0]->rows - 11);
     }
     if (selected_entry < tree_offset) {
         tree_offset = selected_entry;
     }
 
-    term->autoflush = false;
+    FOR_TERM(TERM->autoflush = false);
 
-    term->cursor_enabled = false;
+    FOR_TERM(TERM->cursor_enabled = false);
 
     print("\e[2J\e[H");
     {
         size_t x, y;
         print("\n");
-        term->get_cursor_pos(term, &x, &y);
-        set_cursor_pos_helper(term->cols / 2 - DIV_ROUNDUP(strlen(menu_branding), 2), y);
+        terms[0]->get_cursor_pos(terms[0], &x, &y);
+        set_cursor_pos_helper(terms[0]->cols / 2 - DIV_ROUNDUP(strlen(menu_branding), 2), y);
         print("\e[3%sm%s\e[37m", menu_branding_colour, menu_branding);
         print("\n\n\n\n");
     }
@@ -746,14 +742,14 @@ refresh:
         if (quiet) {
             quiet = false;
             menu_init_term();
-            term->autoflush = false;
-            term->cursor_enabled = false;
+            FOR_TERM(TERM->autoflush = false);
+            FOR_TERM(TERM->cursor_enabled = false);
         }
         print("Config file %s.\n\n", config_ready ? "contains no valid entries" : "not found");
         print("For information on the format of Limine config entries, consult CONFIG.md in\n");
         print("the root of the Limine source repository.\n\n");
         print("Press a key to enter the Limine console...");
-        term->double_buffer_flush(term);
+        FOR_TERM(TERM->double_buffer_flush(TERM));
         getchar();
         reset_term();
         console();
@@ -761,10 +757,10 @@ refresh:
 
     {   // Draw box around boot menu
         size_t x, y;
-        term->get_cursor_pos(term, &x, &y);
+        terms[0]->get_cursor_pos(terms[0], &x, &y);
 
         print(serial ? "/" : "\xda");
-        for (size_t i = 0; i < term->cols - 2; i++) {
+        for (size_t i = 0; i < terms[0]->cols - 2; i++) {
             switch (i) {
                 case 1: case 2: case 3:
                     if (tree_offset > 0) {
@@ -777,16 +773,16 @@ refresh:
         }
         print(serial ? "\\" : "\xbf");
 
-        for (size_t i = y + 1; i < term->rows - 2; i++) {
+        for (size_t i = y + 1; i < terms[0]->rows - 2; i++) {
             set_cursor_pos_helper(0, i);
             print(serial ? "|" : "\xb3");
-            set_cursor_pos_helper(term->cols - 1, i);
+            set_cursor_pos_helper(terms[0]->cols - 1, i);
             print(serial ? "|" : "\xb3");
         }
-        set_cursor_pos_helper(0, term->rows - 2);
+        set_cursor_pos_helper(0, terms[0]->rows - 2);
 
         print(serial ? "\\" : "\xc0");
-        for (size_t i = 0; i < term->cols - 2; i++) {
+        for (size_t i = 0; i < terms[0]->cols - 2; i++) {
             print(serial ? "-" : "\xc4");
         }
         print(serial ? "/" : "\xd9");
@@ -794,15 +790,15 @@ refresh:
         set_cursor_pos_helper(x, y + 2);
     }
 
-    size_t max_entries = print_tree(tree_offset, term->rows - 10, serial ? "|   " : "\xb3   ", 0, 0, selected_entry, menu_tree,
+    size_t max_entries = print_tree(tree_offset, terms[0]->rows - 10, serial ? "|   " : "\xb3   ", 0, 0, selected_entry, menu_tree,
                                  &selected_menu_entry);
 
     {
         size_t x, y;
-        term->get_cursor_pos(term, &x, &y);
+        terms[0]->get_cursor_pos(terms[0], &x, &y);
 
-        if (tree_offset + (term->rows - 10) < max_entries) {
-            set_cursor_pos_helper(2, term->rows - 2);
+        if (tree_offset + (terms[0]->rows - 10) < max_entries) {
+            set_cursor_pos_helper(2, terms[0]->rows - 2);
             print(serial ? "vvv" : "\x19\x19\x19");
         }
 
@@ -813,7 +809,7 @@ refresh:
             print("    \e[32mARROWS\e[0m Select    \e[32mENTER\e[0m %s",
                   selected_menu_entry->expanded ? "Collapse" : "Expand");
         }
-        set_cursor_pos_helper(term->cols - 13, 3);
+        set_cursor_pos_helper(terms[0]->cols - 13, 3);
         print("\e[32mC\e[0m Console");
         set_cursor_pos_helper(x, y);
     }
@@ -826,11 +822,11 @@ refresh:
     if (skip_timeout == false) {
         print("\n\n");
         for (size_t i = timeout; i; i--) {
-            set_cursor_pos_helper(0, term->rows - 1);
-            term->scroll_enabled = false;
+            set_cursor_pos_helper(0, terms[0]->rows - 1);
+            FOR_TERM(TERM->scroll_enabled = false);
             print("\e[2K\e[32mBooting automatically in \e[92m%u\e[32m, press any key to stop the countdown...\e[0m", i);
-            term->scroll_enabled = true;
-            term->double_buffer_flush(term);
+            FOR_TERM(TERM->scroll_enabled = true);
+            FOR_TERM(TERM->double_buffer_flush(TERM));
             if ((c = pit_sleep_and_quit_on_keypress(1))) {
                 skip_timeout = true;
                 if (quiet) {
@@ -839,21 +835,21 @@ refresh:
                     goto timeout_aborted;
                 }
                 print("\e[2K");
-                term->double_buffer_flush(term);
+                FOR_TERM(TERM->double_buffer_flush(TERM));
                 goto timeout_aborted;
             }
         }
         goto autoboot;
     }
 
-    set_cursor_pos_helper(0, term->rows - 1);
+    set_cursor_pos_helper(0, terms[0]->rows - 1);
     if (selected_menu_entry->comment != NULL) {
-        term->scroll_enabled = false;
+        FOR_TERM(TERM->scroll_enabled = false);
         print("\e[36m%s\e[0m", selected_menu_entry->comment);
-        term->scroll_enabled = true;
+        FOR_TERM(TERM->scroll_enabled = true);
     }
 
-    term->double_buffer_flush(term);
+    FOR_TERM(TERM->double_buffer_flush(TERM));
 
     for (;;) {
         c = getchar();
@@ -896,7 +892,7 @@ timeout_aborted:
                 }
                 if (!quiet) {
                     if (term_backend == FALLBACK) {
-                        if (!gterm_init(NULL, 0, 0)) {
+                        if (!gterm_init(NULL, NULL, NULL, 0, 0)) {
 #if defined (BIOS)
                             vga_textmode_init(true);
 #elif defined (UEFI)
diff --git a/common/protos/chainload.c b/common/protos/chainload.c
index c1066dfa..5e0040dd 100644
--- a/common/protos/chainload.c
+++ b/common/protos/chainload.c
@@ -218,10 +218,7 @@ noreturn void efi_chainload_file(char *config, char *cmdline, struct file_handle
     pmm_free(_ptr, image->size);
     fclose(image);
 
-    if (term != NULL) {
-        term->deinit(term, pmm_free);
-        term = NULL;
-    }
+    term_notready();
 
     size_t req_width = 0, req_height = 0, req_bpp = 0;
 
@@ -229,9 +226,9 @@ noreturn void efi_chainload_file(char *config, char *cmdline, struct file_handle
     if (resolution != NULL)
         parse_resolution(&req_width, &req_height, &req_bpp, resolution);
 
-    struct fb_info fbinfo;
-    if (!fb_init(&fbinfo, req_width, req_height, req_bpp))
-        panic(true, "chainload: Unable to set video mode");
+    struct fb_info *fbinfo;
+    size_t fb_count;
+    fb_init(&fbinfo, &fb_count, req_width, req_height, req_bpp);
 
     size_t cmdline_len = strlen(cmdline);
     CHAR16 *new_cmdline;
diff --git a/common/protos/limine.c b/common/protos/limine.c
index 6aee412d..1163cfa7 100644
--- a/common/protos/limine.c
+++ b/common/protos/limine.c
@@ -278,9 +278,10 @@ static void callback_shim(struct term_context *ctx, uint64_t a, uint64_t b, uint
     actual_callback(term_arg, a, b, c, d);
 }
 
+// TODO pair with specific terminal
 static void term_write_shim(uint64_t context, uint64_t buf, uint64_t count) {
     (void)context;
-    _term_write(buf, count);
+    _term_write(terms[0], buf, count);
 }
 
 noreturn void limine_load(char *config, char *cmdline) {
@@ -673,10 +674,11 @@ FEAT_END
         parse_resolution(&req_width, &req_height, &req_bpp, resolution);
     }
 
-    struct fb_info fb;
-
     uint64_t *term_fb_ptr = NULL;
 
+    struct fb_info *fbs;
+    size_t fbs_count;
+
     // Terminal feature
 FEAT_START
     struct limine_terminal_request *terminal_request = get_request(LIMINE_TERMINAL_REQUEST);
@@ -696,11 +698,11 @@ FEAT_START
 
     char *term_conf_override_s = config_get_value(config, 0, "TERM_CONFIG_OVERRIDE");
     if (term_conf_override_s != NULL && strcmp(term_conf_override_s, "yes") == 0) {
-        if (!gterm_init(config, req_width, req_height)) {
+        if (!gterm_init(&fbs, &fbs_count, config, req_width, req_height)) {
             goto term_fail;
         }
     } else {
-        if (!gterm_init(NULL, req_width, req_height)) {
+        if (!gterm_init(&fbs, &fbs_count, NULL, req_width, req_height)) {
             goto term_fail;
         }
     }
@@ -712,10 +714,8 @@ term_fail:
         break; // next feature
     }
 
-    fb = fbinfo;
-
     if (terminal_request->callback != 0) {
-        term->callback = callback_shim;
+        terms[0]->callback = callback_shim;
 
 #if defined (__i386__)
         actual_callback = (void *)limine_term_callback;
@@ -744,8 +744,8 @@ term_fail:
 
     term_fb_ptr = &terminal->framebuffer;
 
-    terminal->columns = term->cols;
-    terminal->rows = term->rows;
+    terminal->columns = terms[0]->cols;
+    terminal->rows = terms[0]->rows;
 
     uint64_t *term_list = ext_mem_alloc(1 * sizeof(uint64_t));
     term_list[0] = reported_addr(terminal);
@@ -758,27 +758,26 @@ term_fail:
     goto skip_fb_init;
 FEAT_END
 
-    if (term != NULL) {
-        term->deinit(term, pmm_free);
-        term = NULL;
-    }
+    term_notready();
 
-    if (!fb_init(&fb, req_width, req_height, req_bpp)) {
+    fb_init(&fbs, &fbs_count, req_width, req_height, req_bpp);
+    if (fbs_count == 0) {
         goto no_fb;
     }
 
 skip_fb_init:
-    memmap_alloc_range(fb.framebuffer_addr,
-                       (uint64_t)fb.framebuffer_pitch * fb.framebuffer_height,
-                       MEMMAP_FRAMEBUFFER, 0, false, false, true);
+    for (size_t i = 0; i < fbs_count; i++) {
+        memmap_alloc_range(fbs[i].framebuffer_addr,
+                           (uint64_t)fbs[i].framebuffer_pitch * fbs[i].framebuffer_height,
+                           MEMMAP_FRAMEBUFFER, 0, false, false, true);
+    }
 
     // Framebuffer feature
 FEAT_START
-    // For now we only support 1 framebuffer
-    struct limine_framebuffer *fbp = ext_mem_alloc(sizeof(struct limine_framebuffer));
+    struct limine_framebuffer *fbp = ext_mem_alloc(fbs_count * sizeof(struct limine_framebuffer));
 
     if (term_fb_ptr != NULL) {
-        *term_fb_ptr = reported_addr(fbp);
+        *term_fb_ptr = reported_addr(&fbp[0]);
     }
 
     struct limine_framebuffer_request *framebuffer_request = get_request(LIMINE_FRAMEBUFFER_REQUEST);
@@ -789,43 +788,41 @@ FEAT_START
     struct limine_framebuffer_response *framebuffer_response =
         ext_mem_alloc(sizeof(struct limine_framebuffer_response));
 
-    struct edid_info_struct *edid_info = get_edid_info();
-    if (edid_info != NULL) {
-        fbp->edid_size = sizeof(struct edid_info_struct);
-        fbp->edid = reported_addr(edid_info);
-    }
-
     framebuffer_response->revision = 1;
 
-    size_t modes_count;
-    struct fb_info *modes = fb_get_mode_list(&modes_count);
-    if (modes != NULL) {
-        uint64_t *modes_list = ext_mem_alloc(modes_count * sizeof(uint64_t));
-        for (size_t i = 0; i < modes_count; i++) {
-            modes[i].memory_model = LIMINE_FRAMEBUFFER_RGB;
-            modes_list[i] = reported_addr(&modes[i]);
+    uint64_t *fb_list = ext_mem_alloc(fbs_count * sizeof(uint64_t));
+
+    for (size_t i = 0; i < fbs_count; i++) {
+        uint64_t *modes_list = ext_mem_alloc(fbs[i].mode_count * sizeof(uint64_t));
+        for (size_t j = 0; j < fbs[i].mode_count; j++) {
+            fbs[i].mode_list[j].memory_model = LIMINE_FRAMEBUFFER_RGB;
+            modes_list[j] = reported_addr(&fbs[i].mode_list[j]);
+        }
+        fbp[i].modes = reported_addr(modes_list);
+        fbp[i].mode_count = fbs[i].mode_count;
+
+        if (fbs[i].edid != NULL) {
+            fbp[i].edid_size = sizeof(struct edid_info_struct);
+            fbp[i].edid = reported_addr(fbs[i].edid);
         }
-        fbp->modes = reported_addr(modes_list);
-        fbp->mode_count = modes_count;
+
+        fbp[i].memory_model     = LIMINE_FRAMEBUFFER_RGB;
+        fbp[i].address          = reported_addr((void *)(uintptr_t)fbs[i].framebuffer_addr);
+        fbp[i].width            = fbs[i].framebuffer_width;
+        fbp[i].height           = fbs[i].framebuffer_height;
+        fbp[i].bpp              = fbs[i].framebuffer_bpp;
+        fbp[i].pitch            = fbs[i].framebuffer_pitch;
+        fbp[i].red_mask_size    = fbs[i].red_mask_size;
+        fbp[i].red_mask_shift   = fbs[i].red_mask_shift;
+        fbp[i].green_mask_size  = fbs[i].green_mask_size;
+        fbp[i].green_mask_shift = fbs[i].green_mask_shift;
+        fbp[i].blue_mask_size   = fbs[i].blue_mask_size;
+        fbp[i].blue_mask_shift  = fbs[i].blue_mask_shift;
+
+        fb_list[i] = reported_addr(&fbp[i]);
     }
 
-    fbp->memory_model     = LIMINE_FRAMEBUFFER_RGB;
-    fbp->address          = reported_addr((void *)(uintptr_t)fb.framebuffer_addr);
-    fbp->width            = fb.framebuffer_width;
-    fbp->height           = fb.framebuffer_height;
-    fbp->bpp              = fb.framebuffer_bpp;
-    fbp->pitch            = fb.framebuffer_pitch;
-    fbp->red_mask_size    = fb.red_mask_size;
-    fbp->red_mask_shift   = fb.red_mask_shift;
-    fbp->green_mask_size  = fb.green_mask_size;
-    fbp->green_mask_shift = fb.green_mask_shift;
-    fbp->blue_mask_size   = fb.blue_mask_size;
-    fbp->blue_mask_shift  = fb.blue_mask_shift;
-
-    uint64_t *fb_list = ext_mem_alloc(1 * sizeof(uint64_t));
-    fb_list[0] = reported_addr(fbp);
-
-    framebuffer_response->framebuffer_count = 1;
+    framebuffer_response->framebuffer_count = fbs_count;
     framebuffer_response->framebuffers = reported_addr(fb_list);
 
     framebuffer_request->response = reported_addr(framebuffer_response);
@@ -860,18 +857,21 @@ FEAT_END
 #endif
 
 #if defined (__aarch64__)
-    uint64_t fb_attr = 0x00;
-    if (fb.framebuffer_addr) {
+    // Find the most restrictive caching mode from all framebuffers to use
+    uint64_t fb_attr = (uint64_t)-1;
+
+    for (size_t i = 0; i < fbs_count; i++) {
         int el = current_el();
         uint64_t res;
 
+        // Figure out the caching mode used for this particular framebuffer
         if (el == 1) {
             asm volatile (
                     "at s1e1w, %1\n\t"
                     "isb\n\t"
                     "mrs %0, par_el1"
                     : "=r"(res)
-                    : "r"(fb.framebuffer_addr)
+                    : "r"(fbs[i].framebuffer_addr)
                     : "memory");
         } else if (el == 2) {
             asm volatile (
@@ -879,7 +879,7 @@ FEAT_END
                     "isb\n\t"
                     "mrs %0, par_el1"
                     : "=r"(res)
-                    : "r"(fb.framebuffer_addr)
+                    : "r"(fbs[i].framebuffer_addr)
                     : "memory");
         } else {
             panic(false, "Unexpected EL in limine_load");
@@ -888,8 +888,26 @@ FEAT_END
         if (res & 1)
             panic(false, "Address translation for framebuffer failed");
 
-        fb_attr = res >> 56;
+        uint64_t new_attr = res >> 56;
+
+        // Use whatever we find first
+        if (fb_attr == (uint64_t)-1)
+            fb_attr = new_attr;
+        // Prefer Device memory over Normal memory
+        else if ((fb_attr & 0b11110000) && !(new_attr & 0b11110000))
+            fb_attr = new_attr;
+        // Prefer tighter Device memory (lower values)
+        else if (!(fb_attr & 0b11110000) && !(new_attr & 0b11110000) && fb_attr > new_attr)
+            fb_attr = new_attr;
+        // Use Normal non-cacheable otherwise (avoid trying to figure out how to downgrade inner vs outer).
+        else if ((fb_attr & 0b11110000) && (new_attr & 0b11110000))
+            fb_attr = 0b01000100; // Inner&outer Non-cacheable
+        // Otherwise do nothing (fb_attr is already more restrictive than new_attr).
     }
+
+    // If no framebuffers are found, just zero out the MAIR entry
+    if (fb_attr == (uint64_t)-1)
+        fb_attr = 0;
 #endif
 
     void *stack = ext_mem_alloc(stack_size) + stack_size;
@@ -1034,10 +1052,8 @@ FEAT_START
 FEAT_END
 
     // Clear terminal for kernels that will use the Limine terminal
-    if (term != NULL) {
-        term_write(term, "\e[2J\e[H", 7);
-        term->in_bootloader = false;
-    }
+    FOR_TERM(term_write(TERM, "\e[2J\e[H", 7));
+    FOR_TERM(TERM->in_bootloader = false);
 
 #if defined (__x86_64__) || defined (__i386__)
 #if defined (BIOS)
diff --git a/common/protos/linux.c b/common/protos/linux.c
index 6e46b13f..5ae70b99 100644
--- a/common/protos/linux.c
+++ b/common/protos/linux.c
@@ -495,10 +495,7 @@ noreturn void linux_load(char *config, char *cmdline) {
     // Video
     ///////////////////////////////////////
 
-    if (term != NULL) {
-        term->deinit(term, pmm_free);
-        term = NULL;
-    }
+    term_notready();
 
     struct screen_info *screen_info = &boot_params->screen_info;
 
@@ -518,11 +515,13 @@ noreturn void linux_load(char *config, char *cmdline) {
     if (resolution != NULL)
         parse_resolution(&req_width, &req_height, &req_bpp, resolution);
 
-    struct fb_info fbinfo;
+    struct fb_info *fbs;
+    size_t fbs_count;
 #if defined (UEFI)
     gop_force_16 = true;
 #endif
-    if (!fb_init(&fbinfo, req_width, req_height, req_bpp)) {
+    fb_init(&fbs, &fbs_count, req_width, req_height, req_bpp);
+    if (fbs_count == 0) {
 #if defined (UEFI)
         goto no_fb;
 #elif defined (BIOS)
@@ -531,8 +530,8 @@ set_textmode:;
 
         screen_info->orig_video_mode = 3;
         screen_info->orig_video_ega_bx = 3;
-        screen_info->orig_video_lines = term->rows;
-        screen_info->orig_video_cols = term->cols;
+        screen_info->orig_video_lines = 25;
+        screen_info->orig_video_cols = 80;
         screen_info->orig_video_points = 16;
 
         screen_info->orig_video_isVGA = VIDEO_TYPE_VGAC;
@@ -540,19 +539,23 @@ set_textmode:;
     } else {
         screen_info->capabilities   = VIDEO_CAPABILITY_64BIT_BASE | VIDEO_CAPABILITY_SKIP_QUIRKS;
         screen_info->flags          = VIDEO_FLAGS_NOCURSOR;
-        screen_info->lfb_base       = (uint32_t)fbinfo.framebuffer_addr;
-        screen_info->ext_lfb_base   = (uint32_t)(fbinfo.framebuffer_addr >> 32);
-        screen_info->lfb_size       = fbinfo.framebuffer_pitch * fbinfo.framebuffer_height;
-        screen_info->lfb_width      = fbinfo.framebuffer_width;
-        screen_info->lfb_height     = fbinfo.framebuffer_height;
-        screen_info->lfb_depth      = fbinfo.framebuffer_bpp;
-        screen_info->lfb_linelength = fbinfo.framebuffer_pitch;
-        screen_info->red_size       = fbinfo.red_mask_size;
-        screen_info->red_pos        = fbinfo.red_mask_shift;
-        screen_info->green_size     = fbinfo.green_mask_size;
-        screen_info->green_pos      = fbinfo.green_mask_shift;
-        screen_info->blue_size      = fbinfo.blue_mask_size;
-        screen_info->blue_pos       = fbinfo.blue_mask_shift;
+        screen_info->lfb_base       = (uint32_t)fbs[0].framebuffer_addr;
+        screen_info->ext_lfb_base   = (uint32_t)(fbs[0].framebuffer_addr >> 32);
+        screen_info->lfb_size       = fbs[0].framebuffer_pitch * fbs[0].framebuffer_height;
+        screen_info->lfb_width      = fbs[0].framebuffer_width;
+        screen_info->lfb_height     = fbs[0].framebuffer_height;
+        screen_info->lfb_depth      = fbs[0].framebuffer_bpp;
+        screen_info->lfb_linelength = fbs[0].framebuffer_pitch;
+        screen_info->red_size       = fbs[0].red_mask_size;
+        screen_info->red_pos        = fbs[0].red_mask_shift;
+        screen_info->green_size     = fbs[0].green_mask_size;
+        screen_info->green_pos      = fbs[0].green_mask_shift;
+        screen_info->blue_size      = fbs[0].blue_mask_size;
+        screen_info->blue_pos       = fbs[0].blue_mask_shift;
+
+        if (fbs[0].edid != NULL) {
+            memcpy(&boot_params->edid_info, fbs[0].edid, sizeof(struct edid_info_struct));
+        }
 
 #if defined (BIOS)
         screen_info->orig_video_isVGA = VIDEO_TYPE_VLFB;
@@ -564,12 +567,6 @@ set_textmode:;
 #if defined (UEFI)
 no_fb:;
 #endif
-    struct edid_info_struct *edid_info = get_edid_info();
-
-    if (edid_info != NULL) {
-        memcpy(&boot_params->edid_info, edid_info, sizeof(struct edid_info_struct));
-    }
-
     ///////////////////////////////////////
     // RSDP
     ///////////////////////////////////////
diff --git a/common/protos/multiboot1.c b/common/protos/multiboot1.c
index 1d7c6929..808cd657 100644
--- a/common/protos/multiboot1.c
+++ b/common/protos/multiboot1.c
@@ -302,77 +302,62 @@ noreturn void multiboot1_load(char *config, char *cmdline) {
     multiboot1_info->bootloader_name = (uint32_t)(size_t)lowmem_bootname - mb1_info_slide;
     multiboot1_info->flags |= (1 << 9);
 
-    if (term != NULL) {
-        term->deinit(term, pmm_free);
-        term = NULL;
-    }
+    term_notready();
+
+    size_t req_width = 0;
+    size_t req_height = 0;
+    size_t req_bpp = 0;
 
     if (header.flags & (1 << 2)) {
-        size_t req_width  = header.fb_width;
-        size_t req_height = header.fb_height;
-        size_t req_bpp    = header.fb_bpp;
+        req_width = header.fb_width;
+        req_height = header.fb_height;
+        req_bpp = header.fb_bpp;
 
         if (header.fb_mode == 0) {
+#if defined (UEFI)
+modeset:;
+#endif
             char *resolution = config_get_value(config, 0, "RESOLUTION");
             if (resolution != NULL)
                 parse_resolution(&req_width, &req_height, &req_bpp, resolution);
 
-            struct fb_info fbinfo;
-            if (!fb_init(&fbinfo, req_width, req_height, req_bpp)) {
+            struct fb_info *fbs;
+            size_t fbs_count;
+            fb_init(&fbs, &fbs_count, req_width, req_height, req_bpp);
+            if (fbs_count == 0) {
 #if defined (UEFI)
                 goto skip_modeset;
 #elif defined (BIOS)
+textmode:
                 vga_textmode_init(false);
 
                 multiboot1_info->fb_addr    = 0xb8000;
-                multiboot1_info->fb_width   = term->cols;
-                multiboot1_info->fb_height  = term->rows;
+                multiboot1_info->fb_width   = 80;
+                multiboot1_info->fb_height  = 25;
                 multiboot1_info->fb_bpp     = 16;
-                multiboot1_info->fb_pitch   = 2 * term->cols;
+                multiboot1_info->fb_pitch   = 2 * 80;
                 multiboot1_info->fb_type    = 2;
 #endif
             } else {
-                multiboot1_info->fb_addr    = (uint64_t)fbinfo.framebuffer_addr;
-                multiboot1_info->fb_width   = fbinfo.framebuffer_width;
-                multiboot1_info->fb_height  = fbinfo.framebuffer_height;
-                multiboot1_info->fb_bpp     = fbinfo.framebuffer_bpp;
-                multiboot1_info->fb_pitch   = fbinfo.framebuffer_pitch;
+                multiboot1_info->fb_addr    = (uint64_t)fbs[0].framebuffer_addr;
+                multiboot1_info->fb_width   = fbs[0].framebuffer_width;
+                multiboot1_info->fb_height  = fbs[0].framebuffer_height;
+                multiboot1_info->fb_bpp     = fbs[0].framebuffer_bpp;
+                multiboot1_info->fb_pitch   = fbs[0].framebuffer_pitch;
                 multiboot1_info->fb_type    = 1;
-                multiboot1_info->fb_red_mask_size    = fbinfo.red_mask_size;
-                multiboot1_info->fb_red_mask_shift   = fbinfo.red_mask_shift;
-                multiboot1_info->fb_green_mask_size  = fbinfo.green_mask_size;
-                multiboot1_info->fb_green_mask_shift = fbinfo.green_mask_shift;
-                multiboot1_info->fb_blue_mask_size   = fbinfo.blue_mask_size;
-                multiboot1_info->fb_blue_mask_shift  = fbinfo.blue_mask_shift;
+                multiboot1_info->fb_red_mask_size    = fbs[0].red_mask_size;
+                multiboot1_info->fb_red_mask_shift   = fbs[0].red_mask_shift;
+                multiboot1_info->fb_green_mask_size  = fbs[0].green_mask_size;
+                multiboot1_info->fb_green_mask_shift = fbs[0].green_mask_shift;
+                multiboot1_info->fb_blue_mask_size   = fbs[0].blue_mask_size;
+                multiboot1_info->fb_blue_mask_shift  = fbs[0].blue_mask_shift;
             }
         } else {
 #if defined (UEFI)
             print("multiboot1: Warning: Cannot use text mode with UEFI\n");
-            struct fb_info fbinfo;
-            if (!fb_init(&fbinfo, 0, 0, 0)) {
-                goto skip_modeset;
-            }
-            multiboot1_info->fb_addr    = (uint64_t)fbinfo.framebuffer_addr;
-            multiboot1_info->fb_width   = fbinfo.framebuffer_width;
-            multiboot1_info->fb_height  = fbinfo.framebuffer_height;
-            multiboot1_info->fb_bpp     = fbinfo.framebuffer_bpp;
-            multiboot1_info->fb_pitch   = fbinfo.framebuffer_pitch;
-            multiboot1_info->fb_type    = 1;
-            multiboot1_info->fb_red_mask_size    = fbinfo.red_mask_size;
-            multiboot1_info->fb_red_mask_shift   = fbinfo.red_mask_shift;
-            multiboot1_info->fb_green_mask_size  = fbinfo.green_mask_size;
-            multiboot1_info->fb_green_mask_shift = fbinfo.green_mask_shift;
-            multiboot1_info->fb_blue_mask_size   = fbinfo.blue_mask_size;
-            multiboot1_info->fb_blue_mask_shift  = fbinfo.blue_mask_shift;
+            goto modeset;
 #elif defined (BIOS)
-            vga_textmode_init(false);
-
-            multiboot1_info->fb_addr    = 0xb8000;
-            multiboot1_info->fb_width   = term->cols;
-            multiboot1_info->fb_height  = term->rows;
-            multiboot1_info->fb_bpp     = 16;
-            multiboot1_info->fb_pitch   = 2 * term->cols;
-            multiboot1_info->fb_type    = 2;
+            goto textmode;
 #endif
         }
 
diff --git a/common/protos/multiboot2.c b/common/protos/multiboot2.c
index 3139ccca..83b02656 100644
--- a/common/protos/multiboot2.c
+++ b/common/protos/multiboot2.c
@@ -516,29 +516,36 @@ noreturn void multiboot2_load(char *config, char* cmdline) {
         tag->common.type = MULTIBOOT_TAG_TYPE_FRAMEBUFFER;
         tag->common.size = sizeof(struct multiboot_tag_framebuffer);
 
-        if (term != NULL) {
-            term->deinit(term, pmm_free);
-            term = NULL;
-        }
+        term_notready();
+
+        size_t req_width = 0;
+        size_t req_height = 0;
+        size_t req_bpp = 0;
 
         if (fbtag) {
-            size_t req_width = fbtag->width;
-            size_t req_height = fbtag->height;
-            size_t req_bpp = fbtag->depth;
+            req_width = fbtag->width;
+            req_height = fbtag->height;
+            req_bpp = fbtag->depth;
 
+#if defined (UEFI)
+modeset:;
+#endif
             char *resolution = config_get_value(config, 0, "RESOLUTION");
             if (resolution != NULL)
                 parse_resolution(&req_width, &req_height, &req_bpp, resolution);
 
-            struct fb_info fbinfo;
-            if (!fb_init(&fbinfo, req_width, req_height, req_bpp)) {
+            struct fb_info *fbs;
+            size_t fbs_count;
+            fb_init(&fbs, &fbs_count, req_width, req_height, req_bpp);
+            if (fbs_count == 0) {
 #if defined (BIOS)
+textmode:
                 vga_textmode_init(false);
 
                 tag->common.framebuffer_addr = 0xb8000;
-                tag->common.framebuffer_pitch = 2 * term->cols;
-                tag->common.framebuffer_width = term->cols;
-                tag->common.framebuffer_height = term->rows;
+                tag->common.framebuffer_pitch = 2 * 80;
+                tag->common.framebuffer_width = 80;
+                tag->common.framebuffer_height = 25;
                 tag->common.framebuffer_bpp = 16;
                 tag->common.framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT;
 #elif defined (UEFI)
@@ -549,54 +556,26 @@ noreturn void multiboot2_load(char *config, char* cmdline) {
                 }
 #endif
             } else {
-                tag->common.framebuffer_addr = fbinfo.framebuffer_addr;
-                tag->common.framebuffer_pitch = fbinfo.framebuffer_pitch;
-                tag->common.framebuffer_width = fbinfo.framebuffer_width;
-                tag->common.framebuffer_height = fbinfo.framebuffer_height;
-                tag->common.framebuffer_bpp = fbinfo.framebuffer_bpp;
+                tag->common.framebuffer_addr = fbs[0].framebuffer_addr;
+                tag->common.framebuffer_pitch = fbs[0].framebuffer_pitch;
+                tag->common.framebuffer_width = fbs[0].framebuffer_width;
+                tag->common.framebuffer_height = fbs[0].framebuffer_height;
+                tag->common.framebuffer_bpp = fbs[0].framebuffer_bpp;
                 tag->common.framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_RGB; // We only support RGB for VBE
 
-                tag->framebuffer_red_field_position = fbinfo.red_mask_shift;
-                tag->framebuffer_red_mask_size = fbinfo.red_mask_size;
-                tag->framebuffer_green_field_position = fbinfo.green_mask_shift;
-                tag->framebuffer_green_mask_size = fbinfo.green_mask_size;
-                tag->framebuffer_blue_field_position = fbinfo.blue_mask_shift;
-                tag->framebuffer_blue_mask_size = fbinfo.blue_mask_size;
+                tag->framebuffer_red_field_position = fbs[0].red_mask_shift;
+                tag->framebuffer_red_mask_size = fbs[0].red_mask_size;
+                tag->framebuffer_green_field_position = fbs[0].green_mask_shift;
+                tag->framebuffer_green_mask_size = fbs[0].green_mask_size;
+                tag->framebuffer_blue_field_position = fbs[0].blue_mask_shift;
+                tag->framebuffer_blue_mask_size = fbs[0].blue_mask_size;
             }
         } else {
 #if defined (UEFI)
             print("multiboot2: Warning: Cannot use text mode with UEFI\n");
-            struct fb_info fbinfo;
-            if (!fb_init(&fbinfo, 0, 0, 0)) {
-                if (is_framebuffer_required) {
-                    panic(true, "multiboot2: Failed to set video mode");
-                } else {
-                    goto skip_modeset;
-                }
-            }
-
-            tag->common.framebuffer_addr = fbinfo.framebuffer_addr;
-            tag->common.framebuffer_pitch = fbinfo.framebuffer_pitch;
-            tag->common.framebuffer_width = fbinfo.framebuffer_width;
-            tag->common.framebuffer_height = fbinfo.framebuffer_height;
-            tag->common.framebuffer_bpp = fbinfo.framebuffer_bpp;
-            tag->common.framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_RGB; // We only support RGB for VBE
-
-            tag->framebuffer_red_field_position = fbinfo.red_mask_shift;
-            tag->framebuffer_red_mask_size = fbinfo.red_mask_size;
-            tag->framebuffer_green_field_position = fbinfo.green_mask_shift;
-            tag->framebuffer_green_mask_size = fbinfo.green_mask_size;
-            tag->framebuffer_blue_field_position = fbinfo.blue_mask_shift;
-            tag->framebuffer_blue_mask_size = fbinfo.blue_mask_size;
+            goto modeset;
 #elif defined (BIOS)
-            vga_textmode_init(false);
-
-            tag->common.framebuffer_addr = 0xb8000;
-            tag->common.framebuffer_width = term->cols;
-            tag->common.framebuffer_height = term->rows;
-            tag->common.framebuffer_bpp = 16;
-            tag->common.framebuffer_pitch = 2 * term->cols;
-            tag->common.framebuffer_type = MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT;
+            goto textmode;
 #endif
         }
 
tab: 248 wrap: offon