diff --git a/Makefile b/Makefile index 111e101..f1db53d 100644 --- a/Makefile +++ b/Makefile @@ -264,7 +264,7 @@ ifeq ($(CONFIG_DRV_CANVAS_2D), y) CONFIG_SRCS-y += drivers/video/canvas.c endif ifeq ($(CONFIG_DRV_BOCHS_VBE), y) - CONFIG_SRCS-y += drivers/video/bochs.c drivers/video/fb.c + CONFIG_SRCS-y += drivers/video/bochs.c drivers/video/fb.c drivers/video/fbcon.c endif ifeq ($(CONFIG_DRV_SOUND_HDA), y) CONFIG_SRCS-y += sound/hda.c sound/sound.c sound/pcm.c sound/tts.c sound/melody.c @@ -320,7 +320,7 @@ DEPS = $(C_OBJS:.o=.d) $(CPP_OBJS:.o=.d) $(S_OBJS:.o=.d) .DEFAULT_GOAL := all -.PHONY: all clean run debug info configure menuconfig config tui nconfig defconfig x86_64_defconfig aarch64_defconfig armv8_defconfig armv8i_defconfig armv81_defconfig arm32_defconfig qemu help +.PHONY: all clean run run-fullscreen debug info configure menuconfig config tui nconfig defconfig x86_64_defconfig aarch64_defconfig armv8_defconfig armv8i_defconfig armv81_defconfig arm32_defconfig qemu help all: $(TARGET) @@ -414,23 +414,39 @@ $(IMAGE): $(BUILD_DIR)/boot/boot.bin $(BUILD_DIR)/boot/stage2.bin $(BUILD_DIR)/k run qemu: $(TARGET) $(QEMU_CMD) +# -vga std gives the Bochs VBE adapter the kernel drives. vgamem_mb is raised +# because a 1280x720 32-bit framebuffer needs more than the 8 MB default. +QEMU_VGA = -device VGA,vgamem_mb=32 + run-gui: $(TARGET) ifeq ($(ARCH), x86_64) qemu-system-x86_64 -drive format=raw,file=$(IMAGE) \ -netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \ - -device e1000,netdev=net0 -vga std -serial stdio + -device e1000,netdev=net0 $(QEMU_VGA) -display gtk,zoom-to-fit=on -serial stdio else ifneq ($(filter $(ARCH), aarch64 arm64),) qemu-system-aarch64 -M virt -cpu cortex-a57 -m 128M -kernel $(BUILD_DIR)/kernel.elf -device virtio-gpu-pci -serial stdio else qemu-system-arm -M virt -cpu cortex-a15 -m 128M -kernel $(BUILD_DIR)/kernel.elf -device virtio-gpu-pci -serial stdio endif +# Fullscreen without distortion: zoom-to-fit scales the guest while preserving +# its aspect ratio, letterboxing rather than stretching on a wider panel. +run-fullscreen: $(TARGET) +ifeq ($(ARCH), x86_64) + qemu-system-x86_64 -drive format=raw,file=$(IMAGE) \ + -netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \ + -device e1000,netdev=net0 $(QEMU_VGA) \ + -display gtk,zoom-to-fit=on,show-cursor=on -full-screen -serial stdio +else + @echo "run-fullscreen targets x86_64 only" +endif + run-vnc: $(TARGET) ifeq ($(ARCH), x86_64) @echo "=== [QEMU VNC Server running at localhost:5900 (VNC Display :0)] ===" qemu-system-x86_64 -drive format=raw,file=$(IMAGE) \ -netdev user,id=net0,hostfwd=tcp::2222-:22,hostfwd=tcp::8080-:80 \ - -device e1000,netdev=net0 -vga std -vnc :0 -serial stdio + -device e1000,netdev=net0 $(QEMU_VGA) -vnc :0 -serial stdio else @echo "=== [QEMU VNC Server running at localhost:5900 (VNC Display :0)] ===" qemu-system-aarch64 -M virt -cpu cortex-a57 -m 128M -kernel $(BUILD_DIR)/kernel.elf -device virtio-gpu-pci -vnc :0 -serial stdio diff --git a/drivers/char/tty.c b/drivers/char/tty.c index 2e0e7c2..63a7aea 100644 --- a/drivers/char/tty.c +++ b/drivers/char/tty.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -49,6 +50,14 @@ static void tty_scroll(tty_t* t) { } void tty_clear(void) { + // While the framebuffer console owns the display, the VGA text buffer is + // no longer scanned out, so clearing it would be invisible. + if (fbcon_is_active()) { + fbcon_clear(); + printk("\033[2J\033[H"); // Keep the serial terminal in step + return; + } + tty_t* t = &ttys[current_tty]; for (int y = 0; y < TTY_HEIGHT; y++) { for (int x = 0; x < TTY_WIDTH; x++) { @@ -64,6 +73,14 @@ void tty_clear(void) { } void tty_set_cursor(int row, int col) { + if (fbcon_is_active()) { + fbcon_set_cursor(row, col); + // Mirror the move to serial so a remote terminal tracks full-screen + // programs such as the nano editor. + printk("\033[%d;%dH", row + 1, col + 1); + return; + } + tty_t* t = &ttys[current_tty]; t->cursor_row = row; t->cursor_col = col; diff --git a/drivers/video/bochs.c b/drivers/video/bochs.c index 2015c25..8812831 100644 --- a/drivers/video/bochs.c +++ b/drivers/video/bochs.c @@ -1,5 +1,6 @@ // Bochs / QEMU VBE Display Adapter Driver for SUB-OS #include +#include #include #include #include @@ -8,6 +9,8 @@ static bochs_vbe_info_t vbe_info; +static bool g_vbe_enabled = false; + static void vbe_write(uint16_t index, uint16_t data) { outw(VBE_DISPI_IOPORT_INDEX, index); outw(VBE_DISPI_IOPORT_DATA, data); @@ -18,6 +21,43 @@ static uint16_t vbe_read(uint16_t index) { return inw(VBE_DISPI_IOPORT_DATA); } +// Parse `video=WIDTHxHEIGHT` from the kernel command line. Falls back to the +// caller's request when the parameter is absent or malformed. +static void vbe_mode_from_cmdline(uint32_t* width, uint32_t* height) { + const char* spec = init_get_param("video"); + if (!spec) return; + + uint32_t w = 0, h = 0; + const char* p = spec; + + while (*p >= '0' && *p <= '9') w = w * 10 + (uint32_t)(*p++ - '0'); + if (*p != 'x' && *p != 'X') return; + p++; + while (*p >= '0' && *p <= '9') h = h * 10 + (uint32_t)(*p++ - '0'); + + // The Bochs adapter tops out at 2560x1600, and the compositor allocates a + // 32-bit backbuffer per surface, so refuse anything the heap cannot hold. + if (w < 640 || h < 480 || w > 1920 || h > 1200) { + printk(KERN_WARNING "VBE: ignoring unsupported video=%s\n", spec); + return; + } + + *width = w; + *height = h; +} + +void bochs_vbe_disable(void) { + if (!vbe_info.available) return; + + vbe_write(VBE_DISPI_INDEX_ENABLE, VBE_DISPI_DISABLED); + g_vbe_enabled = false; + fb_set_active(false); +} + +bool bochs_vbe_is_enabled(void) { + return g_vbe_enabled; +} + int bochs_vbe_set_mode(uint32_t width, uint32_t height, uint32_t bpp) { if (!vbe_info.available) return -1; @@ -30,6 +70,7 @@ int bochs_vbe_set_mode(uint32_t width, uint32_t height, uint32_t bpp) { vbe_info.current_width = width; vbe_info.current_height = height; vbe_info.current_bpp = bpp; + g_vbe_enabled = true; // Connect global Framebuffer driver directly to hardware video memory fb_set_hardware_lfb((uint32_t*)vbe_info.lfb_phys_addr, width, height, (uint8_t)bpp); @@ -42,6 +83,8 @@ int bochs_vbe_set_mode(uint32_t width, uint32_t height, uint32_t bpp) { bool bochs_vbe_init(void) { memset(&vbe_info, 0, sizeof(vbe_info)); + uint32_t mode_w = 800, mode_h = 600; + // Probe VBE DISPI interface uint16_t id = vbe_read(VBE_DISPI_INDEX_ID); if ((id & 0xB0C0) == 0xB0C0) { @@ -58,7 +101,8 @@ bool bochs_vbe_init(void) { vbe_info.lfb_phys_addr = 0xFD000000; } - bochs_vbe_set_mode(800, 600, 32); + vbe_mode_from_cmdline(&mode_w, &mode_h); + bochs_vbe_set_mode(mode_w, mode_h, 32); printk(KERN_INFO "VBE: Bochs/QEMU VBE Display Adapter detected (v%04x, Linear Framebuffer: 0x%llx)\n", vbe_info.vbe_version, vbe_info.lfb_phys_addr); @@ -69,7 +113,8 @@ bool bochs_vbe_init(void) { vbe_info.available = true; vbe_info.vbe_version = 0xB0C5; vbe_info.lfb_phys_addr = 0xFD000000; - bochs_vbe_set_mode(800, 600, 32); + vbe_mode_from_cmdline(&mode_w, &mode_h); + bochs_vbe_set_mode(mode_w, mode_h, 32); printk(KERN_INFO "VBE: Standard VESA/VBE Display Engine online (800x600 32bpp TrueColor)\n"); return true; diff --git a/drivers/video/fb.c b/drivers/video/fb.c index aac2e26..3a27e56 100644 --- a/drivers/video/fb.c +++ b/drivers/video/fb.c @@ -23,6 +23,10 @@ const fb_info_t* fb_get_info(void) { return &fb_info; } +void fb_set_active(bool active) { + fb_info.active = active; +} + void fb_set_hardware_lfb(uint32_t* lfb_addr, uint32_t width, uint32_t height, uint8_t bpp) { if (lfb_addr) { fb_info.address = lfb_addr; diff --git a/drivers/video/fbcon.c b/drivers/video/fbcon.c new file mode 100644 index 0000000..447ea61 --- /dev/null +++ b/drivers/video/fbcon.c @@ -0,0 +1,322 @@ +// Framebuffer console for SUB-OS +// +// Draws the kernel console into the linear framebuffer with the shared 8x8 +// font at double height. Carries enough of a VT100/ANSI parser -- SGR colours, +// cursor addressing, erase-display and erase-line -- that full-screen programs +// like the nano editor render correctly, which the VGA text console cannot do +// once the adapter has been switched into graphics mode. + +#include +#include +#include +#include + +#define FBCON_MAX_PARAMS 6 + +// Standard ANSI palette, bright variants in the upper half. +static const uint32_t g_ansi_colors[16] = { + 0xFF1E293B, 0xFFEF4444, 0xFF22C55E, 0xFFEAB308, + 0xFF3B82F6, 0xFFA855F7, 0xFF06B6D4, 0xFFCBD5E1, + 0xFF64748B, 0xFFF87171, 0xFF4ADE80, 0xFFFBBF24, + 0xFF60A5FA, 0xFFC084FC, 0xFF22D3EE, 0xFFF8FAFC +}; + +#define FBCON_DEFAULT_FG 0xFFCBD5E1 +#define FBCON_DEFAULT_BG 0xFF0B0F19 + +typedef enum { + PARSE_NORMAL, + PARSE_ESC, + PARSE_CSI +} parse_state_t; + +static bool g_active = false; +static int g_cols = 0, g_rows = 0; +static int g_cell_w = 8, g_cell_h = 16; +static int g_cur_x = 0, g_cur_y = 0; +static int g_saved_x = 0, g_saved_y = 0; +static uint32_t g_fg = FBCON_DEFAULT_FG; +static uint32_t g_bg = FBCON_DEFAULT_BG; +static bool g_bold = false; + +static parse_state_t g_state = PARSE_NORMAL; +static int g_params[FBCON_MAX_PARAMS]; +static int g_param_count = 0; +static bool g_param_seen = false; + +// --------------------------------------------------------------------------- +// Glyph output +// --------------------------------------------------------------------------- + +static void draw_cell(int col, int row, char c, uint32_t fg, uint32_t bg) { + const fb_info_t* fb = fb_get_info(); + if (!fb || !fb->active) return; + + int x = col * g_cell_w; + int y = row * g_cell_h; + const uint8_t* glyph = font8x8_glyph(c); + + // Nearest-neighbour scale from the 8x8 face to whatever cell size the + // current resolution divides into, so 80x25 holds at any mode. + for (int dy = 0; dy < g_cell_h; dy++) { + uint8_t bits = glyph[(dy * 8) / g_cell_h]; + for (int dx = 0; dx < g_cell_w; dx++) { + int sx = (dx * 8) / g_cell_w; + uint32_t color = (bits & (0x80u >> sx)) ? fg : bg; + fb_put_pixel((uint32_t)(x + dx), (uint32_t)(y + dy), color); + } + } +} + +static void clear_rows(int first_row, int count) { + if (count <= 0) return; + fb_draw_rect(0, (uint32_t)(first_row * g_cell_h), + (uint32_t)(g_cols * g_cell_w), + (uint32_t)(count * g_cell_h), g_bg); +} + +// Scroll by moving framebuffer scanlines up one cell height; no shadow grid is +// kept, so the pixels themselves are the console's backing store. +static void scroll_one_line(void) { + const fb_info_t* fb = fb_get_info(); + if (!fb || !fb->active || !fb->address) return; + + uint32_t width = fb->width; + uint32_t shift = (uint32_t)g_cell_h; + uint32_t used_height = (uint32_t)(g_rows * g_cell_h); + + for (uint32_t y = 0; y + shift < used_height; y++) { + uint32_t* dst = fb->address + (size_t)y * width; + uint32_t* src = fb->address + (size_t)(y + shift) * width; + memcpy(dst, src, (size_t)g_cols * (size_t)g_cell_w * sizeof(uint32_t)); + } + + clear_rows(g_rows - 1, 1); +} + +static void newline(void) { + g_cur_x = 0; + if (++g_cur_y >= g_rows) { + scroll_one_line(); + g_cur_y = g_rows - 1; + } +} + +// --------------------------------------------------------------------------- +// ANSI handling +// --------------------------------------------------------------------------- + +static void apply_sgr(void) { + if (g_param_count == 0) { + g_fg = FBCON_DEFAULT_FG; + g_bg = FBCON_DEFAULT_BG; + g_bold = false; + return; + } + + for (int i = 0; i < g_param_count; i++) { + int code = g_params[i]; + + if (code == 0) { + g_fg = FBCON_DEFAULT_FG; + g_bg = FBCON_DEFAULT_BG; + g_bold = false; + } else if (code == 1) { + g_bold = true; + } else if (code == 7) { + uint32_t tmp = g_fg; // Reverse video + g_fg = g_bg; + g_bg = tmp; + } else if (code >= 30 && code <= 37) { + g_fg = g_ansi_colors[(code - 30) + (g_bold ? 8 : 0)]; + } else if (code >= 90 && code <= 97) { + g_fg = g_ansi_colors[(code - 90) + 8]; + } else if (code >= 40 && code <= 47) { + g_bg = g_ansi_colors[code - 40]; + } else if (code >= 100 && code <= 107) { + g_bg = g_ansi_colors[(code - 100) + 8]; + } + } +} + +static int param_or(int index, int fallback) { + if (index >= g_param_count) return fallback; + return g_params[index] ? g_params[index] : fallback; +} + +static void handle_csi(char final) { + switch (final) { + case 'm': + apply_sgr(); + break; + + case 'H': case 'f': { + // Cursor position is 1-based on the wire, 0-based internally. + int row = param_or(0, 1) - 1; + int col = param_or(1, 1) - 1; + g_cur_y = (row < 0) ? 0 : (row >= g_rows ? g_rows - 1 : row); + g_cur_x = (col < 0) ? 0 : (col >= g_cols ? g_cols - 1 : col); + break; + } + + case 'A': g_cur_y -= param_or(0, 1); if (g_cur_y < 0) g_cur_y = 0; break; + case 'B': g_cur_y += param_or(0, 1); if (g_cur_y >= g_rows) g_cur_y = g_rows - 1; break; + case 'C': g_cur_x += param_or(0, 1); if (g_cur_x >= g_cols) g_cur_x = g_cols - 1; break; + case 'D': g_cur_x -= param_or(0, 1); if (g_cur_x < 0) g_cur_x = 0; break; + + case 'J': { + int mode = (g_param_count && g_param_seen) ? g_params[0] : 0; + if (mode == 2 || mode == 3) { + clear_rows(0, g_rows); + g_cur_x = g_cur_y = 0; + } else if (mode == 1) { + clear_rows(0, g_cur_y); + } else { + clear_rows(g_cur_y + 1, g_rows - g_cur_y - 1); + } + break; + } + + case 'K': { + int mode = (g_param_count && g_param_seen) ? g_params[0] : 0; + int from = (mode == 1) ? 0 : g_cur_x; + int to = (mode == 1) ? g_cur_x + 1 : g_cols; + if (mode == 2) { from = 0; to = g_cols; } + if (to > from) { + fb_draw_rect((uint32_t)(from * g_cell_w), + (uint32_t)(g_cur_y * g_cell_h), + (uint32_t)((to - from) * g_cell_w), + (uint32_t)g_cell_h, g_bg); + } + break; + } + + case 's': g_saved_x = g_cur_x; g_saved_y = g_cur_y; break; + case 'u': g_cur_x = g_saved_x; g_cur_y = g_saved_y; break; + + default: + break; // Unsupported sequences are consumed and ignored + } +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- + +void fbcon_init(void) { + const fb_info_t* fb = fb_get_info(); + if (!fb) return; + + g_cols = FBCON_COLS; + g_rows = FBCON_ROWS; + g_cell_w = (int)(fb->width / (uint32_t)FBCON_COLS); + g_cell_h = (int)(fb->height / (uint32_t)FBCON_ROWS); + if (g_cell_w < 1) g_cell_w = 1; + if (g_cell_h < 1) g_cell_h = 1; + g_cur_x = g_cur_y = 0; + g_fg = FBCON_DEFAULT_FG; + g_bg = FBCON_DEFAULT_BG; + g_bold = false; + g_state = PARSE_NORMAL; +} + +void fbcon_enable(bool enable) { + if (enable) { + fbcon_init(); + g_active = true; + fbcon_clear(); + } else { + g_active = false; + } +} + +bool fbcon_is_active(void) { + return g_active; +} + +int fbcon_cols(void) { return g_cols; } +int fbcon_rows(void) { return g_rows; } + +void fbcon_set_cursor(int row, int col) { + if (row >= 0 && row < g_rows) g_cur_y = row; + if (col >= 0 && col < g_cols) g_cur_x = col; +} + +void fbcon_get_cursor(int* row, int* col) { + if (row) *row = g_cur_y; + if (col) *col = g_cur_x; +} + +void fbcon_clear(void) { + const fb_info_t* fb = fb_get_info(); + if (!fb || !fb->active) return; + + fb_draw_rect(0, 0, fb->width, fb->height, g_bg); + g_cur_x = g_cur_y = 0; +} + +void fbcon_putc(char c) { + if (!g_active || g_cols == 0 || g_rows == 0) return; + + switch (g_state) { + case PARSE_ESC: + if (c == '[') { + g_state = PARSE_CSI; + g_param_count = 0; + g_param_seen = false; + for (int i = 0; i < FBCON_MAX_PARAMS; i++) g_params[i] = 0; + } else { + g_state = PARSE_NORMAL; // Two-character escape, ignored + } + return; + + case PARSE_CSI: + if (c >= '0' && c <= '9') { + if (g_param_count == 0) g_param_count = 1; + g_params[g_param_count - 1] = g_params[g_param_count - 1] * 10 + (c - '0'); + g_param_seen = true; + } else if (c == ';') { + if (g_param_count < FBCON_MAX_PARAMS) g_param_count++; + } else if (c == '?' || c == '>') { + // Private-mode introducer; parameters still parse normally. + } else { + handle_csi(c); + g_state = PARSE_NORMAL; + } + return; + + default: + break; + } + + switch (c) { + case '\033': g_state = PARSE_ESC; return; + case '\n': newline(); return; + case '\r': g_cur_x = 0; return; + case '\t': + g_cur_x = (g_cur_x + 8) & ~7; + if (g_cur_x >= g_cols) newline(); + return; + case '\b': + if (g_cur_x > 0) { + g_cur_x--; + draw_cell(g_cur_x, g_cur_y, ' ', g_fg, g_bg); + } + return; + case '\a': + return; + default: + break; + } + + // Log-level markers (\001 followed by a digit) never reach the glass. + if ((unsigned char)c < 32) return; + + draw_cell(g_cur_x, g_cur_y, c, g_fg, g_bg); + if (++g_cur_x >= g_cols) newline(); +} + +void fbcon_write(const char* data, size_t len) { + if (!data) return; + for (size_t i = 0; i < len; i++) fbcon_putc(data[i]); +} diff --git a/include/drivers/bochs.h b/include/drivers/bochs.h index 63086df..ba12476 100644 --- a/include/drivers/bochs.h +++ b/include/drivers/bochs.h @@ -36,6 +36,12 @@ typedef struct { bool bochs_vbe_init(void); bool bochs_vbe_is_available(void); int bochs_vbe_set_mode(uint32_t width, uint32_t height, uint32_t bpp); + +// Leave graphics and hand the adapter back to VGA text mode. Disabling the +// DISPI interface restores the mode the BIOS left behind, which is what makes +// the 80x25 console visible again after a desktop session ends. +void bochs_vbe_disable(void); +bool bochs_vbe_is_enabled(void); const bochs_vbe_info_t* bochs_vbe_get_info(void); #endif // _DRIVERS_BOCHS_H diff --git a/include/drivers/fb.h b/include/drivers/fb.h index e6b2254..e2f2493 100644 --- a/include/drivers/fb.h +++ b/include/drivers/fb.h @@ -17,6 +17,10 @@ typedef struct { void fb_init(void); const fb_info_t* fb_get_info(void); void fb_set_hardware_lfb(uint32_t* lfb_addr, uint32_t width, uint32_t height, uint8_t bpp); + +// Mark the framebuffer live or dormant. Drawing routines no-op while dormant, +// which keeps stray writes out of video memory after a return to text mode. +void fb_set_active(bool active); void fb_put_pixel(uint32_t x, uint32_t y, uint32_t color); void fb_draw_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t color); void fb_clear(uint32_t color); diff --git a/include/drivers/fbcon.h b/include/drivers/fbcon.h new file mode 100644 index 0000000..d17f9ce --- /dev/null +++ b/include/drivers/fbcon.h @@ -0,0 +1,41 @@ +#ifndef _DRIVERS_FBCON_H +#define _DRIVERS_FBCON_H + +#include +#include +#include + +// Framebuffer console. +// +// Once the VBE adapter is switched into graphics mode there is no way back to +// an 80x25 text console without reprogramming the VGA register file, and the +// text buffer at 0xB8000 is no longer being scanned out. Anything printed after +// that point is invisible on the display even though it still reaches serial. +// +// This renders the console into the framebuffer instead, the way Linux fbcon +// does: a character grid drawn with the shared 8x8 font at double height, with +// enough of a VT100 parser (SGR colours, cursor addressing, erase) for +// full-screen curses-style programs such as the nano editor to work. + +// The userland models a fixed 80x25 console -- nano pads its bars with %-80s, +// the shell wraps at TTY_WIDTH -- so fbcon presents exactly that geometry and +// scales the glyph cell to fill whatever resolution the adapter is running. +#define FBCON_COLS 80 +#define FBCON_ROWS 25 + +void fbcon_init(void); + +// While active, kernel output is drawn here instead of the VGA text buffer. +void fbcon_enable(bool enable); +bool fbcon_is_active(void); + +void fbcon_write(const char* data, size_t len); +void fbcon_putc(char c); +void fbcon_clear(void); + +int fbcon_cols(void); +int fbcon_rows(void); +void fbcon_set_cursor(int row, int col); +void fbcon_get_cursor(int* row, int* col); + +#endif // _DRIVERS_FBCON_H diff --git a/kernel/main.c b/kernel/main.c index 3c88f6a..8c9b2d5 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -284,7 +284,7 @@ void kernel_main(void* memory_map, uint64_t memory_map_count) { printk(ANSI_BRIGHT_CYAN " SUB-OS 64-Bit [%s] Modular Monolithic Kernel %s\n" ANSI_RESET, arch_get_name(), kernel_get_version()); printk(ANSI_BRIGHT_CYAN "=================================================================\n\n" ANSI_RESET); - init_early("root=/dev/sda console=tty1 init=/bin/lazybox quiet"); + init_early("root=/dev/sda console=tty1 init=/bin/lazybox video=1280x720 quiet"); rtc_init(); printk(KERN_INFO "[1/14] Initializing 64-Bit GDT, IDT, PIC & PIT Timer... "); diff --git a/kernel/printk.c b/kernel/printk.c index 1c114af..5862456 100644 --- a/kernel/printk.c +++ b/kernel/printk.c @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -68,7 +69,13 @@ int vprintk(const char* fmt, va_list args) { sink_fn(display_ptr, (size_t)len, sink_ctx); sink_active = false; } else if (!sink_fn) { - tty_write(display_ptr, (size_t)len); + // The VGA text buffer stops being scanned out once the adapter is in + // graphics mode, so the framebuffer console takes over there. + if (fbcon_is_active()) { + fbcon_write(display_ptr, (size_t)len); + } else { + tty_write(display_ptr, (size_t)len); + } } // 2. Output to serial console (COM1 on x86, PL011 on ARM/AArch64)