From 3fdb1c14f1e245d9ea16d72ce0d247d1cba3659a Mon Sep 17 00:00:00 2001 From: tkchia Date: Wed, 7 Sep 2022 09:41:08 +0800 Subject: [PATCH] Add VGA support on bare metal (#588) If your main module has this declaration: STATIC_YOINK("vga_console"); Then a VGA driver will be linked into your executable which displays your stdio characters on the PC display, whereas before we could only use the serial port. Your display is an ANSI terminal and it's still a work in progress. --- Makefile | 7 +- examples/examples.mk | 1 + examples/hello4.c | 2 + libc/calls/writev-metal.c | 6 + libc/intrin/g_fds.c | 14 +- libc/libc.mk | 1 + libc/vga/tty.c | 1216 +++++++++++++++++++++++++++++++++++++ libc/vga/vga-init.S | 66 ++ libc/vga/vga.internal.h | 118 ++++ libc/vga/vga.mk | 58 ++ libc/vga/writev-vga.c | 72 +++ 11 files changed, 1556 insertions(+), 5 deletions(-) create mode 100644 libc/vga/tty.c create mode 100644 libc/vga/vga-init.S create mode 100644 libc/vga/vga.internal.h create mode 100644 libc/vga/vga.mk create mode 100644 libc/vga/writev-vga.c diff --git a/Makefile b/Makefile index 7f22266a0..9d0a91e16 100644 --- a/Makefile +++ b/Makefile @@ -117,7 +117,8 @@ include third_party/xed/xed.mk # │ include third_party/zlib/zlib.mk # │ include libc/elf/elf.mk # │ include ape/ape.mk # │ -include libc/fmt/fmt.mk #─┘ +include libc/fmt/fmt.mk # │ +include libc/vga/vga.mk #─┘ include libc/calls/calls.mk #─┐ include libc/runtime/runtime.mk # ├──SYSTEMS RUNTIME include libc/crt/crt.mk # │ You can issue system calls @@ -318,7 +319,8 @@ COSMOPOLITAN_OBJECTS = \ LIBC_SYSV \ LIBC_INTRIN \ LIBC_NT_KERNEL32 \ - LIBC_NEXGEN32E + LIBC_NEXGEN32E \ + LIBC_VGA COSMOPOLITAN_HEADERS = \ APE \ @@ -343,6 +345,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/writev-metal.c b/libc/calls/writev-metal.c index c94263782..7bc473d75 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.internal.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/tty.c b/libc/vga/tty.c new file mode 100644 index 000000000..68b3190e8 --- /dev/null +++ b/libc/vga/tty.c @@ -0,0 +1,1216 @@ +/*-*- 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│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/fmt/bing.internal.h" +#include "libc/fmt/itoa.h" +#include "libc/intrin/bits.h" +#include "libc/intrin/safemacros.internal.h" +#include "libc/runtime/pc.internal.h" +#include "libc/str/str.h" +#include "libc/str/thompike.h" +#include "libc/str/tpenc.h" +#include "libc/str/unicode.h" +#include "libc/sysv/errfuns.h" +#include "libc/vga/vga.internal.h" + +/** + * @fileoverview ECMA-48 / VT100 video terminal implementation for bare + * metal VGA. + * + * The code is mainly a pared-down version of the implementation of a + * pseudo-tty in tool/build/lib/pty.c. + * + * @see tool/build/lib/pty.c + */ + +#define DEFAULT_FG 7 +#define DEFAULT_BG 0 +#define CRTPORT 0x3d4 + +static void SetYn(struct Tty *tty, unsigned short yn) { +#ifndef VGA_TTY_HEIGHT + tty->yn = yn; +#endif +} + +static void SetXn(struct Tty *tty, unsigned short xn) { +#ifndef VGA_TTY_WIDTH + tty->xn = xn; +#endif +} + +static wchar_t * SetWcs(struct Tty *tty, wchar_t *wcs) +{ +#ifdef VGA_USE_WCS + tty->wcs = wcs; + return wcs; +#else + return NULL; +#endif +} + +static size_t Yn(struct Tty *tty) +{ +#ifdef VGA_TTY_HEIGHT + return VGA_TTY_HEIGHT; +#else + return tty->yn; +#endif +} + +static size_t Xn(struct Tty *tty) +{ +#ifdef VGA_TTY_WIDTH + return VGA_TTY_WIDTH; +#else + return tty->xn; +#endif +} + +static wchar_t *Wcs(struct Tty *tty) +{ +#ifdef VGA_USE_WCS + return tty->wcs; +#else + return NULL; +#endif +} + +void _StartTty(struct Tty *tty, unsigned short yn, unsigned short xn, + unsigned short starty, unsigned short startx, + void *vccs, wchar_t *wcs) { + struct VgaTextCharCell *ccs = vccs; + memset(tty, 0, sizeof(struct Tty)); + SetYn(tty, yn); + SetXn(tty, xn); + tty->ccs = ccs; + if (starty >= yn) + starty = yn - 1; + if (startx >= xn) + startx = xn - 1; + tty->y = starty; + tty->x = startx; + if (SetWcs(tty, wcs)) { + size_t n = (size_t)yn * xn, i; + for (i = 0; i < n; ++i) + wcs[i] = bing(ccs[i].ch, 0); + } + _TtyResetOutputMode(tty); +} + +static wchar_t *GetXlatAscii(void) { + unsigned i; + static bool once; + static wchar_t xlat[128]; + if (!once) { + for (i = 0; i < 128; ++i) { + xlat[i] = i; + } + once = true; + } + return xlat; +} + +static wchar_t *GetXlatLineDrawing(void) { + unsigned i; + static bool once; + static wchar_t xlat[128]; + if (!once) { + for (i = 0; i < 128; ++i) { + if (0x5F <= i && i <= 0x7E) { + xlat[i] = u" ◆▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥π≠£·"[i - 0x5F]; + } else { + xlat[i] = i; + } + } + once = true; + } + return xlat; +} + +static void XlatAlphabet(wchar_t xlat[128], int a, int b) { + unsigned i; + for (i = 0; i < 128; ++i) { + if ('a' <= i && i <= 'z') { + xlat[i] = i - 'a' + a; + } else if ('A' <= i && i <= 'Z') { + xlat[i] = i - 'A' + b; + } else { + xlat[i] = i; + } + } +} + +static wchar_t *GetXlatItalic(void) { + static bool once; + static wchar_t xlat[128]; + if (!once) { + XlatAlphabet(xlat, L'𝑎', L'𝐴'); + once = true; + } + return xlat; +} + +static wchar_t *GetXlatBoldItalic(void) { + static bool once; + static wchar_t xlat[128]; + if (!once) { + XlatAlphabet(xlat, L'𝒂', L'𝑨'); + once = true; + } + return xlat; +} + +static wchar_t *GetXlatBoldFraktur(void) { + static bool once; + static wchar_t xlat[128]; + if (!once) { + XlatAlphabet(xlat, L'𝖆', L'𝕬'); + once = true; + } + return xlat; +} + +static wchar_t *GetXlatFraktur(void) { + unsigned i; + static bool once; + static wchar_t xlat[128]; + if (!once) { + for (i = 0; i < ARRAYLEN(xlat); ++i) { + if ('A' <= i && i <= 'Z') { + xlat[i] = L"𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ"[i - 'A']; + } else if ('a' <= i && i <= 'z') { + xlat[i] = i - 'a' + L'𝔞'; + } else { + xlat[i] = i; + } + } + once = true; + } + return xlat; +} + +static wchar_t *GetXlatDoubleWidth(void) { + unsigned i; + static bool once; + static wchar_t xlat[128]; + if (!once) { + for (i = 0; i < ARRAYLEN(xlat); ++i) { + if ('!' <= i && i <= '~') { + xlat[i] = -(i - '!' + L'!'); + } else { + xlat[i] = i; + } + } + once = true; + } + return xlat; +} + +static wchar_t *GetXlatSgr(struct Tty *tty) { + switch (!!(tty->pr & kTtyFraktur) << 2 | !!(tty->pr & kTtyItalic) << 1 | + !!(tty->pr & kTtyBold) << 0) { + case 0b100: + case 0b110: + return GetXlatFraktur(); + case 0b101: + case 0b111: + return GetXlatBoldFraktur(); + case 0b011: + return GetXlatBoldItalic(); + case 0b010: + return GetXlatItalic(); + default: + return GetXlatAscii(); + } +} + +static void TtySetXlat(struct Tty *tty, wchar_t *xlat) { + tty->xlat = xlat; + tty->pr &= ~(kTtyItalic | kTtyFraktur); +} + +static void TtySetCodepage(struct Tty *tty, char id) { + unsigned i; + switch (id) { + default: + case 'B': + TtySetXlat(tty, GetXlatAscii()); + break; + case '0': + TtySetXlat(tty, GetXlatLineDrawing()); + break; + } +} + +static uint8_t TtyGetVgaAttr(struct Tty *tty) +{ + uint8_t attr = tty->fg | tty->bg << 4; + if ((tty->pr & kTtyBold) != 0) + attr |= 0x08; + if ((tty->pr & kTtyBlink) != 0) + attr |= 0x80; + return attr; +} + +void _TtyErase(struct Tty *tty, size_t dst, size_t n) { + uint8_t attr = TtyGetVgaAttr(tty); + size_t i; + for (i = 0; i < n; ++i) + tty->ccs[dst + i] = (struct VgaTextCharCell){ ' ', attr }; + if (Wcs(tty)) + wmemset(Wcs(tty) + dst, L' ', n); +} + +void _TtyMemmove(struct Tty *tty, size_t dst, size_t src, size_t n) { + memmove(tty->ccs + dst, tty->ccs + src, n * sizeof(struct VgaTextCharCell)); + if (Wcs(tty)) + wmemmove(Wcs(tty) + dst, Wcs(tty) + src, n); +} + +void _TtyResetOutputMode(struct Tty *tty) { + tty->pr = 0; + tty->fg = DEFAULT_FG; + tty->bg = DEFAULT_BG; + tty->u8 = 0; + tty->n8 = 0; + tty->conf = 0; + tty->savey = 0; + tty->savex = 0; + tty->state = 0; + tty->esc.i = 0; + tty->input.i = 0; + tty->xlat = GetXlatAscii(); +} + +void _TtyFullReset(struct Tty *tty) { + _TtyResetOutputMode(tty); + tty->y = 0; + tty->x = 0; + _TtyErase(tty, 0, Yn(tty) * Xn(tty)); +} + +void _TtySetY(struct Tty *tty, unsigned short y) { + tty->conf &= ~kTtyRedzone; + tty->y = MAX(0, MIN(Yn(tty) - 1, y)); +} + +void _TtySetX(struct Tty *tty, unsigned short x) { + tty->conf &= ~kTtyRedzone; + tty->x = MAX(0, MIN(Xn(tty) - 1, x)); +} + +static void TtyScroll(struct Tty *tty) { + _TtyMemmove(tty, 0, Xn(tty), Xn(tty) * (Yn(tty) - 1)); + _TtyErase(tty, Xn(tty) * (Yn(tty) - 1), Xn(tty)); +} + +static void TtyReverse(struct Tty *tty) { + _TtyMemmove(tty, Xn(tty), 0, Xn(tty) * (Yn(tty) - 1)); + _TtyErase(tty, 0, Xn(tty)); +} + +static void TtyIndex(struct Tty *tty) { + if (tty->y < Yn(tty) - 1) { + ++tty->y; + } else { + TtyScroll(tty); + } +} + +static void TtyReverseIndex(struct Tty *tty) { + if (tty->y) { + --tty->y; + } else { + TtyReverse(tty); + } +} + +static void TtyCarriageReturn(struct Tty *tty) { + _TtySetX(tty, 0); +} + +static void TtyNewline(struct Tty *tty) { + TtyIndex(tty); + if (!(tty->conf & kTtyNoopost)) { + TtyCarriageReturn(tty); + } +} + +static void TtyAdvance(struct Tty *tty) { + tty->conf &= ~kTtyRedzone; + tty->x = 0; + if (tty->y < Yn(tty) - 1) { + ++tty->y; + } else { + TtyScroll(tty); + } +} + +static void TtyWriteGlyph(struct Tty *tty, wint_t wc, int w) { + uint8_t attr = TtyGetVgaAttr(tty); + size_t i; + int c; + if (w < 1) + wc = L' ', w = 1; + if ((tty->conf & kTtyRedzone) || tty->x + w > Xn(tty)) { + TtyAdvance(tty); + } + i = tty->y * Xn(tty) + tty->x; + c = unbing(wc); + if (c == -1) + c = 0xFE; + tty->ccs[i] = (struct VgaTextCharCell){ c, attr }; + if (Wcs(tty)) + Wcs(tty)[i] = wc; + if ((tty->x += w) >= Xn(tty)) { + tty->x = Xn(tty) - 1; + tty->conf |= kTtyRedzone; + } +} + +static void TtyWriteTab(struct Tty *tty) { + uint8_t attr = TtyGetVgaAttr(tty); + unsigned x, x2; + if (tty->conf & kTtyRedzone) { + TtyAdvance(tty); + } + x2 = MIN(Xn(tty), ROUNDUP(tty->x + 1, 8)); + for (x = tty->x; x < x2; ++x) { + tty->ccs[tty->y * Xn(tty) + x] = (struct VgaTextCharCell){ ' ', attr }; + } + if (Wcs(tty)) { + for (x = tty->x; x < x2; ++x) { + Wcs(tty)[tty->y * Xn(tty) + x] = L' '; + } + } + if (x2 < Xn(tty)) { + tty->x = x2; + } else { + tty->x = Xn(tty) - 1; + tty->conf |= kTtyRedzone; + } +} + +int TtyAtoi(const char *s, const char **e) { + int i; + for (i = 0; isdigit(*s); ++s) i *= 10, i += *s - '0'; + if (e) *e = s; + return i; +} + +static int TtyGetMoveParam(struct Tty *tty) { + int x = TtyAtoi(tty->esc.s, NULL); + if (x < 1) x = 1; + return x; +} + +static void TtySetCursorPosition(struct Tty *tty) { + int row, col; + const char *s = tty->esc.s; + row = max(1, TtyAtoi(s, &s)); + if (*s == ';') ++s; + col = max(1, TtyAtoi(s, &s)); + _TtySetY(tty, row - 1); + _TtySetX(tty, col - 1); +} + +static void TtySetCursorRow(struct Tty *tty) { + _TtySetY(tty, TtyGetMoveParam(tty) - 1); +} + +static void TtySetCursorColumn(struct Tty *tty) { + _TtySetX(tty, TtyGetMoveParam(tty) - 1); +} + +static void TtyMoveCursor(struct Tty *tty, int dy, int dx) { + int n = TtyGetMoveParam(tty); + _TtySetY(tty, tty->y + dy * n); + _TtySetX(tty, tty->x + dx * n); +} + +static void TtyScrollUp(struct Tty *tty) { + int n = TtyGetMoveParam(tty); + while (n--) TtyScroll(tty); +} + +static void TtyScrollDown(struct Tty *tty) { + int n = TtyGetMoveParam(tty); + while (n--) TtyReverse(tty); +} + +static void TtySetCursorStatus(struct Tty *tty, bool status) { + if (status) { + tty->conf &= ~kTtyNocursor; + } else { + tty->conf |= kTtyNocursor; + } +} + +static void TtySetMode(struct Tty *tty, bool status) { + const char *p = tty->esc.s; + switch (*p++) { + case '?': + while (isdigit(*p)) { + switch (TtyAtoi(p, &p)) { + case 25: + TtySetCursorStatus(tty, status); + break; + default: + break; + } + if (*p == ';') { + ++p; + } + } + break; + default: + break; + } +} + +static void TtySaveCursorPosition(struct Tty *tty) { + tty->savey = tty->y; + tty->savex = tty->x; +} + +static void TtyRestoreCursorPosition(struct Tty *tty) { + _TtySetY(tty, tty->savey); + _TtySetX(tty, tty->savex); +} + +static void TtyEraseDisplay(struct Tty *tty) { + switch (TtyAtoi(tty->esc.s, NULL)) { + case 0: + _TtyErase(tty, tty->y * Xn(tty) + tty->x, + Yn(tty) * Xn(tty) - (tty->y * Xn(tty) + tty->x)); + break; + case 1: + _TtyErase(tty, 0, tty->y * Xn(tty) + tty->x); + break; + case 2: + case 3: + _TtyErase(tty, 0, Yn(tty) * Xn(tty)); + break; + default: + break; + } +} + +static void TtyEraseLine(struct Tty *tty) { + switch (TtyAtoi(tty->esc.s, NULL)) { + case 0: + _TtyErase(tty, tty->y * Xn(tty) + tty->x, Xn(tty) - tty->x); + break; + case 1: + _TtyErase(tty, tty->y * Xn(tty), tty->x); + break; + case 2: + _TtyErase(tty, tty->y * Xn(tty), Xn(tty)); + break; + default: + break; + } +} + +static void TtyEraseCells(struct Tty *tty) { + int i, n, x; + i = tty->y * Xn(tty) + tty->x; + n = Yn(tty) * Xn(tty); + x = min(max(TtyAtoi(tty->esc.s, NULL), 1), n - i); + _TtyErase(tty, i, x); +} + +static int TtyArg1(struct Tty *tty) { + return max(1, TtyAtoi(tty->esc.s, NULL)); +} + +static void TtyInsertCells(struct Tty *tty) { + int n = min(Xn(tty) - tty->x, TtyArg1(tty)); + _TtyMemmove(tty, tty->y * Xn(tty) + tty->x + n, tty->y * Xn(tty) + tty->x, + Xn(tty) - (tty->x + n)); + _TtyErase(tty, tty->y * Xn(tty) + tty->x, n); +} + +static void TtyInsertLines(struct Tty *tty) { + int n = min(Yn(tty) - tty->y, TtyArg1(tty)); + _TtyMemmove(tty, (tty->y + n) * Xn(tty), tty->y * Xn(tty), + (Yn(tty) - tty->y - n) * Xn(tty)); + _TtyErase(tty, tty->y * Xn(tty), n * Xn(tty)); +} + +static void TtyDeleteCells(struct Tty *tty) { + int n = min(Xn(tty) - tty->x, TtyArg1(tty)); + _TtyMemmove(tty, tty->y * Xn(tty) + tty->x, tty->y * Xn(tty) + tty->x + n, + Xn(tty) - (tty->x + n)); + _TtyErase(tty, tty->y * Xn(tty) + tty->x, n); +} + +static void TtyDeleteLines(struct Tty *tty) { + int n = min(Yn(tty) - tty->y, TtyArg1(tty)); + _TtyMemmove(tty, tty->y * Xn(tty), (tty->y + n) * Xn(tty), + (Yn(tty) - tty->y - n) * Xn(tty)); + _TtyErase(tty, (tty->y + n) * Xn(tty), n * Xn(tty)); +} + +static void TtyReportDeviceStatus(struct Tty *tty) { + _TtyWriteInput(tty, "\e[0n", 4); +} + +static void TtyReportPreferredVtType(struct Tty *tty) { + _TtyWriteInput(tty, "\e[?1;0c", 4); +} + +static void TtyReportPreferredVtIdentity(struct Tty *tty) { + _TtyWriteInput(tty, "\e/Z", 4); +} + +static void TtyBell(struct Tty *tty) { + tty->conf |= kTtyBell; +} + +static void TtyLed(struct Tty *tty) { + switch (TtyAtoi(tty->esc.s, NULL)) { + case 0: + tty->conf &= ~kTtyLed1; + tty->conf &= ~kTtyLed2; + tty->conf &= ~kTtyLed3; + tty->conf &= ~kTtyLed4; + break; + case 1: + tty->conf |= kTtyLed1; + break; + case 2: + tty->conf |= kTtyLed2; + break; + case 3: + tty->conf |= kTtyLed3; + break; + case 4: + tty->conf |= kTtyLed4; + break; + default: + break; + } +} + +static void TtyReportCursorPosition(struct Tty *tty) { + char *p; + char buf[2 + 10 + 1 + 10 + 1]; + p = buf; + *p++ = '\e'; + *p++ = '['; + p = FormatInt32(p, (tty->y + 1) & 0x7fff); + *p++ = ';'; + p = FormatInt32(p, (tty->x + 1) & 0x7fff); + *p++ = 'R'; + _TtyWriteInput(tty, buf, p - buf); +} + +static void TtyCsiN(struct Tty *tty) { + switch (TtyAtoi(tty->esc.s, NULL)) { + case 5: + TtyReportDeviceStatus(tty); + break; + case 6: + TtyReportCursorPosition(tty); + break; + default: + break; + } +} + +/** + * Map the given (R, G, B) triplet to one of the 16 basic foreground colors + * or one of the 8 background colors. + * + * @see drivers/tty/vt/vt.c in Linux 5.9.14 source code + */ +static uint8_t TtyMapTrueColor(uint8_t r, uint8_t g, uint8_t b, bool as_fg) +{ + uint8_t hue = 0; + if (as_fg) { + uint8_t max = MAX(MAX(r, g), b); + if (r > max / 2) + hue |= 4; + if (g > max / 2) + hue |= 2; + if (b > max / 2) + hue |= 1; + if (hue == 7 && max <= 0x55) + hue = 8; + else if (max > 0xaa) + hue |= 8; + } else { + if (r >= 128) + hue |= 4; + if (g >= 128) + hue |= 2; + if (b >= 128) + hue |= 1; + } + return hue; +} + +/** + * Map the given 256-color code one of the 16 basic foreground colors or one + * of the 8 background colors. + * + * @see drivers/tty/vt/vt.c in Linux 5.9.14 source code + */ +static uint8_t TtyMapXtermColor(uint8_t color, bool as_fg) +{ + uint8_t r, g, b; + if (color < 8) { + r = (color & 1) ? 0xaa : 0; + g = (color & 2) ? 0xaa : 0; + b = (color & 4) ? 0xaa : 0; + } else if (color < 16) { + r = (color & 1) ? 0xff : 0x55; + g = (color & 2) ? 0xff : 0x55; + b = (color & 4) ? 0xff : 0x55; + } else if (color < 232) { + color -= 16; + b = color % 6 * 0x55 / 2; + color /= 6; + g = color % 6 * 0x55 / 2; + color /= 6; + r = color * 0x55 / 2; + } else + r = g = b = (unsigned)color * 10 - 2312; + return TtyMapTrueColor(r, g, b, as_fg); +} + +static void TtySelectGraphicsRendition(struct Tty *tty) { + char *p, c; + unsigned x; + uint8_t code[4]; + enum { + kSgr, + kSgrFg = 010, + kSgrFgTrue = 012, + kSgrFgXterm = 015, + kSgrBg = 020, + kSgrBgTrue = 022, + kSgrBgXterm = 025, + } t; + x = 0; + t = kSgr; + p = tty->esc.s; + bzero(code, sizeof(code)); + for (;;) { + c = *p++; + switch (c) { + case '\0': + return; + case '0' ... '9': + x *= 10; + x += c - '0'; + break; + case ';': + case 'm': + code[code[3]] = x; + x = 0; + switch (t) { + case kSgr: + switch (code[0]) { + case 38: + t = kSgrFg; + break; + case 48: + t = kSgrBg; + break; + case 0: + tty->pr = 0; + tty->fg = DEFAULT_FG; + tty->bg = DEFAULT_BG; + tty->xlat = GetXlatSgr(tty); + break; + case 1: + tty->pr |= kTtyBold; + tty->xlat = GetXlatSgr(tty); + break; + case 2: + tty->pr |= kTtyFaint; + break; + case 3: + tty->pr |= kTtyItalic; + tty->xlat = GetXlatSgr(tty); + break; + case 4: + tty->pr |= kTtyUnder; + break; + case 5: + tty->pr |= kTtyBlink; + break; + case 7: + tty->pr |= kTtyFlip; + break; + case 8: + tty->pr |= kTtyConceal; + break; + case 9: + tty->pr |= kTtyStrike; + break; + case 20: + tty->pr |= kTtyFraktur; + tty->xlat = GetXlatSgr(tty); + break; + case 21: + tty->pr |= kTtyUnder | kTtyDunder; + break; + case 22: + tty->pr &= ~(kTtyFaint | kTtyBold); + tty->xlat = GetXlatSgr(tty); + break; + case 23: + tty->pr &= ~kTtyItalic; + tty->xlat = GetXlatSgr(tty); + break; + case 24: + tty->pr &= ~(kTtyUnder | kTtyDunder); + break; + case 25: + tty->pr &= ~kTtyBlink; + break; + case 27: + tty->pr &= ~kTtyFlip; + break; + case 28: + tty->pr &= ~kTtyConceal; + break; + case 29: + tty->pr &= ~kTtyStrike; + break; + case 39: + tty->fg = DEFAULT_FG; + tty->pr &= ~kTtyFg; + break; + case 49: + tty->bg = DEFAULT_BG; + tty->pr &= ~kTtyBg; + break; + case 90 ... 97: + code[0] -= 90 - 30; + code[0] += 8; + /* fallthrough */ + case 30 ... 37: + tty->fg = code[0] - 30; + tty->pr |= kTtyFg; + tty->pr &= ~kTtyTrue; + break; + case 100 ... 107: + code[0] -= 100 - 40; + /* fallthrough */ + case 40 ... 47: + tty->bg = code[0] - 40; + tty->pr |= kTtyBg; + tty->pr &= ~kTtyTrue; + break; + default: + break; + } + break; + case kSgrFg: + case kSgrBg: + switch (code[0]) { + case 2: + case 5: + t += code[0]; + break; + default: + t = kSgr; + break; + } + break; + case kSgrFgTrue: + if (++code[3] == 3) { + code[3] = 0; + t = kSgr; + tty->fg = TtyMapTrueColor(code[0], code[1], code[2], true); + tty->pr |= kTtyFg; + tty->pr |= kTtyTrue; + } + break; + case kSgrBgTrue: + if (++code[3] == 3) { + code[3] = 0; + t = kSgr; + tty->bg = TtyMapTrueColor(code[0], code[1], code[2], false); + tty->pr |= kTtyBg; + tty->pr |= kTtyTrue; + } + break; + case kSgrFgXterm: + t = kSgr; + tty->fg = TtyMapXtermColor(code[0], true); + tty->pr |= kTtyFg; + tty->pr &= ~kTtyTrue; + break; + case kSgrBgXterm: + t = kSgr; + tty->bg = TtyMapXtermColor(code[0], false); + tty->pr |= kTtyBg; + tty->pr &= ~kTtyTrue; + break; + default: + abort(); + } + break; + default: + break; + } + } +} + +static void TtyCsi(struct Tty *tty) { + switch (tty->esc.s[tty->esc.i - 1]) { + case 'f': + case 'H': + TtySetCursorPosition(tty); + break; + case 'G': + TtySetCursorColumn(tty); + break; + case 'd': + TtySetCursorRow(tty); + break; + case 'F': + tty->x = 0; + /* fallthrough */ + case 'A': + TtyMoveCursor(tty, -1, +0); + break; + case 'E': + tty->x = 0; + /* fallthrough */ + case 'B': + TtyMoveCursor(tty, +1, +0); + break; + case 'C': + TtyMoveCursor(tty, +0, +1); + break; + case 'D': + TtyMoveCursor(tty, +0, -1); + break; + case 'S': + TtyScrollUp(tty); + break; + case 'T': + TtyScrollDown(tty); + break; + case '@': + TtyInsertCells(tty); + break; + case 'P': + TtyDeleteCells(tty); + break; + case 'L': + TtyInsertLines(tty); + break; + case 'M': + TtyDeleteLines(tty); + break; + case 'J': + TtyEraseDisplay(tty); + break; + case 'K': + TtyEraseLine(tty); + break; + case 'X': + TtyEraseCells(tty); + break; + case 's': + TtySaveCursorPosition(tty); + break; + case 'u': + TtyRestoreCursorPosition(tty); + break; + case 'n': + TtyCsiN(tty); + break; + case 'm': + TtySelectGraphicsRendition(tty); + break; + case 'h': + TtySetMode(tty, true); + break; + case 'l': + TtySetMode(tty, false); + break; + case 'c': + TtyReportPreferredVtType(tty); + break; + case 'q': + TtyLed(tty); + break; + default: + break; + } +} + +static void TtyScreenAlignmentDisplay(struct Tty *tty) { + uint8_t attr = TtyGetVgaAttr(tty); + size_t n = Yn(tty) * Xn(tty), i; + for (i = 0; i < n; ++i) + tty->ccs[i] = (struct VgaTextCharCell){ 'E', attr }; + if (Wcs(tty)) + wmemset(Wcs(tty), L'E', n); +} + +static void TtyEscHash(struct Tty *tty) { + switch (tty->esc.s[1]) { + case '5': + TtySetXlat(tty, GetXlatAscii()); + break; + case '6': + TtySetXlat(tty, GetXlatDoubleWidth()); + break; + case '8': + TtyScreenAlignmentDisplay(tty); + break; + default: + break; + } +} + +static void TtyEsc(struct Tty *tty) { + switch (tty->esc.s[0]) { + case 'c': + _TtyFullReset(tty); + break; + case '7': + TtySaveCursorPosition(tty); + break; + case '8': + TtyRestoreCursorPosition(tty); + break; + case 'E': + tty->x = 0; + case 'D': + TtyIndex(tty); + break; + case 'M': + TtyReverseIndex(tty); + break; + case 'Z': + TtyReportPreferredVtIdentity(tty); + break; + case '(': + TtySetCodepage(tty, tty->esc.s[1]); + break; + case '#': + TtyEscHash(tty); + break; + default: + break; + } +} + +static void TtyCntrl(struct Tty *tty, int c01) { + switch (c01) { + case '\a': + TtyBell(tty); + break; + case 0x85: + case '\f': + case '\v': + case '\n': + TtyNewline(tty); + break; + case '\r': + TtyCarriageReturn(tty); + break; + case '\e': + tty->state = kTtyEsc; + tty->esc.i = 0; + break; + case '\t': + TtyWriteTab(tty); + break; + case 0x7F: + case '\b': + tty->x = MAX(0, tty->x - 1); + break; + case 0x84: + TtyIndex(tty); + break; + case 0x8D: + TtyReverseIndex(tty); + break; + case 0x9B: + tty->state = kTtyCsi; + break; + default: + break; + } +} + +static void TtyEscAppend(struct Tty *tty, char c) { + tty->esc.i = MIN(tty->esc.i + 1, ARRAYLEN(tty->esc.s) - 1); + tty->esc.s[tty->esc.i - 1] = c; + tty->esc.s[tty->esc.i - 0] = 0; +} + +static void TtyUpdateHwCursor(struct Tty *tty) { + unsigned short pos = tty->y * Xn(tty) + tty->x; + outb(CRTPORT, 0x0e); + outb(CRTPORT + 1, (unsigned char)(pos >> 8)); + outb(CRTPORT, 0x0f); + outb(CRTPORT + 1, (unsigned char)pos); +} + +ssize_t _TtyWrite(struct Tty *tty, const void *data, size_t n) { + int i; + wchar_t wc; + const uint8_t *p; + for (p = data, i = 0; i < n; ++i) { + switch (tty->state) { + case kTtyAscii: + if (0x00 <= p[i] && p[i] <= 0x7F) { + if (0x20 <= p[i] && p[i] <= 0x7E) { + if ((wc = tty->xlat[p[i]]) >= 0) { + TtyWriteGlyph(tty, wc, 1); + } else { + TtyWriteGlyph(tty, -wc, 2); + } + } else { + TtyCntrl(tty, p[i]); + } + } else if (!ThomPikeCont(p[i])) { + tty->state = kTtyUtf8; + tty->u8 = ThomPikeByte(p[i]); + tty->n8 = ThomPikeLen(p[i]) - 1; + } + break; + case kTtyUtf8: + if (ThomPikeCont(p[i])) { + tty->u8 = ThomPikeMerge(tty->u8, p[i]); + if (--tty->n8) break; + } + wc = tty->u8; + if ((0x00 <= wc && wc <= 0x1F) || (0x7F <= wc && wc <= 0x9F)) { + TtyCntrl(tty, wc); + } else { + TtyWriteGlyph(tty, wc, wcwidth(wc)); + } + tty->state = kTtyAscii; + tty->u8 = 0; + --i; + break; + case kTtyEsc: + if (p[i] == '[') { + tty->state = kTtyCsi; + } else if (0x30 <= p[i] && p[i] <= 0x7E) { + TtyEscAppend(tty, p[i]); + TtyEsc(tty); + tty->state = kTtyAscii; + } else if (0x20 <= p[i] && p[i] <= 0x2F) { + TtyEscAppend(tty, p[i]); + } else { + tty->state = kTtyAscii; + } + break; + case kTtyCsi: + TtyEscAppend(tty, p[i]); + switch (p[i]) { + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '0' ... '9': + break; + case '`': + case '~': + case '^': + case '@': + case '[': + case ']': + case '{': + case '}': + case '_': + case '|': + case '\\': + case 'A' ... 'Z': + case 'a' ... 'z': + TtyCsi(tty); + tty->state = kTtyAscii; + break; + default: + tty->state = kTtyAscii; + continue; + } + break; + default: + unreachable; + } + } + TtyUpdateHwCursor(tty); + return n; +} + +ssize_t _TtyWriteInput(struct Tty *tty, const void *data, size_t n) { + int c; + bool cr; + char *p; + const char *q; + size_t i, j, m; + q = data; + p = tty->input.p; + i = tty->input.i; + cr = i && p[i - 1] == '\r'; + for (j = 0; j < n && i < sizeof tty->input.p; ++j) { + c = q[j] & 255; + if (c == '\r') { + cr = true; + } else if (cr) { + if (c != '\n') { + p[i++] = '\n'; + } + cr = false; + } + p[i++] = c; + } + if (cr && i < sizeof tty->input.p) { + p[i++] = '\n'; + } + if (!(tty->conf & kTtyNoecho)) { + _TtyWrite(tty, p + tty->input.i, i - tty->input.i); + } + tty->input.i = i; + return n; +} + +ssize_t _TtyRead(struct Tty *tty, void *buf, size_t size) { + char *p; + size_t n; + n = MIN(size, tty->input.i); + if (!(tty->conf & kTtyNocanon)) { + if ((p = memchr(tty->input.p, '\n', n))) { + n = MIN(n, tty->input.p - p + 1); + } else { + n = 0; + } + } + memcpy(buf, tty->input.p, n); + memcpy(tty->input.p, tty->input.p + n, tty->input.i - n); + tty->input.i -= n; + return n; +} diff --git a/libc/vga/vga-init.S b/libc/vga/vga-init.S new file mode 100644 index 000000000..db00bb7c7 --- /dev/null +++ b/libc/vga/vga-init.S @@ -0,0 +1,66 @@ +/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=asm ts=8 tw=8 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/macros.internal.h" + +// Code snippet for initializing the VGA video mode for bare metal. +// +// If a program requests VGA support (by yoinking vga_console), +// and it is started in bare metal mode, then try to ensure that +// the VGA monitor is in a known mode. This is easier to do while +// the program is still running in real mode. +// +// This module also ropes in the sys_writev_vga routine, which +// implements the actual VGA console output under x86-64 long mode. +// +// @see rlinit & .sort.text.real.init.* (ape/ape.S) +// @see ape/ape.lds +// @see sys_writev_vga (libc/vga/writev-vga.c) + .section .sort.text.real.init.2,"ax",@progbits + .code16 + mov $0x4f03,%ax # get current video mode via VESA + int $0x10 + cmp $0x004f,%ax # is VESA a thing here? + jz 1f + mov $0x0f,%ah # if not, get the video mode via a + int $0x10 # classical BIOS call + cbtw + xchgw %ax,%bx +1: mov $0x0003,%ax # check if we are in a 80 × ? × 16 + cmp %ax,%bx # text mode + jnz 2f + cmpb $25-1,0x0484 # check if number of screen rows + jnz 2f # (BDA.ROWS + 1) is 25; if so, then + mov $0x0500,%ax # just make sure we are on display + # page 0 +2: int $0x10 # otherwise, change the video mode + .previous + .code64 + .section .rodata,"a",@progbits +vga_console: + .endobj vga_console,globl,hidden + .previous + .yoink sys_writev_vga diff --git a/libc/vga/vga.internal.h b/libc/vga/vga.internal.h new file mode 100644 index 000000000..39cf3ff47 --- /dev/null +++ b/libc/vga/vga.internal.h @@ -0,0 +1,118 @@ +#ifndef COSMOPOLITAN_LIBC_VGA_VGA_INTERNAL_H_ +#define COSMOPOLITAN_LIBC_VGA_VGA_INTERNAL_H_ + +/* + * VGA_TTY_HEIGHT, VGA_TTY_WIDTH, & VGA_USE_WCS are configuration knobs + * which can potentially be used to tweak the features to be compiled into + * our VGA teletypewriter support. + */ + +/** + * Height of the video screen, in character units. Undefine if the height + * may vary at runtime. + */ +#define VGA_TTY_HEIGHT 25 +/** + * Width of the video screen, in character units. Undefine if the width may + * vary at runtime. + */ +#define VGA_TTY_WIDTH 80 +/** + * If VGA_USE_WCS is defined, the tty code can maintain an array of the + * Unicode characters "underlying" the 8-bit (or 9-bit) characters that are + * actually displayed on the text screen. This can be used to implement + * something similar to Linux's /dev/vcsu* facility. + * + * @see lkml.kernel.org/lkml/204888.1529277815@turing-police.cc.vt.edu/T/ + */ +#undef VGA_USE_WCS + +#define kTtyFg 0x0001 +#define kTtyBg 0x0002 +#define kTtyBold 0x0004 +#define kTtyFlip 0x0008 +#define kTtyFaint 0x0010 +#define kTtyUnder 0x0020 +#define kTtyDunder 0x0040 +#define kTtyTrue 0x0080 +#define kTtyBlink 0x0100 +#define kTtyItalic 0x0200 +#define kTtyFraktur 0x0400 +#define kTtyStrike 0x0800 +#define kTtyConceal 0x1000 + +#define kTtyBell 0x001 +#define kTtyRedzone 0x002 +#define kTtyNocursor 0x004 +#define kTtyBlinkcursor 0x008 +#define kTtyNocanon 0x010 +#define kTtyNoecho 0x020 +#define kTtyNoopost 0x040 +#define kTtyLed1 0x080 +#define kTtyLed2 0x100 +#define kTtyLed3 0x200 +#define kTtyLed4 0x400 + +#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_ + +struct VgaTextCharCell { + uint8_t ch, attr; +}; + +struct Tty { + unsigned short y, x; +#ifndef VGA_TTY_HEIGHT + unsigned short yn; +#endif +#ifndef VGA_TTY_WIDTH + unsigned short xn; +#endif + uint32_t u8; + uint32_t n8; + uint32_t pr; + uint8_t fg, bg; + uint32_t conf; + unsigned short savey, savex; + struct VgaTextCharCell *ccs; +#ifdef VGA_USE_WCS + wchar_t *wcs; +#endif + wchar_t *xlat; + enum TtyState { + kTtyAscii, + kTtyUtf8, + kTtyEsc, + kTtyCsi, + } state; + struct TtyEsc { + unsigned i; + char s[64]; + } esc; + struct TtyInput { + size_t i; + char p[256]; + } input; +}; + +void _StartTty(struct Tty *, unsigned short, unsigned short, + unsigned short, unsigned short, void *, wchar_t *); +ssize_t _TtyRead(struct Tty *, void *, size_t); +ssize_t _TtyWrite(struct Tty *, const void *, size_t); +ssize_t _TtyWriteInput(struct Tty *, const void *, size_t); +void _TtyResetOutputMode(struct Tty *); +void _TtyFullReset(struct Tty *); +void _TtyMemmove(struct Tty *, size_t, size_t, size_t); +void _TtyErase(struct Tty *, size_t, size_t); +void _TtySetY(struct Tty *, unsigned short); +void _TtySetX(struct Tty *, unsigned short); + +ssize_t sys_writev_vga(struct Fd *, const struct iovec *, int); + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_VGA_VGA_INTERNAL_H_ */ diff --git a/libc/vga/vga.mk b/libc/vga/vga.mk new file mode 100644 index 000000000..0fabf0284 --- /dev/null +++ b/libc/vga/vga.mk @@ -0,0 +1,58 @@ +#-*-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_FMT + +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..854596441 --- /dev/null +++ b/libc/vga/writev-vga.c @@ -0,0 +1,72 @@ +/*-*- 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.internal.h" +#include "libc/runtime/pc.internal.h" +#include "libc/str/str.h" + +static struct Tty vga_tty; +#ifdef VGA_USE_WCS +static wchar_t vga_wcs[VGA_TTY_HEIGHT * VGA_TTY_WIDTH]; +#else +static wchar_t * const vga_wcs = NULL; +#endif + +ssize_t sys_writev_vga(struct Fd *fd, const struct iovec *iov, int iovlen) { + size_t i, wrote = 0; + ssize_t res = 0; + for (i = 0; i < iovlen; ++i) { + void *input = iov[i].iov_base; + size_t len = iov[i].iov_len; + res = _TtyWrite(&vga_tty, input, len); + if (res < 0) + break; + wrote += res; + if (res != len) + return wrote; + } + if (!wrote) + return res; + return wrote; +} + +__attribute__((__constructor__)) static textstartup void _vga_init(void) { + void * const vid_buf = (void *)(BANE + 0xb8000ull); + /* Get the initial cursor position from the BIOS data area. */ + typedef struct { + unsigned char col, row; + } bios_curs_pos_t; + bios_curs_pos_t pos = *(bios_curs_pos_t *)(BANE + 0x0450ull); + /* + * Initialize our tty structure from the current screen contents & current + * cursor position. + */ + _StartTty(&vga_tty, VGA_TTY_HEIGHT, VGA_TTY_WIDTH, pos.row, pos.col, + vid_buf, vga_wcs); +}