From d9c746b1e597f037a6eb6d54dde7238cc8139f09 Mon Sep 17 00:00:00 2001 From: Xavier Perrin Date: Mon, 20 Mar 2023 16:48:50 +0100 Subject: [PATCH] Keyboard driver and fancy screens (#6) * Keyboard basic input handling, basic screens * Cleaner screen handling * Use VNC by default with QEMU * Use pointer on functions to define screens * Add current commit to homepage * Silly box, improved vga api, improved headers * CRTC VGA cursor BS * Add colors test, fix cursor, no infinite buff cycling --- .vscode/extensions.json | 1 + .vscode/settings.json | 5 + .vscode/tasks.json | 2 +- Makefile | 29 ++-- build/i686-cc/Dockerfile | 2 +- src/drivers/keyboard/keyboard.c | 23 +++ src/drivers/keyboard/keyboard.h | 55 +++++++ src/drivers/keyboard/keyboard_internal.h | 9 ++ src/drivers/vga/{vga_buffer.c => buffer.c} | 162 ++++++++++++++++----- src/drivers/vga/crtc.c | 37 +++++ src/drivers/vga/{vga_printf.c => printf.c} | 3 +- src/drivers/vga/vga.h | 110 +++++++++++--- src/drivers/vga/vga_internal.h | 11 +- src/kernel.c | 88 ++++++++++- src/kernel.h | 18 +++ src/klibc/asm.c | 12 ++ src/klibc/libc.h | 13 ++ src/screens/box.c | 53 +++++++ src/screens/ipsum.c | 41 ++++++ src/screens/loop.c | 83 +++++++++++ src/screens/screens.h | 26 ++++ src/test/test.h | 13 +- src/test/test_printf.c | 28 +++- 23 files changed, 729 insertions(+), 95 deletions(-) create mode 100644 src/drivers/keyboard/keyboard.c create mode 100644 src/drivers/keyboard/keyboard.h create mode 100644 src/drivers/keyboard/keyboard_internal.h rename src/drivers/vga/{vga_buffer.c => buffer.c} (59%) create mode 100644 src/drivers/vga/crtc.c rename src/drivers/vga/{vga_printf.c => printf.c} (98%) create mode 100644 src/klibc/asm.c create mode 100644 src/screens/box.c create mode 100644 src/screens/ipsum.c create mode 100644 src/screens/loop.c create mode 100644 src/screens/screens.h diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 2a8705b..3046dad 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -4,5 +4,6 @@ "EditorConfig.EditorConfig", "13xforever.language-x86-64-assembly", "zixuanwang.linkerscript", + "king2021.vnc-extension", ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index ad5676e..909ad1a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,11 @@ "editor.defaultFormatter": "ms-vscode.cpptools", "editor.formatOnSave": true }, + "remote.extensionKind": { + "king2021.vnc-extension": [ + "ui" + ] + }, "editor.insertSpaces": true, "editor.tabSize": 2, "terminal.integrated.macOptionIsMeta": true diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a7c6d2c..aedb62e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -14,7 +14,7 @@ { "label": "Run QEMU", "type": "shell", - "command": "make use_docker && echo Starting QEMU& docker run -it --platform linux/amd64 --rm -p 4666:4666 -v ${workspaceFolder}:/build ghcr.io/l-aurelie/i686-cc:latest make run", + "command": "make use_docker && echo Starting QEMU& docker run -it --platform linux/amd64 --rm -p 4666:4666 -p 5999:5999 -v ${workspaceFolder}:/build ghcr.io/l-aurelie/i686-cc:latest make run", "isBackground": true, "problemMatcher": { "pattern": { diff --git a/Makefile b/Makefile index b934117..1e5c3c4 100644 --- a/Makefile +++ b/Makefile @@ -21,10 +21,14 @@ HAS_CC := $(shell $(CC) --version 2>/dev/null) SIGNATURE_ADDRESS=0x0030DEAD SIGNATURE_VALUE=0x00DEAD42 # !! NEEDS TO BE A WORD FOR THE TEST TO WORK !! +# git version +GIT_VERSION := "$(shell git describe --abbrev=4 --dirty --always --tags)" + CPPFLAGS += \ -MMD \ -D SIGNATURE_ADDRESS=$(SIGNATURE_ADDRESS) \ - -D SIGNATURE_VALUE=$(SIGNATURE_VALUE) + -D SIGNATURE_VALUE=$(SIGNATURE_VALUE) \ + -D VERSION=\"$(GIT_VERSION)\" DBGFLAGS := \ -g \ @@ -61,8 +65,10 @@ ifneq (,$(wildcard /.dockerenv)) INSIDE_DOCKER := true endif -# gbb -GDB_PORT := 4666 +#qemu +VNC_DISPLAY := 99 +GDB_PORT := 4666 +QEMU_RAM := 4M # sources SRC_PATH := src @@ -80,7 +86,7 @@ ERROR_CC = @$(error "[ERROR] $(CC) not found, either run `make use_docker $(MAK ERROR_GRUB = @$(error "[ERROR] grub, xorriso or grub*-bios missing, either run `make use_docker $(MAKECMDGOALS)` or run: $(DOCKER_CMD)") ## Rulez ## -.PHONY: all use_docker run gdb test clean fclean re +.PHONY: all use_docker run run-curses gdb test clean fclean re all: $(NAME) $(IMG_NAME) @@ -130,17 +136,22 @@ else endif run: $(IMG_NAME) - @echo "[INFO] Qemu is running a gdb server at $(GDB_PORT)" - qemu-system-i386 -boot d -cdrom $(IMG_NAME) \ - -m 4M \ - -display curses \ + @echo "[INFO] QEMU is running a vnc server at localhost:`echo 5900+$(VNC_DISPLAY) | bc` gdb server at $(GDB_PORT)" + qemu-system-i386 -name $(NAME) -boot d -cdrom $(IMG_NAME) \ + -m $(QEMU_RAM) \ + -display vnc=:$(VNC_DISPLAY) \ -gdb tcp:0.0.0.0:$(GDB_PORT) # ncurses interface and gdb server +run-curses: $(IMG_NAME) + qemu-system-i386 -name $(NAME) -boot d -cdrom $(IMG_NAME) \ + -m $(QEMU_RAM) \ + -display curses + test: $(IMG_NAME) @echo "[INFO] Starting up $(IMG_NAME) and checking if signature is set in memory]" timeout 15 qemu-system-i386 \ -boot d -cdrom $(IMG_NAME) \ - -m 4M -nographic -gdb tcp:localhost:$(GDB_PORT) & + -m $(QEMU_RAM) -nographic -gdb tcp:localhost:$(GDB_PORT) & sleep 10 && (echo "x/wx $(SIGNATURE_ADDRESS)") | make gdb 2>/dev/null | grep -i $(SIGNATURE_VALUE) @echo "[INFO] Signature matched, test passed!" diff --git a/build/i686-cc/Dockerfile b/build/i686-cc/Dockerfile index 35368f8..ae7e457 100644 --- a/build/i686-cc/Dockerfile +++ b/build/i686-cc/Dockerfile @@ -58,7 +58,7 @@ RUN apk add --no-cache \ gmp-dev \ mpc1-dev \ mpfr-dev \ - grub grub-bios xorriso \ + grub grub-bios xorriso git\ qemu-system-i386 qemu-ui-curses \ gdb \ bash diff --git a/src/drivers/keyboard/keyboard.c b/src/drivers/keyboard/keyboard.c new file mode 100644 index 0000000..66fa2fa --- /dev/null +++ b/src/drivers/keyboard/keyboard.c @@ -0,0 +1,23 @@ +#include "keyboard.h" +#include "../../klibc/libc.h" +#include "keyboard_internal.h" + +// TODO use partial init with ranges +// http://www.osdever.net/bkerndev/Docs/keyboard.htm +unsigned char us_scancode_1[128] = { + 0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', + '=', '\b', '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', + '[', ']', '\n', 0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', + ';', '\'', '`', 0, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', + '.', '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +scancode kbd_get() { + return (inb(KBD_IO_STATUS_REGISTER) & 1) ? inb(KBD_IO_DATA_PORT) : 0; +} + +char kbd_code_to_ascii(scancode code) { + return (code <= 127) ? us_scancode_1[code] : 0; +} diff --git a/src/drivers/keyboard/keyboard.h b/src/drivers/keyboard/keyboard.h new file mode 100644 index 0000000..217d704 --- /dev/null +++ b/src/drivers/keyboard/keyboard.h @@ -0,0 +1,55 @@ +#ifndef KEYBOARD_H +#define KEYBOARD_H + +#include + +// scancode set 1 +// https://wiki.osdev.org/PS/2_Keyboard#Scan_Code_Set_1 +typedef enum scancode { + KBD_ESCAPE = 0x1, + KBD_BACKSPACE = 0x0E, + KBD_TAB, + KBD_ENTER = 0x1C, + KBD_LEFT_CTRL, + KBD_LEFT_SHIFT = 0x2A, + KBD_RIGHT_SHIFT = 0x36, + KBD_LEFT_ALT = 0x38, + KBD_SPACE, + KBD_CAPS_LOCK, + KBD_F1, + KBD_F2, + KBD_F3, + KBD_F4, + KBD_F5, + KBD_F6, + KBD_F7, + KBD_F8, + KBD_F9, + KBD_F10, + KBD_NUMLOCK, + KBD_SCROLLLOCK, + KBD_F11 = 0x57, + KBD_F12, + // Hack from here, only released + KBD_HOME = 0xC7, + KBD_CURSOR_UP, + KBD_PAGE_UP, + KBD_CURSOR_LEFT = 0xCB, + KBD_CURSOR_RIGHT = 0xCD, + KBD_END = 0xCF, + KBD_CURSOR_DOWN, + KBD_PAGE_DOWN, + KBD_INSERT, + KBD_DELETE, +} scancode; + +// http://www.osdever.net/bkerndev/Docs/keyboard.htm +extern unsigned char us_scancode_1[128]; + +/// @brief returns a keyboard scancode +/// @return keyboard scancode +scancode kbd_get(); + +char kbd_code_to_ascii(scancode code); + +#endif diff --git a/src/drivers/keyboard/keyboard_internal.h b/src/drivers/keyboard/keyboard_internal.h new file mode 100644 index 0000000..7adf6e6 --- /dev/null +++ b/src/drivers/keyboard/keyboard_internal.h @@ -0,0 +1,9 @@ +#ifndef KEYBOARD_INTERNAL_H +#define KEYBOARD_INTERNAL_H + +// PS/2 controller I/O registers +#define KBD_IO_DATA_PORT 0x60 +#define KBD_IO_STATUS_REGISTER 0x64 +#define KBD_IO_COMMAND_REGISTER 0x64 + +#endif diff --git a/src/drivers/vga/vga_buffer.c b/src/drivers/vga/buffer.c similarity index 59% rename from src/drivers/vga/vga_buffer.c rename to src/drivers/vga/buffer.c index 9a002b3..c5fa8ed 100644 --- a/src/drivers/vga/vga_buffer.c +++ b/src/drivers/vga/buffer.c @@ -2,11 +2,29 @@ vga_global_info g_vga_state = {}; -// private +// private // + +static bool vga_set_crtc_cursor(uint8_t screen_nbr) { + vga_screen_info screen; + if (screen_nbr >= VGA_SCREEN_MAX) + return false; + + screen = g_vga_state.screen[screen_nbr]; + if (screen.cursor.column >= VGA_COL && screen.cursor.row >= VGA_ROW) + return false; + screen.buffer.pos[screen.cursor.row * VGA_COL + screen.cursor.column + 1] = + (vga_char){.color = screen.attributes}; + // TODO param for cursor size? + vga_crtc_enable_cursor(VGA_CURSOR_HEIGHT); + vga_crtc_set_cursor(screen.cursor.column, screen.cursor.row); + + return true; +} vga_screen_info *vga_set_cursor(vga_info *info, bool insert_newline) { vga_screen_info *screen = &g_vga_state.screen[info->screen]; + screen->cursor.vga = info->nocursor; // load cursor info from the screen state once if ((!info->nocursor || info->setcursor) && !info->internal.cursor_loaded) { // if you want to set that to 0 you'll need nocursor, that's an obvious flaw @@ -32,15 +50,19 @@ vga_screen_info *vga_set_cursor(vga_info *info, bool insert_newline) { if (info->noscroll) return 0; - // cycle buffer at end of scroll + // TODO cycle buffer at end of scroll if ((screen->buffer.pos + VGA_SCREEN_SIZE) >= screen->buffer.tail) { - vga_char tmp[VGA_SCREEN_SIZE] = {0}; - - memcpy(tmp, screen->buffer.pos, (VGA_SCREEN_SIZE) * sizeof(vga_char)); - bzero(screen->buffer.head, (VGA_SCREEN_SIZE) * sizeof(vga_char)); + // vga_char tmp[VGA_SCREEN_SIZE] = {0}; + + // memcpy(tmp, screen->buffer.pos, (VGA_SCREEN_SIZE) * sizeof(vga_char)); + // bzero(screen->buffer.head, (VGA_SCREEN_SIZE) * sizeof(vga_char)); + // screen->buffer.pos = screen->buffer.head; + // memcpy(screen->buffer.head, tmp + VGA_COL, + // (VGA_SCREEN_SIZE - VGA_COL) * sizeof(vga_char)); + bzero(screen->buffer.head, g_vga_state.buffer.size * sizeof(vga_char)); + vga_screen_clear(0); + vga_screen_setcursorpos(info->screen, 0, 0); screen->buffer.pos = screen->buffer.head; - memcpy(screen->buffer.head, tmp + VGA_COL, - (VGA_SCREEN_SIZE - VGA_COL) * sizeof(vga_char)); } else { screen->buffer.pos += VGA_COL; } @@ -57,7 +79,6 @@ vga_screen_info *vga_set_cursor(vga_info *info, bool insert_newline) { return screen; } -// TODO scrolling cursor and history int vga_buffer_writechar(vga_info *info, const unsigned char c) { int result = 0; unsigned char wrap_char = VGA_WRAP_DEFAULT_CHAR; @@ -104,6 +125,7 @@ int vga_buffer_write(vga_info *info, const unsigned char *s, bool is_ascii) { size_t result = 0; while (*str) { + // TODO: \t if (is_ascii && *str == '\n') vga_set_cursor(info, true); else @@ -113,17 +135,15 @@ int vga_buffer_write(vga_info *info, const unsigned char *s, bool is_ascii) { return result; } -// public +// public // -int vga_init(size_t history_size, uint16_t *buffer_addr) { +bool vga_init(size_t history_size, uint16_t *buffer_addr) { static bool init = false; - if (history_size == 0) - return -1; - // initalizing screens in RAM - if (!init || (g_vga_state.buffer.addr != buffer_addr) || - (g_vga_state.buffer.history_size != history_size)) { + if (history_size != 0 && + (!init || (g_vga_state.buffer.addr != buffer_addr) || + (g_vga_state.buffer.history_size != history_size))) { // set the global state g_vga_state.vga_addr = (uint16_t *)VGA_HW_ADDR; g_vga_state.buffer.addr = buffer_addr; @@ -151,60 +171,109 @@ int vga_init(size_t history_size, uint16_t *buffer_addr) { init = true; } - return 1; + return init; } -int vga_screen_setattributes(uint8_t screen_nbr, vga_attributes attributes) { +// Getters / Setters + +bool vga_screen_getcursorpos(uint8_t screen_nbr, uint8_t *column, + uint8_t *row) { vga_screen_info *screen; if (screen_nbr >= VGA_SCREEN_MAX) - return -42; + return false; + + screen = &g_vga_state.screen[screen_nbr]; + *column = screen->cursor.column; + *row = screen->cursor.row; + return true; +} + +bool vga_screen_setcursorpos(uint8_t screen_nbr, uint8_t column, uint8_t row) { + vga_screen_info *screen; + + if (screen_nbr >= VGA_SCREEN_MAX || column >= VGA_COL || row >= VGA_ROW) + return false; + + screen = &g_vga_state.screen[screen_nbr]; + screen->cursor.column = column; + screen->cursor.row = row; + return true; +} + +bool vga_screen_setattributes(uint8_t screen_nbr, vga_attributes attributes) { + vga_screen_info *screen; + + if (screen_nbr >= VGA_SCREEN_MAX) + return false; screen = &g_vga_state.screen[screen_nbr]; screen->attributes = attributes; - return 1; + return true; } -int vga_screen_fillattributes(uint8_t screen_nbr, vga_attributes attributes) { +bool vga_screen_setvgacursor(uint8_t screen_nbr, bool enable_cursor) { vga_screen_info *screen; if (screen_nbr >= VGA_SCREEN_MAX) - return -42; + return false; + + screen = &g_vga_state.screen[screen_nbr]; + screen->cursor.vga = enable_cursor; + + return true; +} + +bool vga_screen_fillattributes(uint8_t screen_nbr, vga_attributes attributes) { + vga_screen_info *screen; + + if (screen_nbr >= VGA_SCREEN_MAX) + return false; screen = &g_vga_state.screen[screen_nbr]; screen->attributes = attributes; for (size_t i = 0; i < VGA_SCREEN_SIZE; ++i) screen->buffer.pos[i].color = attributes; - return 1; + return true; } -int vga_screen_fillbackground(uint8_t screen_nbr, - enum vga_color background_color) { +bool vga_screen_fillbackground(uint8_t screen_nbr, + enum vga_color background_color) { vga_screen_info *screen; if (screen_nbr >= VGA_SCREEN_MAX) - return -42; + return false; screen = &g_vga_state.screen[screen_nbr]; screen->attributes.bg = background_color; for (size_t i = 0; i < VGA_SCREEN_SIZE; ++i) screen->buffer.pos[i].color.bg = background_color; - return 1; + return true; } -int vga_screen_show(uint8_t screen_nbr) { +// Show + +bool vga_screen_show(uint8_t screen_nbr) { + vga_screen_info screen; if (screen_nbr >= VGA_SCREEN_MAX) - return -42; + return false; + + screen = g_vga_state.screen[screen_nbr]; + if (screen.cursor.vga) { + vga_set_crtc_cursor(screen_nbr); + } else + vga_crtc_disable_cursor(); - memcpy(g_vga_state.vga_addr, g_vga_state.screen[screen_nbr].buffer.pos, + memcpy(g_vga_state.vga_addr, screen.buffer.pos, VGA_SCREEN_SIZE * sizeof(vga_char)); - return 1; + return true; } -int vga_screen_show_scrolled(uint8_t screen_nbr, uint32_t rows) { +int vga_screen_show_scrolled(uint8_t screen_nbr, int rows) { vga_screen_info *screen; vga_char *target; + bool isnotup = true; if (screen_nbr >= VGA_SCREEN_MAX) return -42; @@ -212,19 +281,38 @@ int vga_screen_show_scrolled(uint8_t screen_nbr, uint32_t rows) { // Overlay not implemented yet screen = &g_vga_state.screen[screen_nbr]; target = screen->buffer.pos - (rows * VGA_COL); - if (target < screen->buffer.head) + if (rows < 0 || target < screen->buffer.head) { target = screen->buffer.head; + isnotup = false; + } + + // disabled cursor if scrolled up + if (screen->cursor.vga && !rows) { + // TODO param for cursor size? and set attrs to current instead of -1 + vga_set_crtc_cursor(screen_nbr); + } else + vga_crtc_disable_cursor(); memcpy(g_vga_state.vga_addr, target, VGA_SCREEN_SIZE * sizeof(vga_char)); + return isnotup; +} - return 1; +int vga_screen_getscrolloffset(uint8_t screen_nbr) { + vga_screen_info screen; + + if (screen_nbr >= VGA_SCREEN_MAX) + return -42; + screen = g_vga_state.screen[screen_nbr]; + return (screen.buffer.pos - screen.buffer.head) / 80; } -int vga_screen_clear(uint8_t screen_nbr) { +// Clear + +bool vga_screen_clear(uint8_t screen_nbr) { vga_screen_info *screen; if (screen_nbr >= VGA_SCREEN_MAX) - return -42; + return false; screen = &g_vga_state.screen[screen_nbr]; // reset the cursor @@ -234,5 +322,5 @@ int vga_screen_clear(uint8_t screen_nbr) { // clear the screen bzero(screen->buffer.pos, VGA_SCREEN_SIZE * sizeof(vga_char)); - return 1; + return true; } diff --git a/src/drivers/vga/crtc.c b/src/drivers/vga/crtc.c new file mode 100644 index 0000000..ae85b28 --- /dev/null +++ b/src/drivers/vga/crtc.c @@ -0,0 +1,37 @@ +#include "vga.h" +#include "vga_internal.h" + +// http://www.osdever.net/FreeVGA/vga/crtcreg.htm + +void vga_crtc_disable_cursor() { + // Select the cursor start register in the CRTC + outb(VGA_CRTC_INDEX, VGA_CRTC_REG_CURSOR_START); + // Write the CD bit + outb(VGA_CRTC_DATA, 1 << 5); +} + +void vga_crtc_enable_cursor(uint8_t scanline_start, uint8_t scanline_end) { + // protect from overflow + if (scanline_start > 15 || scanline_end > 15) { + scanline_start = 0; + scanline_end = 15; + } + + outb(VGA_CRTC_INDEX, VGA_CRTC_REG_CURSOR_START); // select the start register + outb(VGA_CRTC_DATA, scanline_start); // disable CD and set cusor start + + outb(VGA_CRTC_INDEX, VGA_CRTC_REG_CURSOR_END); + outb(VGA_CRTC_DATA, scanline_end); +} + +void vga_crtc_set_cursor(uint8_t column, uint8_t row) { + uint16_t position = row * VGA_COL + column; + + // HIGH cursor byte + outb(VGA_CRTC_INDEX, VGA_CRTC_REG_CURSOR_LOCATION_HIGH); + outb(VGA_CRTC_DATA, position >> 8); + + // LOW cursor byte + outb(VGA_CRTC_INDEX, VGA_CRTC_REG_CURSOR_LOCATION_LOW); + outb(VGA_CRTC_DATA, position); +} diff --git a/src/drivers/vga/vga_printf.c b/src/drivers/vga/printf.c similarity index 98% rename from src/drivers/vga/vga_printf.c rename to src/drivers/vga/printf.c index efd529c..13b174b 100644 --- a/src/drivers/vga/vga_printf.c +++ b/src/drivers/vga/printf.c @@ -1,5 +1,6 @@ #include "vga.h" #include "vga_internal.h" +#include /// @brief implement printf argument reading /// @return numbers of character written, negative if error @@ -63,7 +64,6 @@ int vga_printf_readarg(vga_info *info, const char *format, va_list *ap, default: // Not implemented yet return -1; - break; } return result; } @@ -84,6 +84,7 @@ int vga_vdprintf(vga_info info, const char *format, va_list ap) { case '\n': vga_set_cursor(&info, true); break; + // TODO \r, maybe \b default: result += vga_buffer_writechar(&info, format[i]); } diff --git a/src/drivers/vga/vga.h b/src/drivers/vga/vga.h index c3b19ac..13499f6 100644 --- a/src/drivers/vga/vga.h +++ b/src/drivers/vga/vga.h @@ -5,14 +5,38 @@ #include #include -// VGA text driver +// VGA text driver // + +#define VGA_ROW 25 +#define VGA_COL 80 +/// VGA_ROW * VGA_COL +#define VGA_SCREEN_SIZE 2000 /// max number of screens -#define VGA_SCREEN_MAX 10 +#define VGA_SCREEN_MAX 12 /// default character used when wrapping #define VGA_WRAP_DEFAULT_CHAR '>' +#define VGA_CURSOR_HEIGHT 13, 15 + +/// HW address of the VGA buffer +#define VGA_HW_ADDR 0xB8000 + +/// CRTC I/O index addr, used to select reg +#define VGA_CRTC_INDEX 0x3D4 +/// CRTC I/O data addr +#define VGA_CRTC_DATA 0x3D5 + +/// 7 | CD, set == cursor disabled 5 | Cursor scan line start 4 3 2 1 0 | +#define VGA_CRTC_REG_CURSOR_START 0x0A +/// 7 | CSK 6 5 | Cursor scan line end 4 3 2 1 0 | +#define VGA_CRTC_REG_CURSOR_END 0x0B +/// HIGH (<< 8) bytes of the VGA cursor location +#define VGA_CRTC_REG_CURSOR_LOCATION_HIGH 0x0E +/// LOW bytes of the VGA cursor location +#define VGA_CRTC_REG_CURSOR_LOCATION_LOW 0x0F + /// @brief VGA colors, goes only up to 8 for the background enum vga_color { VGA_COLOR_BLACK = 0, @@ -66,14 +90,16 @@ typedef struct vga_info { } internal; /// DO NOT TOUCH, sad sad sad lack of separation of concern } vga_info; -// functions +// Functions // + +// driver init /// @brief initializes the vga driver, call before any other vga function /// @param history_size needs to be at least 1, amount of screens worth of /// history, N * 80*25 * sizeof(vga_char). /// @param buffer_addr address of the screen buffers in memory -/// @return positive if initialize succeeded, negative if it failed -int vga_init(size_t history_size, uint16_t *buffer_addr); +/// @return false if the screen doesn't exist or is already initalized +bool vga_init(size_t history_size, uint16_t *buffer_addr); /// @brief output formatted string to a vga screen buffer (not posix) /// @param info set the control info, pass a vga_info struct @@ -82,40 +108,86 @@ int vga_init(size_t history_size, uint16_t *buffer_addr); /// @return negative if error, number of chars written otherwise int vga_printf(vga_info info, const char *format, ...); +// getters/setters + +/// @brief get the position of the cursor into column and row +/// @param screen_nbr screen to get the cursor position of +/// @param column destination variable for the column position +/// @param row destionation variable for the row position +/// @return negative if screen doesn't exist +bool vga_screen_getcursorpos(uint8_t screen_nbr, uint8_t *column, uint8_t *row); + +/// @brief set the position of the cursor +/// @param screen_nbr screen to set the cursor position in +/// @param column cursor column position (starts at 0) +/// @param row cursor row position (starts at 0) +/// @return negative if screen doesn't exist or values overflow +bool vga_screen_setcursorpos(uint8_t screen_nbr, uint8_t column, uint8_t row); + +/// @brief enable or disable the vga cursor attribute on a screen +/// @param screen_nbr screen to enable the cursor on +/// @param enable_cursor enable or disable cursor +/// @return negative if screen doesn't exist +bool vga_screen_setvgacursor(uint8_t screen_nbr, bool enable_cursor); + /// @brief set color attributes for next characters on a given screen /// @param screen_nbr nbr of the screen to change attrs on /// @param attributes color attributes to set -/// @return negative if screen doesn't exist -int vga_screen_setattributes(uint8_t screen_nbr, vga_attributes attributes); +/// @return false if screen doesn't exist +bool vga_screen_setattributes(uint8_t screen_nbr, vga_attributes attributes); /// @brief replace character color attributes for a given screen and set them /// @param screen_nbr nbr of the screen to edit /// @param attributes color attributes to override existing values with -/// @return negative if screen doesn't exist -int vga_screen_fillattributes(uint8_t screen_nbr, vga_attributes attributes); +/// @return false if screen doesn't exist +bool vga_screen_fillattributes(uint8_t screen_nbr, vga_attributes attributes); /// @brief replace background color for a given screen and set it /// @param screen_nbr nbr of the screen to edit /// @param background_color background color to fill the screen with -/// @return negative if screen doesn't exist -int vga_screen_fillbackground(uint8_t screen_nbr, - enum vga_color background_color); +/// @return false if screen doesn't exist +bool vga_screen_fillbackground(uint8_t screen_nbr, + enum vga_color background_color); + +/// @brief Get position in history buffer in screen +/// @param screen_nbr nbr of the screen to get history pos of +/// @return negative if screen doesn't exist, nbr or rows scrolled up in buffer +int vga_screen_getscrolloffset(uint8_t screen_nbr); + +// display /// @brief display the selected screen buffer /// @param screen_nbr screen to print -/// @return negative if screen doesn't exist -int vga_screen_show(uint8_t screen_nbr); +/// @return false if screen doesn't exist +bool vga_screen_show(uint8_t screen_nbr); /// @brief display the selected screen buffer, scrolled /// @param screen_nbr screen to print -/// @param rows number of rows to scroll up by +/// @param rows number of rows to scroll up by, -1 to go at the top /// TODO @param scroll_overlay display position in buffer -/// @return negative if screen doesn't exist -int vga_screen_show_scrolled(uint8_t screen_nbr, uint32_t rows); +/// @return negative if screen doesn't exist, 0 if at buffer head +int vga_screen_show_scrolled(uint8_t screen_nbr, int rows); + +// reset /// @brief clear the screen and reset cursor info /// @param screen_nbr screen to clear -/// @return negative if screen doesn't exist -int vga_screen_clear(uint8_t screen_nbr); +/// @return false if screen doesn't exist +bool vga_screen_clear(uint8_t screen_nbr); + +// CRTC + +/// @brief set the CD bit in the crtc +void vga_crtc_disable_cursor(); + +/// @brief enable the cursor and set it's scanlines (shape) +/// @param scanline_start needs to be < 15 +/// @param scanline_end needs to be < 15 +void vga_crtc_enable_cursor(uint8_t scanline_start, uint8_t scanline_end); + +/// @brief enable the VGA cursor and set its position +/// @param column cursor column, starts at 0 must be < 80 +/// @param row cursor row starts at 0 must be < 25 +void vga_crtc_set_cursor(uint8_t column, uint8_t row); #endif diff --git a/src/drivers/vga/vga_internal.h b/src/drivers/vga/vga_internal.h index 1c03bfd..6dd2e46 100644 --- a/src/drivers/vga/vga_internal.h +++ b/src/drivers/vga/vga_internal.h @@ -9,16 +9,6 @@ #include #include -// HW // - -/// HW address of the VGA buffer -#define VGA_HW_ADDR 0xB8000 - -#define VGA_ROW 25 -#define VGA_COL 80 -/// VGA_ROW * VGA_COL -#define VGA_SCREEN_SIZE 2000 - // STATE // /// @brief per screen vga state @@ -26,6 +16,7 @@ typedef struct vga_screen_info { struct { uint8_t column; /// store last column uint8_t row; /// store last row + bool vga; /// show the vga cursor } cursor; /// store last position vga_attributes attributes; /// used to store current color attributes struct { diff --git a/src/kernel.c b/src/kernel.c index a56c9a3..e320574 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -2,28 +2,100 @@ #include #include +#include "drivers/keyboard/keyboard.h" #include "drivers/vga/vga.h" #include "kernel.h" #include "klibc/libc.h" +#include "cp437.h" +#include "screens/screens.h" #include "test/test.h" -/// @brief Wait indefinitely -void __attribute__((noinline)) kernel_wait(void) { - while (42) { - } +#define SCREEN_TOTAL 12 + +enum screen_numbers { + HOME_SCREEN, + NOTE_SCREEN, + IPSUM_SCREEN, + PRINTF_DEMO_SCREEN = 8, + VGA_DEMO_SCREEN, +}; + +static void notepad_greet(uint8_t screen_nbr) { + vga_printf( + (vga_info){.screen = screen_nbr, .row = 1, .column = 35}, + "%aNotepad%a\n\n>", + (vga_attributes){.bg = VGA_COLOR_LIGHT_GREY, .fg = VGA_COLOR_BLACK}, + (vga_attributes){.fg = VGA_COLOR_WHITE}); +} + +static void vga_demo(uint8_t screen_nbr) { + test_vga_cp437(screen_nbr); + vga_printf((vga_info){.screen = screen_nbr}, "\n"); + test_vga_color(screen_nbr, CP437_SMILEY); +} + +void screen_init(uint8_t screen_nbr) { + struct panel { + char title[40]; + void (*screen_init_func)(uint8_t); + }; + struct panel screen[12] = { + [HOME_SCREEN] = {"Home screen (current)"}, + [NOTE_SCREEN] = {"Notepad", ¬epad_greet}, + [IPSUM_SCREEN] = {"Lorem ipsum dolor sit amet", &screen_lorem_ipsum}, + [PRINTF_DEMO_SCREEN] = {"vga_printf() demo", &test_printf}, + [VGA_DEMO_SCREEN] = {"CP437 and Colors demo", &vga_demo}}; + uint8_t last_row, last_col; + + vga_screen_setattributes(screen_nbr, (vga_attributes){.fg = VGA_COLOR_RED}); + + // logo + vga_printf((vga_info){.screen = screen_nbr, .setcursor = true, .row = 2}, + "%s\n", KFS_LOGO); + + // version + vga_printf((vga_info){.screen = screen_nbr, .nowrap = true, .column = 35}, + "vers: %s\n", VERSION); + + // silly box + vga_screen_getcursorpos(screen_nbr, &last_col, &last_row); + vga_screen_setcursorpos(screen_nbr, 0, 0); + screen_create_box_light(screen_nbr, VGA_COL - 1, VGA_ROW - 1); + vga_screen_setcursorpos(screen_nbr, last_col, last_row); + + // Print function keys and init screens + vga_screen_setattributes(screen_nbr, (vga_attributes){.fg = VGA_COLOR_WHITE}); + for (uint8_t i = 0; i < SCREEN_TOTAL; ++i) + if (screen[i].title[0]) { + if (screen[i].screen_init_func) + screen[i].screen_init_func(i); + + vga_printf((vga_info){.screen = screen_nbr, .column = 5}, "F%u: %s\n", + i + 1, screen[i].title); + } + + vga_printf((vga_info){.screen = screen_nbr, .column = 5}, + "%C %C: Scroll up and down\n", CP437_UP_ARROW, CP437_DOWN_ARROW); + vga_printf((vga_info){.screen = screen_nbr, .column = 5}, + "HOME: Scroll to the top of the screen\n"); + vga_printf((vga_info){.screen = screen_nbr, .column = 5}, + "END: Scroll to the bottom of the screen\n"); + + vga_screen_show(screen_nbr); } /// @brief The entrypoint of our kernel void kernel_main(void) { - int *memory_signature = (void *)SIGNATURE_ADDRESS; + volatile int *memory_signature = (void *)SIGNATURE_ADDRESS; - // initialized the vga driver with an history size of 16*25 rows + // initialized the vga driver and set screens vga_init(16, (uint16_t *)SCREEN_BUFFER_ADDR); - test_vga(9, false, true); // write tests on scr 9 w/o scrolling and print them + screen_init(HOME_SCREEN); // Set the kernel signature *memory_signature = SIGNATURE_VALUE; - kernel_wait(); + // call the home screen loop + screen_loop(HOME_SCREEN, NOTE_SCREEN); } diff --git a/src/kernel.h b/src/kernel.h index e29edec..252cb65 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -3,6 +3,13 @@ #include +// git commit/tag version +#ifndef VERSION +#define VERSION "UNKNOWN" +#endif + +// MEMORY + #ifndef SIGNATURE_ADDRESS /// Signature of our kernel, at around 3MB #define SIGNATURE_ADDRESS 0x0030DEAD @@ -15,4 +22,15 @@ /// can get big quickly depending on the history size #define SCREEN_BUFFER_ADDR 0x001FFFFF +// Screens +#define KFS_LOGO \ + " d8888 .d8888b. \n" \ + " d8P888 d88P Y88b '|| .'|. ' \n" \ + " d8P 888 888 || .. .||. .... /| \n" \ + " d8P 888 .d88P || .' || ||. ' /|| \n" \ + " d88 888 .od888P\" ||'|. || . '|.. || \n" \ + " 8888888888d88P\" .||. ||. .||. |'..|' || \n" \ + " 888 888\" || \n" \ + " 888 888888888 ,/-' \n" + #endif diff --git a/src/klibc/asm.c b/src/klibc/asm.c new file mode 100644 index 0000000..928e2c2 --- /dev/null +++ b/src/klibc/asm.c @@ -0,0 +1,12 @@ +#include + +uint8_t inb(uint16_t port) { + uint8_t ret; + + __asm__ volatile("inb %1, %0" : "=a"(ret) : "Nd"(port)); + return ret; +} + +void outb(uint16_t port, uint8_t data) { + __asm__ volatile("outb %0, %1" : : "a"(data), "Nd"(port)); +} diff --git a/src/klibc/libc.h b/src/klibc/libc.h index 5ac7556..d969930 100644 --- a/src/klibc/libc.h +++ b/src/klibc/libc.h @@ -2,6 +2,7 @@ #define LIBC_H #include +#include //// ctype.h //// @@ -113,4 +114,16 @@ char *itoa(char *str, int value, unsigned char base); /// @return pointer to str on success, NULL with a non-valid base argument char *utoa(char *str, unsigned int value, unsigned char base); +/// x86 programming nonstandard /// + +/// @brief returns a byte read from an I/O port +/// @param port address to read from +/// @return byte-sized content of the port +uint8_t inb(uint16_t port); + +/// @brief write a byte to an I/O port +/// @param port address to write in +/// @param data value to write in port +void outb(uint16_t port, uint8_t data); + #endif diff --git a/src/screens/box.c b/src/screens/box.c new file mode 100644 index 0000000..4a587f6 --- /dev/null +++ b/src/screens/box.c @@ -0,0 +1,53 @@ +#include "../cp437.h" +#include "../drivers/vga/vga.h" + +bool screen_create_box_light(uint8_t screen_nbr, uint8_t width, + uint8_t height) { + uint8_t cursor_column; + uint8_t cursor_row; + + if (!vga_screen_getcursorpos(screen_nbr, &cursor_column, &cursor_row) || + (cursor_column + width) >= VGA_COL || (cursor_row + height >= VGA_ROW)) + return false; + + // Vertical + for (uint8_t i = cursor_row; i <= height + cursor_row; ++i) { + uint8_t current_char = CP437_BOX_DRAWINGS_LIGHT_VERTICAL; + vga_printf((vga_info){.screen = screen_nbr, + .nocursor = true, + .row = i, + .column = cursor_column}, + "%C", current_char); + vga_printf((vga_info){.screen = screen_nbr, + .nocursor = true, + .row = i, + .column = cursor_column + width}, + "%C", current_char); + } + + // Horizontal + for (uint8_t i = cursor_column; i <= width + cursor_column; ++i) { + uint8_t current_char = CP437_BOX_DRAWINGS_LIGHT_HORIZONTAL; + + if (i == cursor_column) + current_char = CP437_BOX_DRAWINGS_LIGHT_DOWN_AND_RIGHT; + if (i == width) + current_char = CP437_BOX_DRAWINGS_LIGHT_DOWN_AND_LEFT; + vga_printf((vga_info){.screen = screen_nbr, + .nocursor = true, + .row = cursor_row, + .column = i}, + "%C", current_char); + if (i == cursor_column) + current_char = CP437_BOX_DRAWINGS_LIGHT_UP_AND_RIGHT; + if (i == width) + current_char = CP437_BOX_DRAWINGS_LIGHT_UP_AND_LEFT; + vga_printf((vga_info){.screen = screen_nbr, + .nocursor = true, + .row = cursor_row + height, + .column = i}, + "%C", current_char); + } + + return true; +} diff --git a/src/screens/ipsum.c b/src/screens/ipsum.c new file mode 100644 index 0000000..04aead0 --- /dev/null +++ b/src/screens/ipsum.c @@ -0,0 +1,41 @@ +#include "../drivers/vga/vga.h" + +#define LOREM_IPSUM \ + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. " \ + "Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies " \ + "sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius " \ + "a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy " \ + "molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. " \ + "Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, " \ + "enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. " \ + "Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent " \ + "blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. " \ + "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere " \ + "cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque " \ + "fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\n\nUt " \ + "velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel " \ + "massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non " \ + "tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae " \ + "ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur " \ + "aliquet pellentesque diam. Integer quis metus vitae elit lobortis " \ + "egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi " \ + "vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor " \ + "tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris " \ + "ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, " \ + "metus purus iaculis lectus, et tristique ligula justo vitae " \ + "magna.\nAliquam convallis sollicitudin purus. Praesent aliquam, enim at " \ + "fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu " \ + "lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod " \ + "libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean " \ + "suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt " \ + "tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna " \ + "fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu " \ + "amet.\n" + +void screen_lorem_ipsum(uint8_t screen_nbr) { + vga_screen_fillattributes( + screen_nbr, + (vga_attributes){.bg = VGA_COLOR_LIGHT_GREY, .fg = VGA_COLOR_BLACK}); + vga_printf((vga_info){.screen = screen_nbr, .nowrapchar = true}, + LOREM_IPSUM "\nagain:\n" LOREM_IPSUM); +} diff --git a/src/screens/loop.c b/src/screens/loop.c new file mode 100644 index 0000000..544d29b --- /dev/null +++ b/src/screens/loop.c @@ -0,0 +1,83 @@ +#include "../drivers/keyboard/keyboard.h" +#include "../drivers/vga/vga.h" +#include "../klibc/libc.h" +#include "screens.h" + +static int set_scroll(uint8_t screen_nbr, scancode code, int scroll_state, + bool isnotup) { + switch (code) { + case KBD_HOME: + scroll_state = -1; + break; + case KBD_END: + scroll_state = 0; + break; + case KBD_CURSOR_UP: + if (isnotup) + scroll_state++; + break; + case KBD_CURSOR_DOWN: + if (scroll_state > 0) + scroll_state--; + if (scroll_state == -1) { + scroll_state = vga_screen_getscrolloffset(screen_nbr) - 1; + } + break; + default: + } + return scroll_state; +} + +void screen_loop(uint8_t home_screen, uint8_t note_screen) { + uint8_t current_screen = home_screen; + int scroll_state = 0; + bool isnotup = vga_screen_show_scrolled(current_screen, scroll_state); + + // TODO function or charmap for the homepage print + while (42) { + scancode code = 0; + char c = 0; + + if ((code = kbd_get())) { + c = kbd_code_to_ascii(code); + + if ((isprint(c) || code == KBD_SPACE) && current_screen == note_screen) { + vga_printf((vga_info){.screen = note_screen}, "%C", c); + scroll_state = 0; + } else { + // control characters + switch (code) { + case KBD_F1 ... KBD_F10: + // switch screen + current_screen = code - KBD_F1; + scroll_state = 0; + break; + case KBD_HOME ... KBD_PAGE_DOWN: + // scroll + scroll_state = + set_scroll(current_screen, code, scroll_state, isnotup); + break; + // control chars + case KBD_DELETE: + if (current_screen == note_screen) + vga_screen_clear(note_screen); + break; + case KBD_ENTER: + if (current_screen == note_screen) + vga_printf((vga_info){.screen = note_screen}, "\n"); + break; + case KBD_TAB: + if (!current_screen) + vga_printf((vga_info){.screen = note_screen}, " "); + break; + default: + break; + } + } + + if (current_screen == note_screen) + vga_screen_setvgacursor(note_screen, true); + isnotup = vga_screen_show_scrolled(current_screen, scroll_state); + } + } +} diff --git a/src/screens/screens.h b/src/screens/screens.h new file mode 100644 index 0000000..07562ae --- /dev/null +++ b/src/screens/screens.h @@ -0,0 +1,26 @@ +#ifndef SCREENS_H +#define SCREENS_h + +#include + +// Screens + +/// @brief everyone's favorite, lorem ipsum +/// @param screen_nbr screen to ipsum to +void screen_lorem_ipsum(uint8_t screen_nbr); + +/// @brief loop handling screen selection +/// @param home_screen nbr of the home_screen +/// @param note_screen nbr of the note screen +void screen_loop(uint8_t home_screen, uint8_t note_screen); + +// Display helpers + +/// @brief create a light box on a given screen starting at the cusor +/// @param screen_nbr nbr of the screen to write to +/// @param width width of the box, starting from 0 +/// @param height height of the box, starting from 0 +/// @return false if box too big or the screen doesn't exist +bool screen_create_box_light(uint8_t screen_nbr, uint8_t width, uint8_t height); + +#endif diff --git a/src/test/test.h b/src/test/test.h index f8cb6c6..1f74b1c 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -7,8 +7,15 @@ /// @brief executes a range of visual tests /// @param screen_nbr screen to write to -/// @param print print the test or not -/// @param scroll print scrolled or not -void test_vga(uint8_t screen_nbr, bool scroll, bool print); +void test_printf(uint8_t screen_nbr); + +/// @brief display the whole cp437 charset +/// @param screen_nbr screen to write to +void test_vga_cp437(uint8_t screen_nbr); + +/// @brief display VGA colors +/// @param screen_nbr screen to write to +/// @param test_char CP437 character to fill +void test_vga_color(uint8_t screen_nbr, uint8_t test_char); #endif diff --git a/src/test/test_printf.c b/src/test/test_printf.c index bf5c290..6eb8850 100644 --- a/src/test/test_printf.c +++ b/src/test/test_printf.c @@ -2,14 +2,15 @@ #include "../drivers/vga/vga.h" #include "test.h" -void test_vga(uint8_t screen_nbr, bool scroll, bool print) { +void test_printf(uint8_t screen_nbr) { // test background vga_screen_fillbackground(screen_nbr, VGA_COLOR_BLUE); // test scroll - vga_printf((vga_info){.screen = screen_nbr}, "This should be invisible2\n"); - vga_printf((vga_info){.screen = screen_nbr}, "This should be invisible\n"); + vga_printf((vga_info){.screen = screen_nbr}, "This shouldn't be in view\n"); + vga_printf((vga_info){.screen = screen_nbr}, + "This REALLY shouldn't be in view\n"); vga_printf( (vga_info){.screen = screen_nbr, .row = 24, .scrollattributes = true}, "\n\n"); // put stuff above in history @@ -35,8 +36,23 @@ void test_vga(uint8_t screen_nbr, bool scroll, bool print) { "int: %i hex: %x unsigned:%u octal: %o binary: %b", -42, 42, 42, 42, 42); vga_printf((vga_info){.screen = screen_nbr, .row = 24, .column = 78}, "%CY", CP437_YEN_SIGN); +} + +void test_vga_cp437(uint8_t screen_nbr) { + uint8_t cp437[255] = {0}; + + for (uint8_t c = 1; c < 255; ++c) + cp437[c] = c; + vga_printf((vga_info){.screen = screen_nbr, .nowrapchar = true}, "%C%S", 0, + cp437 + 1); +} - // print - if (print) - vga_screen_show_scrolled(screen_nbr, (scroll) ? 2 : 0); +void test_vga_color(uint8_t screen_nbr, uint8_t test_char) { + for (uint8_t bg = 0; bg < 8; ++bg) { + for (uint8_t fg = 0; fg < 16; ++fg) { + vga_printf((vga_info){.screen = screen_nbr, .nowrap = true}, "%a%C", + (vga_attributes){.bg = bg, .fg = fg}, test_char); + } + vga_printf((vga_info){.screen = screen_nbr}, "\n"); + } }