From c42c607c80c2920b585ee1a65a9c66ec68acd609 Mon Sep 17 00:00:00 2001 From: tkchia Date: Sun, 4 Sep 2022 09:05:45 +0000 Subject: [PATCH] Quick prototype for VGA console output in bare metal mode --- Makefile | 5 +- examples/examples.mk | 1 + examples/hello4.c | 2 + libc/calls/calls.mk | 3 +- libc/calls/writev-metal.c | 6 ++ libc/intrin/g_fds.c | 14 ++++- libc/libc.mk | 1 + libc/vga/vga.h | 15 +++++ libc/vga/vga.mk | 57 +++++++++++++++++ libc/vga/writev-vga.c | 129 ++++++++++++++++++++++++++++++++++++++ 10 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 libc/vga/vga.h create mode 100644 libc/vga/vga.mk create mode 100644 libc/vga/writev-vga.c diff --git a/Makefile b/Makefile index 42661a907..20c2d5cc6 100644 --- a/Makefile +++ b/Makefile @@ -111,6 +111,7 @@ include libc/linux/linux.mk # │ You can manipulate arrays include libc/tinymath/tinymath.mk # │ You can issue raw system calls include third_party/compiler_rt/compiler_rt.mk # │ include libc/str/str.mk # │ +include libc/vga/vga.mk # │ include third_party/xed/xed.mk # │ include third_party/zlib/zlib.mk # │ include libc/elf/elf.mk # │ @@ -316,7 +317,8 @@ COSMOPOLITAN_OBJECTS = \ LIBC_SYSV \ LIBC_INTRIN \ LIBC_NT_KERNEL32 \ - LIBC_NEXGEN32E + LIBC_NEXGEN32E \ + LIBC_VGA COSMOPOLITAN_HEADERS = \ APE \ @@ -341,6 +343,7 @@ COSMOPOLITAN_HEADERS = \ LIBC_TINYMATH \ LIBC_X \ LIBC_ZIPOS \ + LIBC_VGA \ NET_HTTP \ THIRD_PARTY_DLMALLOC \ THIRD_PARTY_GDTOA \ diff --git a/examples/examples.mk b/examples/examples.mk index 1deecdb80..1f1134f09 100644 --- a/examples/examples.mk +++ b/examples/examples.mk @@ -66,6 +66,7 @@ EXAMPLES_DIRECTDEPS = \ LIBC_THREAD \ LIBC_TIME \ LIBC_TINYMATH \ + LIBC_VGA \ LIBC_X \ LIBC_ZIPOS \ NET_HTTP \ diff --git a/examples/hello4.c b/examples/hello4.c index 960c530a9..f3aecca99 100644 --- a/examples/hello4.c +++ b/examples/hello4.c @@ -10,6 +10,8 @@ #include "libc/math.h" #include "libc/stdio/stdio.h" +STATIC_YOINK("vga_console"); + int main(int argc, char *argv[]) { volatile long double x = -.5; volatile long double y = 1.5; diff --git a/libc/calls/calls.mk b/libc/calls/calls.mk index 2708a40bd..c76919609 100644 --- a/libc/calls/calls.mk +++ b/libc/calls/calls.mk @@ -51,7 +51,8 @@ LIBC_CALLS_A_DIRECTDEPS = \ LIBC_STR \ LIBC_STUBS \ LIBC_SYSV_CALLS \ - LIBC_SYSV + LIBC_SYSV \ + LIBC_VGA LIBC_CALLS_A_DEPS := \ $(call uniq,$(foreach x,$(LIBC_CALLS_A_DIRECTDEPS),$($(x)))) diff --git a/libc/calls/writev-metal.c b/libc/calls/writev-metal.c index c94263782..77bc315b1 100644 --- a/libc/calls/writev-metal.c +++ b/libc/calls/writev-metal.c @@ -19,10 +19,16 @@ #include "libc/calls/struct/fd.internal.h" #include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.internal.h" +#include "libc/intrin/weaken.h" #include "libc/sysv/errfuns.h" +#include "libc/vga/vga.h" ssize_t sys_writev_metal(struct Fd *fd, const struct iovec *iov, int iovlen) { switch (fd->kind) { + case kFdConsole: + if (weaken(sys_writev_vga)) + weaken(sys_writev_vga)(fd, iov, iovlen); + /* fallthrough */ case kFdSerial: return sys_writev_serial(fd, iov, iovlen); default: diff --git a/libc/intrin/g_fds.c b/libc/intrin/g_fds.c index 118a33142..b90a1435b 100644 --- a/libc/intrin/g_fds.c +++ b/libc/intrin/g_fds.c @@ -21,6 +21,7 @@ #include "libc/intrin/pthread.h" #include "libc/intrin/pushpop.h" #include "libc/intrin/spinlock.h" +#include "libc/intrin/weaken.h" #include "libc/nt/runtime.h" #include "libc/sysv/consts/o.h" @@ -55,10 +56,17 @@ textstartup void InitializeFileDescriptors(void) { fds->f = 3; fds->p = fds->__init_p; if (IsMetal()) { + extern const char vga_console[]; pushmov(&fds->f, 3ull); - fds->__init_p[0].kind = pushpop(kFdSerial); - fds->__init_p[1].kind = pushpop(kFdSerial); - fds->__init_p[2].kind = pushpop(kFdSerial); + if (weaken(vga_console)) { + fds->__init_p[0].kind = pushpop(kFdConsole); + fds->__init_p[1].kind = pushpop(kFdConsole); + fds->__init_p[2].kind = pushpop(kFdConsole); + } else { + fds->__init_p[0].kind = pushpop(kFdSerial); + fds->__init_p[1].kind = pushpop(kFdSerial); + fds->__init_p[2].kind = pushpop(kFdSerial); + } fds->__init_p[0].handle = VEIL("r", 0x3F8ull); fds->__init_p[1].handle = VEIL("r", 0x3F8ull); fds->__init_p[2].handle = VEIL("r", 0x3F8ull); diff --git a/libc/libc.mk b/libc/libc.mk index 3dde7c5f4..de254707c 100644 --- a/libc/libc.mk +++ b/libc/libc.mk @@ -30,6 +30,7 @@ o/$(MODE)/libc: o/$(MODE)/libc/calls \ o/$(MODE)/libc/thread \ o/$(MODE)/libc/time \ o/$(MODE)/libc/tinymath \ + o/$(MODE)/libc/vga \ o/$(MODE)/libc/x \ o/$(MODE)/libc/zipos \ $(LIBC_CHECKS) diff --git a/libc/vga/vga.h b/libc/vga/vga.h new file mode 100644 index 000000000..dfa4fbedc --- /dev/null +++ b/libc/vga/vga.h @@ -0,0 +1,15 @@ +#ifndef COSMOPOLITAN_LIBC_VGA_VGA_H_ +#define COSMOPOLITAN_LIBC_VGA_VGA_H_ + +#if !(__ASSEMBLER__ + __LINKER__ + 0) +#include "libc/calls/struct/fd.internal.h" +#include "libc/calls/struct/iovec.h" +#include "libc/calls/struct/iovec.internal.h" + +COSMOPOLITAN_C_START_ + +ssize_t sys_writev_vga(struct Fd *, const struct iovec *, int); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_VGA_VGA_H_ */ diff --git a/libc/vga/vga.mk b/libc/vga/vga.mk new file mode 100644 index 000000000..2fb20f310 --- /dev/null +++ b/libc/vga/vga.mk @@ -0,0 +1,57 @@ +#-*-mode:makefile-gmake;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐ +#───vi: set et ft=make ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘ + +PKGS += LIBC_VGA + +LIBC_VGA_ARTIFACTS += LIBC_VGA_A +LIBC_VGA_A = o/$(MODE)/libc/vga/vga.a +LIBC_VGA_A_FILES := $(wildcard libc/vga/*) +LIBC_VGA_A_HDRS = $(filter %.h,$(LIBC_VGA_A_FILES)) +LIBC_VGA_A_SRCS_S = $(filter %.S,$(LIBC_VGA_A_FILES)) +LIBC_VGA_A_SRCS_C = $(filter %.c,$(LIBC_VGA_A_FILES)) + +LIBC_VGA = \ + $(LIBC_VGA_A_DEPS) \ + $(LIBC_VGA_A) + +LIBC_VGA_A_SRCS = \ + $(LIBC_VGA_A_SRCS_S) \ + $(LIBC_VGA_A_SRCS_C) + +LIBC_VGA_A_OBJS = \ + $(LIBC_VGA_A_SRCS_S:%.S=o/$(MODE)/%.o) \ + $(LIBC_VGA_A_SRCS_C:%.c=o/$(MODE)/%.o) + +LIBC_VGA_A_CHECKS = \ + $(LIBC_VGA_A).pkg \ + $(LIBC_VGA_A_HDRS:%=o/$(MODE)/%.ok) + +LIBC_VGA_A_DIRECTDEPS = \ + LIBC_NEXGEN32E \ + LIBC_SYSV \ + LIBC_STR \ + LIBC_INTRIN \ + LIBC_STUBS + +LIBC_VGA_A_DEPS := \ + $(call uniq,$(foreach x,$(LIBC_VGA_A_DIRECTDEPS),$($(x)))) + +$(LIBC_VGA_A):libc/vga/ \ + $(LIBC_VGA_A).pkg \ + $(LIBC_VGA_A_OBJS) + +$(LIBC_VGA_A).pkg: \ + $(LIBC_VGA_A_OBJS) \ + $(foreach x,$(LIBC_VGA_A_DIRECTDEPS),$($(x)_A).pkg) + +LIBC_VGA_LIBS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x))) +LIBC_VGA_SRCS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x)_SRCS)) +LIBC_VGA_HDRS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x)_HDRS)) +LIBC_VGA_BINS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x)_BINS)) +LIBC_VGA_CHECKS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x)_CHECKS)) +LIBC_VGA_OBJS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x)_OBJS)) +LIBC_VGA_TESTS = $(foreach x,$(LIBC_VGA_ARTIFACTS),$($(x)_TESTS)) +$(LIBC_VGA_OBJS): $(BUILD_FILES) libc/vga/vga.mk + +.PHONY: o/$(MODE)/libc/vga +o/$(MODE)/libc/vga: $(LIBC_VGA_CHECKS) diff --git a/libc/vga/writev-vga.c b/libc/vga/writev-vga.c new file mode 100644 index 000000000..5fec0b420 --- /dev/null +++ b/libc/vga/writev-vga.c @@ -0,0 +1,129 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ This is free and unencumbered software released into the public domain. │ +│ │ +│ Anyone is free to copy, modify, publish, use, compile, sell, or │ +│ distribute this software, either in source code form or as a compiled │ +│ binary, for any purpose, commercial or non-commercial, and by any │ +│ means. │ +│ │ +│ In jurisdictions that recognize copyright laws, the author or authors │ +│ of this software dedicate any and all copyright interest in the │ +│ software to the public domain. We make this dedication for the benefit │ +│ of the public at large and to the detriment of our heirs and │ +│ successors. We intend this dedication to be an overt act of │ +│ relinquishment in perpetuity of all present and future rights to this │ +│ software under copyright law. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, │ +│ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF │ +│ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. │ +│ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR │ +│ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │ +│ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR │ +│ OTHER DEALINGS IN THE SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/fd.internal.h" +#include "libc/calls/struct/iovec.h" +#include "libc/calls/struct/iovec.internal.h" +#include "libc/vga/vga.h" +#include "libc/runtime/pc.internal.h" +#include "libc/str/str.h" + +#define CRTPORT 0x3d4 +#define WIDTH 80 + +typedef struct { + char ch; + uint8_t attr; +} char_cell_t; + +typedef char_cell_t char_row_t[WIDTH]; + +const char vga_console[0]; + +static unsigned short height = 25, curr_row = 12, curr_col = 0; +static uint8_t curr_attr = 0x07; + +static void scroll(void) { + unsigned j; + uint8_t attr = curr_attr; + char_row_t * const vid_buf = (char_row_t *)(BANE + 0xb8000ull); + memmove(vid_buf, vid_buf + 1, (height - 1) * sizeof(char_row_t)); + for (j = 0; j < WIDTH; ++j) + vid_buf[height - 1][j] = (char_cell_t){ ' ', attr }; +} + +static void may_scroll(void) { + if (curr_col >= WIDTH) { + curr_col = 0; + scroll(); + } +} + +static void updatexy_vga(void) { + unsigned short pos = curr_row * WIDTH + curr_col; + outb(CRTPORT, 0x0e); + outb(CRTPORT + 1, (unsigned char)(pos >> 8)); + outb(CRTPORT, 0x0f); + outb(CRTPORT + 1, (unsigned char)pos); +} + +static void writec_vga(char c) { + /* TODO: ensure screen in a known mode (text or graphics), at rlinit time */ + /* TODO: use our own font, rather than rely on BIOS's CP437 font */ + /* TODO: handle UTF-8 multi-bytes */ + /* TODO: handle VT102 escape sequences */ + /* TODO: maybe make BEL (\a) character code emit an alarm of some sort */ + char_row_t * const vid_buf = (char_row_t *)(BANE + 0xb8000ull); + uint8_t attr = curr_attr; + unsigned short col; + switch (c) { + case '\b': + if (curr_col) { + col = curr_col - 1; + vid_buf[curr_row][col] = (char_cell_t){ ' ', attr }; + curr_col = col; + } + break; + case '\t': + col = curr_col; + do { + vid_buf[curr_row][col] = (char_cell_t){ ' ', attr }; + ++col; + } while (col % 8 != 0); + curr_col = col; + may_scroll(); + break; + case '\r': + curr_col = 0; + break; + case '\n': + curr_col = 0; + if (curr_row < height - 1) + ++curr_row; + else + scroll(); + break; + default: + col = curr_col; + vid_buf[curr_row][col] = (char_cell_t){ c, attr }; + curr_col = col + 1; + may_scroll(); + } +} + +ssize_t sys_writev_vga(struct Fd *fd, const struct iovec *iov, int iovlen) { + char_row_t * const vid_buf = (char_row_t *)(BANE + 0xb8000ull); + size_t i, j, wrote = 0; + for (i = 0; i < iovlen; ++i) { + const char *input = (const char *)iov[i].iov_base; + for (j = 0; j < iov[i].iov_len; ++j) { + writec_vga(input[j]); + ++wrote; + } + } + updatexy_vga(); + return wrote; +}