Bare metal VGA: support some VT100 escape sequences & UTF-8

The code is based on a pared-down version of the pty code
in tool/build/lib/pty.c.

It currently adds about 12 KiB to the size of the final
executable (compared to the earlier (simplistic) teletype
implementation).

Some terminal features, such as kTtyBell & kTtyNocursor,
are not yet implemented.
This commit is contained in:
tkchia 2022-09-06 15:18:25 +00:00
parent aeacc4239d
commit d4d24482ce
3 changed files with 1358 additions and 89 deletions

1229
libc/vga/tty.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,58 @@
#ifndef COSMOPOLITAN_LIBC_VGA_VGA_INTERNAL_H_ #ifndef COSMOPOLITAN_LIBC_VGA_VGA_INTERNAL_H_
#define 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) #if !(__ASSEMBLER__ + __LINKER__ + 0)
#include "libc/calls/struct/fd.internal.h" #include "libc/calls/struct/fd.internal.h"
#include "libc/calls/struct/iovec.h" #include "libc/calls/struct/iovec.h"
@ -8,8 +60,59 @@
COSMOPOLITAN_C_START_ 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); ssize_t sys_writev_vga(struct Fd *, const struct iovec *, int);
COSMOPOLITAN_C_END_ COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_VGA_VGA_H_ */ #endif /* COSMOPOLITAN_LIBC_VGA_VGA_INTERNAL_H_ */

View file

@ -31,105 +31,42 @@
#include "libc/runtime/pc.internal.h" #include "libc/runtime/pc.internal.h"
#include "libc/str/str.h" #include "libc/str/str.h"
#define CRTPORT 0x3d4 static struct Tty vga_tty;
#define WIDTH 80 #ifdef VGA_USE_WCS
static wchar_t vga_wcs[VGA_TTY_HEIGHT * VGA_TTY_WIDTH];
typedef struct { #else
char ch; static wchar_t * const vga_wcs = NULL;
uint8_t attr; #endif
} char_cell_t;
typedef char_cell_t char_row_t[WIDTH];
static unsigned short height = 25, curr_row, curr_col;
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: 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) { 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, wrote = 0;
size_t i, j, wrote = 0; ssize_t res = 0;
for (i = 0; i < iovlen; ++i) { for (i = 0; i < iovlen; ++i) {
const char *input = (const char *)iov[i].iov_base; void *input = iov[i].iov_base;
for (j = 0; j < iov[i].iov_len; ++j) { size_t len = iov[i].iov_len;
writec_vga(input[j]); res = _TtyWrite(&vga_tty, input, len);
++wrote; if (res < 0)
break;
wrote += res;
if (res != len)
return wrote;
} }
} if (!wrote)
updatexy_vga(); return res;
return wrote; return wrote;
} }
__attribute__((__constructor__)) static textstartup void _vga_init(void) { __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. */ /* Get the initial cursor position from the BIOS data area. */
typedef struct { typedef struct {
unsigned char col, row; unsigned char col, row;
} bios_curs_pos_t; } bios_curs_pos_t;
bios_curs_pos_t pos = *(bios_curs_pos_t *)(BANE + 0x0450ull); bios_curs_pos_t pos = *(bios_curs_pos_t *)(BANE + 0x0450ull);
curr_row = pos.row; /*
curr_col = pos.col; * 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);
} }