Start exploring graphical video modes for VGA console (#637)

This commit is contained in:
tkchia 2022-10-02 16:50:16 +08:00 committed by GitHub
parent 304cf8869c
commit ecb2ef7c39
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 964 additions and 70 deletions

View file

@ -112,19 +112,34 @@ static noasan textreal void __normalize_e820(struct mman *mm) {
mm->e820n = n;
}
/**
* Identity maps an area of physical memory to its negative address.
*/
noasan textreal void __invert_memory_area(struct mman *mm, uint64_t *pml4t,
uint64_t ps, uint64_t size,
uint64_t pte_flags) {
uint64_t pe = ps + size, p, *m;
ps = ROUNDDOWN(ps, 4096);
pe = ROUNDUP(pe, 4096);
for (p = ps; p != pe; p += 4096) {
m = __get_virtual(mm, pml4t, BANE + p, true);
if (m && !(*m & PAGE_V)) {
*m = p | PAGE_V | pte_flags;
}
}
}
/**
* Identity maps all usable physical memory to its negative address.
*/
static noasan textreal void __invert_memory(struct mman *mm, uint64_t *pml4t) {
uint64_t i, j, *m, p, pe;
for (i = 0; i < mm->e820n; ++i) {
for (p = mm->e820[i].addr, pe = mm->e820[i].addr + mm->e820[i].size;
p != pe + 0x200000; p += 4096) {
m = __get_virtual(mm, pml4t, BANE + p, true);
if (m && !(*m & PAGE_V)) {
*m = p | PAGE_V | PAGE_RW;
}
}
uint64_t ps = mm->e820[i].addr, size = mm->e820[i].size;
/* ape/ape.S has already mapped the first 2 MiB of physical memory. */
if (ps < 0x200000 && ps + size <= 0x200000)
continue;
__invert_memory_area(mm, pml4t, ps, size, PAGE_RW);
}
}
@ -148,6 +163,12 @@ noasan textreal void __setup_mman(struct mman *mm, uint64_t *pml4t) {
export_offsetof(struct mman, e820);
export_offsetof(struct mman, e820_end);
export_offsetof(struct mman, bad_idt);
export_offsetof(struct mman, pc_video_type);
export_offsetof(struct mman, pc_video_stride);
export_offsetof(struct mman, pc_video_width);
export_offsetof(struct mman, pc_video_height);
export_offsetof(struct mman, pc_video_framebuffer);
export_offsetof(struct mman, pc_video_framebuffer_size);
__normalize_e820(mm);
__invert_memory(mm, pml4t);
}

View file

@ -18,8 +18,27 @@ struct mman {
unsigned char pc_drive_last_head; /* 0x1d21 */
unsigned char pc_drive; /* 0x1d22 */
char bad_idt[6]; /* 0x1d23 */
unsigned char pc_video_type; /* 0x1d29 */
unsigned short pc_video_stride; /* 0x1d2a — line width, including any
invisible "pixels" in
bytes (NOTE) */
unsigned short pc_video_width; /* 0x1d2c — width in chars. (text)
or pixels (graphics) */
unsigned short pc_video_height; /* 0x1d2e — height in chars. (text)
or pixels (graphics) */
uint64_t pc_video_framebuffer; /* 0x1d30 — physical address of
video frame buffer */
uint64_t pc_video_framebuffer_size; /* 0x1d38 */
};
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
/* Values for mman::pc_video_type. TODO: implement graphics modes. */
#define PC_VIDEO_TEXT 0
#define PC_VIDEO_BGR565 1
#define PC_VIDEO_BGR555 2
#define PC_VIDEO_BGRX8888 3
#define PC_VIDEO_RGBX8888 4
#endif /* COSMOPOLITAN_LIBC_RUNTIME_MMAN_H_ */

View file

@ -186,6 +186,8 @@ struct IdtDescriptor {
uint64_t *__get_virtual(struct mman *, uint64_t *, int64_t, bool);
uint64_t __clear_page(uint64_t);
uint64_t __new_page(struct mman *);
void __invert_memory_area(struct mman *, uint64_t *, uint64_t, uint64_t,
uint64_t);
void __map_phdrs(struct mman *, uint64_t *, uint64_t);
forceinline unsigned char inb(unsigned short port) {

611
libc/vga/rlinit-vesa.S Normal file
View file

@ -0,0 +1,611 @@
/*-*- 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 "ape/relocations.h"
#include "libc/macros.internal.h"
#include "libc/runtime/mman.internal.h"
#include "libc/vga/vga.internal.h"
#define VGA_PREFER_GRAPH_HEIGHT \
(VGA_PREFER_TTY_HEIGHT * VGA_ASSUME_CHAR_HEIGHT_PX)
#define VGA_PREFER_GRAPH_WIDTH \
(VGA_PREFER_TTY_WIDTH * VGA_ASSUME_CHAR_WIDTH_PX)
#define MAX_VESA_MODES_TO_TRACK 64
// Mode information data structure, used internally.
.set "mi::type",0
.set "mi::bpp",1
.set "mi::width",2
.set "mi::height",4
.set "mi::mode_num",6
.set "mi::stride",8
.set "mi::fb",10
.set "mi::sizeof",14
// Routine to activate additional VESA functionality if the user holds
// down a magic key, when booting from bare metal. Currently this just
// dumps a list of all usable VESA VBE video modes on the console.
//
// TODO: allow user to select a video mode and switch to it
// TODO: implement character drawing in graphics modes
//
// @return CF = 0 if we decided to set a new video mode, CF = 1 otherwise
.real
.code16
_rlinit_vesa:
push %es
pushw $0
testb $0b00000011,0x0417 # read keyboard shift state (as
jnz .doit # given by BIOS's IRQ 1); if Shift
# key pressed, activate code below
mov $0x8300,%ax # wait for the magic key for a short
mov $(250000>>16),%cx # period of time...
mov $(250000&0xffff),%dx
push %ss
pop %es
mov %sp,%bx
int $0x15
jc .done
.wait: pause
testb $0b00000011,0x0417
jnz .doit2
cmpb $0,%es:(%bx)
jz .wait
stc
.done: pop %ax
pop %es
ret
.doit2: mov $0x8301,%ax # we got the magic key; cancel the
int $0x15 # wait timer
.doit: pop %ax # we got the magic key; do stuff
pop %es
// fall through
.endfn _rlinit_vesa,globl,hidden
.do_vesa_rlinit:
push %eax
push %bx
push %edx
push %bp
sub $2+MAX_VESA_MODES_TO_TRACK*"mi::sizeof",%sp
mov %sp,%bp
call .gather_vesa_modes # gather list of VESA modes
jc 8f
call .get_default_mode # choose a default mode to use
jc 6f
call .choose_mode # allow user to select different mode
movw %bx,%bp
call .snooze
mov "mi::mode_num"(%bp),%bx
mov $0x4f02,%ax
int $0x10
cmp $0x004f,%ax
jnz 9f
mov "mi::type"(%bp),%al
.set mm,0x0500
mov %al,mm+"struct mman::pc_video_type"
mov "mi::width"(%bp),%ax
mov %ax,mm+"struct mman::pc_video_width"
movzwl "mi::height"(%bp),%edx
mov %dx,mm+"struct mman::pc_video_height"
mov "mi::fb"(%bp),%eax
mov %eax,mm+"struct mman::pc_video_framebuffer"
movzwl "mi::stride"(%bp),%eax
mov %ax,mm+"struct mman::pc_video_stride"
imul %edx,%eax
mov %eax,mm+"struct mman::pc_video_framebuffer_size"
xor %eax,%eax
mov %eax,mm+"struct mman::pc_video_framebuffer"+4
mov %eax,mm+"struct mman::pc_video_framebuffer_size"+4
clc
5: lahf
add $2+MAX_VESA_MODES_TO_TRACK*"mi::sizeof",%sp
sahf
pop %bp
pop %edx
pop %bx
pop %eax
ret
6: mov $REAL(str.no_mode),%si
7: call .puts
8: call .snooze
stc
jmp 5b
9: mov $REAL(str.bad_mode),%si
jmp 7b
.endfn .do_vesa_rlinit
// Preliminarily choose a "default" VESA screen mode from a list of
// gathered screen modes.
//
// @return %bx is such that %ss:(%bx) = internal struc. for default mode
// @return CF = 1 if no available modes, CF = 0 otherwise
.get_default_mode:
push %eax
push %cx
push %edx
push %edi
push %bp
mov (%bp),%cx
jcxz 8f
inc %bp
inc %bp
or $-1,%esi # %esi = best fit screen size + penalty
# (to be recalculated later)
mov %bp,%bx # %bx = pointer to best info. struct.
1: mov "mi::type"(%bp),%ax # load mode type & bits per pixel
ror %ah # convert bpp into penalty value:
movzbl %ah,%edi # fewer bpp are better, but prefer
# 16 bpp over 15 bpp
cmp $PC_VIDEO_TEXT,%al # handle text modes specially
jz 9f
movzwl "mi::width"(%bp),%eax
jz 9f
cmp $VGA_PREFER_GRAPH_WIDTH,%ax # calculate screen size
jb 3f
movzwl "mi::height"(%bp),%edx
cmp $VGA_PREFER_GRAPH_HEIGHT,%dx
jb 3f
imul %edx,%eax
2: add %edi,%eax
jc 3f
cmp %esi,%eax
jnb 3f
mov %eax,%esi
mov %bp,%bx
3: add $"mi::sizeof",%bp
loop 1b
clc
7: pop %bp
pop %edi
pop %edx
pop %cx
pop %eax
ret
8: stc
jmp 7b
9: imul $VGA_ASSUME_CHAR_WIDTH_PX,%eax
cmp $VGA_PREFER_GRAPH_WIDTH,%eax
jb 2b
movzwl "mi::height"(%bp),%edx
imul $VGA_ASSUME_CHAR_HEIGHT_PX,%edx
cmp $VGA_PREFER_GRAPH_HEIGHT,%edx
jb 3b
imul %edx,%eax
jo 3b
mov $1<<31,%edi # severely disparage text modes
jmp 2b
.endfn .get_default_mode
// Allow the user to choose a VESA screen mode to use.
//
// @return %bx is such that ss:(%bx) = internal struc. for chosen mode
.choose_mode:
push %ax
push %si
push %di
mov (%bp),%di
imul $"mi::sizeof",%di
lea 2(%bp,%di),%di
1: mov $REAL(str.choose_mode),%si
call .puts
mov %ss:("mi::mode_num")(%bx),%ax
call .putx
mov %ss:("mi::width")(%bx),%ax
call .putd
mov %ss:("mi::height")(%bx),%ax
call .putd
mov %ss:("mi::bpp")(%bx),%al
call .putdb
mov $0,%ah
int $0x16
#define UPSCN 0x48
#define DNSCN 0x50
#define CRSCN 0x1c
cmp $DNSCN,%ah
jz 3f
cmp $CRSCN,%ah
jz 4f
cmp $UPSCN,%ah
jnz 1b
lea 2(%bp),%ax # up arrow pressed
cmp %ax,%bx
jz 1b
2: sub $"mi::sizeof",%bx
jmp 1b
3: add $"mi::sizeof",%bx # down arrow pressed
cmp %di,%bx
jnz 1b
jmp 2b
4: call .putnl # Enter pressed
pop %di
pop %si
pop %ax
ret
.endfn .choose_mode
.snooze:
push %ax
push %cx
push %dx
mov $0x86,%ah # do a(nother) short few-second wait
mov $(2000000>>16),%cx
mov $(2000000&0xffff),%dx
int $0x15
pop %dx
pop %cx
pop %ax
ret
.endfn .snooze
// Dump a list of all the VESA VBE video modes that are usable on the
// target machine. Also gather a list of video modes and basic
// information about these modes at %ss:(%bp).
.gather_vesa_modes:
push %ds
push %es
push %si
push %di
push %ss # allocate 0x200 bytes on stack
pop %es # for general VESA information
sub $0x200,%sp
mov $0x200/2,%cx
mov %sp,%di
cld
xor %ax,%ax
rep stosw
mov $0x4f00,%ax # get general VESA information
mov %sp,%di
movl $/*"VBE2"*/0x32454256,%es:(%di)
int $0x10
cmp $0x004f,%ax # if this fails, complain
jnz .fail2
mov $REAL(str.mode_list_start),%si
call .puts # otherwise start iterating through
lds %es:0xe(%di),%si # the returned video mode list
movw $0,(%bp)
cld
.iter1: lodsw
inc %ax
jz .done2
dec %ax
call .munge # process mode number
jc .iter1
.iter2: lodsw
inc %ax
jz .nl
dec %ax
call .munge # process another mode number
jc .iter2
.iter3: lodsw
inc %ax
jz .nl
dec %ax
call .munge # process another mode number
jc .iter3
call .putnl
jmp .iter1 # ...and so on
.nl: call .putnl
clc
.done2: add $0x200,%sp # OK, we are finally done
pop %di
pop %si
pop %es
pop %ds
ret
.fail2: mov $REAL(str.no_vesa),%si
call .puts
stc
jmp .done2
.endfn .gather_vesa_modes
// Display information on one video mode number, which should be in %ax.
// If %ax is a mode which we can use, also update the mode information
// buffer at %ss:(%bp). Assume %es = %ss.
//
// @return CF = 0 if video mode is usable, CF = 1 otherwise
.munge: push %ax
push %bx
push %cx
push %si
push %di
or $1<<14,%ax # force frame buffer mode
xchg %ax,%si # remember mode number
sub $0x100,%sp # allocate 0x100 stack bytes for
mov $0x100/2,%cx # information on this mode; clear
mov %sp,%di # the bytes
cld
xor %ax,%ax
rep stosw
mov %si,%cx # get information on one mode
mov $0x4f01,%ax
mov %sp,%di
int $0x10
cmp $0x004f,%ax # if error, skip mode
jnz .fail3
mov %es:(%di),%al
mov %al,%cl
and $0b00001011,%al # also skip if mode is unusable, or
cmp $0b00001011,%al # extra information unavailable, or
jnz .fail3 # is monochrome, or is graphics w/o
mov %cl,%al # linear frame buffer
and $0b10010000,%al
cmp $0b00010000,%al
jz .fail3
call .video_type # check if we know about the video
jc .fail3 # buffer type, & what exact type it is
mov (%bp),%bx # if we are already tracking too
cmp $MAX_VESA_MODES_TO_TRACK,%bx # VESA modes, also skip
jnb .fail3
inc %bx # otherwise start noting down mode
mov %bx,(%bp) # information...
imul $"mi::sizeof",%bx
lea 2-"mi::sizeof"(%ebp,%ebx),%bx
mov %al,%ss:("mi::type")(%bx) # ...starting from frame buffer type
call .putsp # echo and remember mode information
call .putsp
test $0b00010000,%cl # first, echo mode attributes
setnz %al
imul $'G'-'T',%ax,%ax # - 'G': graphics; 'T': text mode
add $'T',%al
call .putc
call .putsp
xchg %ax,%si # then process
mov %ax,%ss:("mi::mode_num")(%bx) # - mode number
call .putx
mov %es:0x12(%di),%ax # - mode width
mov %ax,%ss:("mi::width")(%bx)
call .putd
mov %es:0x14(%di),%ax # - mode height
mov %ax,%ss:("mi::height")(%bx)
call .putd
mov %es:0x19(%di),%al # - bits per pixel
mov %al,%ss:("mi::bpp")(%bx)
call .putdb
mov %es:0x10(%di),%ax # - mode stride
mov %ax,%ss:("mi::stride")(%bx)
testb $0b00010000,%cl
jz .txt
mov %es:0x28(%di),%eax
.fb: mov %eax,%ss:("mi::fb")(%bx) # - frame buffer address
clc
.done3: lea 0x100(%esp),%sp
pop %di
pop %si
pop %cx
pop %bx
pop %ax
ret
.fail3: stc
jmp .done3
.txt: movzwl %es:8(%di),%eax # for text mode, use window A as
shl $4,%eax # frame buffer
jmp .fb
.endfn .munge
// Check if the given video mode information uses a video buffer type
// we know about, and say what type of video buffer it is.
//
// @param %es:(%di) = video mode information from int 0x10, %ax = 0x4f01
// @param %cl = low byte of video mode attributes (= %es:(%di))
// @return %al = video buffer type (for mman::pc_video_type)
// @return CF = 0 if video mode is usable, CF = 1 otherwise
.video_type:
push %bx
push %cx
push %edx
mov $PC_VIDEO_TEXT,%al # if text mode, simply say so
test $0b00010000,%cl
jz .ok4
cmp $6,%es:0x1b(%di) # if graphics mode, check if direct
jnz .fail4 # color; bail out if not
mov %es:0x19(%di),%cl # check actual color fields & bits
mov %es:0x1f(%di),%ax # per pixel, against a list
mov %es:0x21(%di),%edx
mov $REAL(.type_list),%bx
.iter4: cmp %edx,%cs:4(%bx)
jnz .next
cmp %cl,%cs:1(%bx)
jnz .next
cmp %ax,%cs:2(%bx)
jz .found
.next: add $8,%bx
cmp $REAL(.type_list_end),%bx
jnz .iter4
.fail4: stc # unsupported mode; return failure
jmp .done4
.found: mov %cs:(%bx),%al # this mode is supported; return the
.ok4: clc # corresponding type code
.done4: pop %edx
pop %cx
pop %bx
ret
// Output a string via BIOS.
.puts: cld
push %si
0: lodsb
test %al,%al
jz 1f
call .putc
jmp 0b
1: pop %si
ret
.endfn .puts
// Output a 16-bit number in decimal via BIOS.
.putd: push %ax
push %cx
push %dx
push %si
push %ds
push %ss
pop %ds
sub $8,%sp
lea 4(%esp),%si
mov $10,%cx
xor %dx,%dx
div %cx
add $'0'|' '<<8,%dx
mov %dx,(%si)
movb $0,2(%si)
1: mov $' ',%dl
test %ax,%ax
jz 2f
xor %dx,%dx
div %cx
add $'0',%dl
2: dec %si
mov %dl,(%si)
cmp %sp,%si
jnz 1b
call .puts
add $8,%sp
pop %ds
pop %si
pop %dx
pop %cx
pop %ax
ret
.endfn .putd
// Output an 8-bit number in decimal via BIOS.
.putdb: push %ax
mov %al,%ah
cmp $100,%al
jnb 3f
mov $' ',%al
1: call .putc
mov %ah,%al
aam
testb %ah,%ah
jz 5f
add $'0'|'0'<<8,%ax
2: xchg %al,%ah
call .putc
mov %ah,%al
call .putc
pop %ax
ret
3: cmp $200,%al
jnb 4f
sub $100,%ah
mov $'1',%al
jmp 1b
4: sub $200,%ah
mov $'2',%al
jmp 1b
5: add $'0'|' '<<8,%ax
jmp 2b
// Output a number in hexadecimal via BIOS.
.putx: push %ax
push %bx
push %cx
xchg %ax,%bx
mov $'0',%al
call .putc
mov $'x',%al
call .putc
mov $4,%cx
0: rol $4,%bx
mov %bl,%al
and $0b00001111,%al
add $'0',%al
cmp $'9',%al
jna 1f
add $'a'-'9'-1,%al
1: call .putc
loop 0b
pop %cx
pop %bx
pop %ax
.endfn .putx
// fall through
// Output a character via BIOS.
.putsp: mov $' ',%al
.endfn .putsp
// fall through
.putc: push %ax
push %bx
mov $7,%bx
mov $0x0e,%ah
int $0x10
pop %bx
pop %ax
ret
.endfn .putc
.putnl:
mov $'\r',%al
call .putc
mov $'\n',%al
jmp .putc
.endfn .putnl
.previous
str.no_vesa:
.asciz "info: no VESA\r\n"
.endobj str.no_vesa
str.choose_mode:
#define UPGLYPH "\x18"
#define DNGLYPH "\x19"
#define CRGLYPH "\x1b\xd9"
.ascii "\rchoose video mode (",UPGLYPH," ",DNGLYPH," ",CRGLYPH
.asciz "): "
.endobj str.choose_mode
str.no_mode:
.asciz "info: no usable video mode\r\n"
.endobj str.no_mode
str.bad_mode:
.asciz "info: mode switch fail\r\n"
.endobj str.bad_mode
str.mode_list_start:
.ascii "info: VESA video modes:\r\n"
.ascii " mode# X Y bpp"
.ascii " mode# X Y bpp"
.asciz " mode# X Y bpp\r\n"
.endobj str.mode_list_start
.type_list:
// value to use for mman::pc_video_type
// bits per pixel
// red field size (in bits)
// red field position (bits)
// green field size
// green field position
// blue field size
// blue field position
//
.byte PC_VIDEO_BGR565, 16, 5,11, 6, 5, 5, 0
.byte PC_VIDEO_BGR555, 15, 5,10, 5, 5, 5, 0
.byte PC_VIDEO_BGR555, 16, 5,10, 5, 5, 5, 0
.byte PC_VIDEO_RGBX8888,32, 8, 0, 8, 8, 8,16
.byte PC_VIDEO_BGRX8888,32, 8,16, 8, 8, 8, 0
.type_list_end:

View file

@ -25,6 +25,7 @@
OTHER DEALINGS IN THE SOFTWARE.
*/
#include "libc/macros.internal.h"
#include "libc/runtime/mman.internal.h"
#include "libc/vga/vga.internal.h"
// Code snippet for initializing the VGA video mode for bare metal.
@ -42,6 +43,8 @@
// @see sys_writev_vga (libc/vga/writev-vga.c)
.section .sort.text.real.init.2,"ax",@progbits
.code16
call _rlinit_vesa
jnc 9f
mov $0x4f03,%ax # get current video mode via VESA
int $0x10
cmp $0x004f,%ax # is VESA a thing here?
@ -58,6 +61,16 @@
mov $0x0500,%ax # just make sure we are on display
# page 0
2: int $0x10 # otherwise, change the video mode
.set mm,0x0500 # note down video mode parameters
movb $PC_VIDEO_TEXT,mm+"struct mman::pc_video_type"
movw $160,mm+"struct mman::pc_video_stride"
movw $80,mm+"struct mman::pc_video_width"
movw $25,mm+"struct mman::pc_video_height"
movl $0xb8000,mm+"struct mman::pc_video_framebuffer"
movl $0x8000,mm+"struct mman::pc_video_framebuffer_size"
xor %eax,%eax
mov %eax,mm+"struct mman::pc_video_framebuffer"+4
mov %eax,mm+"struct mman::pc_video_framebuffer_size"+4
mov $0x1003,%ax # enable/disable VGA text blinking
#ifdef VGA_USE_BLINK
mov $1,%bx
@ -65,6 +78,7 @@
xor %bx,%bx
#endif
int $0x10
9:
.previous
.code64
.section .rodata,"a",@progbits

View file

@ -240,7 +240,7 @@ static uint8_t TtyGetVgaAttr(struct Tty *tty) {
return attr;
}
void _TtyErase(struct Tty *tty, size_t dst, size_t n) {
static 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)
@ -248,11 +248,32 @@ void _TtyErase(struct Tty *tty, size_t dst, size_t n) {
if (Wcs(tty)) wmemset(Wcs(tty) + dst, L' ', n);
}
void _TtyMemmove(struct Tty *tty, size_t dst, size_t src, size_t n) {
void _TtyEraseLineCells(struct Tty *tty, size_t dsty, size_t dstx, size_t n) {
size_t xn = Xn(tty);
TtyErase(tty, dsty * xn + dstx, n);
}
void _TtyEraseLines(struct Tty *tty, size_t dsty, size_t n) {
size_t xn = Xn(tty);
TtyErase(tty, dsty * xn, n * xn);
}
static 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 _TtyMoveLineCells(struct Tty *tty, size_t dsty, size_t dstx,
size_t srcy, size_t srcx, size_t n) {
size_t xn = Xn(tty);
TtyMemmove(tty, dsty * xn + dstx, srcy * xn + srcx, n);
}
void _TtyMoveLines(struct Tty *tty, size_t dsty, size_t srcy, size_t n) {
size_t xn = Xn(tty);
TtyMemmove(tty, dsty * xn, srcy * xn, n * xn);
}
void _TtyResetOutputMode(struct Tty *tty) {
tty->pr = 0;
tty->fg = DEFAULT_FG;
@ -272,7 +293,7 @@ void _TtyFullReset(struct Tty *tty) {
_TtyResetOutputMode(tty);
tty->y = 0;
tty->x = 0;
_TtyErase(tty, 0, Yn(tty) * Xn(tty));
_TtyEraseLines(tty, 0, Yn(tty));
}
void _TtySetY(struct Tty *tty, unsigned short y) {
@ -286,13 +307,13 @@ void _TtySetX(struct Tty *tty, unsigned short 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));
_TtyMoveLines(tty, 0, 1, Yn(tty) - 1);
_TtyEraseLines(tty, Yn(tty) - 1, 1);
}
static void TtyReverse(struct Tty *tty) {
_TtyMemmove(tty, Xn(tty), 0, Xn(tty) * (Yn(tty) - 1));
_TtyErase(tty, 0, Xn(tty));
_TtyMoveLines(tty, 1, 0, Yn(tty) - 1);
_TtyEraseLines(tty, 0, 1);
}
static void TtyIndex(struct Tty *tty) {
@ -464,15 +485,16 @@ static void TtyRestoreCursorPosition(struct Tty *tty) {
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));
_TtyEraseLineCells(tty, tty->y, tty->x, Xn(tty) - tty->x);
_TtyEraseLines(tty, tty->y + 1, Yn(tty) - tty->y - 1);
break;
case 1:
_TtyErase(tty, 0, tty->y * Xn(tty) + tty->x);
_TtyEraseLines(tty, 0, tty->y);
_TtyEraseLineCells(tty, tty->y, 0, tty->x);
break;
case 2:
case 3:
_TtyErase(tty, 0, Yn(tty) * Xn(tty));
_TtyEraseLines(tty, 0, Yn(tty));
break;
default:
break;
@ -482,13 +504,13 @@ static void TtyEraseDisplay(struct Tty *tty) {
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);
_TtyEraseLineCells(tty, tty->y, tty->x, Xn(tty) - tty->x);
break;
case 1:
_TtyErase(tty, tty->y * Xn(tty), tty->x);
_TtyEraseLineCells(tty, tty->y, 0, tty->x);
break;
case 2:
_TtyErase(tty, tty->y * Xn(tty), Xn(tty));
_TtyEraseLines(tty, tty->y, 1);
break;
default:
break;
@ -496,11 +518,23 @@ static void TtyEraseLine(struct Tty *tty) {
}
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);
int yn, xn, yi, xi, n, left;
yn = Yn(tty);
xn = Xn(tty);
yi = tty->y;
xi = tty->x;
left = min(max(TtyAtoi(tty->esc.s, NULL), 1), yn * xn - (yi * xn + xi));
while (left) {
if (left >= xn - xi) {
_TtyEraseLineCells(tty, yi, xi, xn - xi);
left -= xn - xi;
++yi;
xi = 0;
} else {
_TtyEraseLineCells(tty, yi, xi, left);
left = 0;
}
}
}
static int TtyArg1(struct Tty *tty) {
@ -509,30 +543,28 @@ static int TtyArg1(struct Tty *tty) {
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);
_TtyMoveLineCells(tty, tty->y, tty->x + n, tty->y, tty->x,
Xn(tty) - (tty->x + n));
_TtyEraseLineCells(tty, tty->y, 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));
_TtyMoveLines(tty, tty->y + n, tty->y, Yn(tty) - tty->y - n);
_TtyEraseLines(tty, tty->y, n);
}
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);
_TtyMoveLineCells(tty, tty->y, tty->x, tty->y, tty->x + n,
Xn(tty) - (tty->x + n));
_TtyEraseLineCells(tty, tty->y, 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));
_TtyMoveLines(tty, tty->y, tty->y + n, Yn(tty) - tty->y - n);
_TtyEraseLines(tty, tty->y + n, n);
}
static void TtyReportDeviceStatus(struct Tty *tty) {

View file

@ -1,22 +1,21 @@
#ifndef COSMOPOLITAN_LIBC_VGA_VGA_INTERNAL_H_
#define COSMOPOLITAN_LIBC_VGA_VGA_INTERNAL_H_
/** Preferred width of the video screen, in character units. */
#define VGA_PREFER_TTY_HEIGHT 30
/** Preferred width of the video screen, in character units. */
#define VGA_PREFER_TTY_WIDTH 80
/** Assumed height of each character in pixels, in graphics modes. */
#define VGA_ASSUME_CHAR_HEIGHT_PX 16
/** Assumed width of each character in pixels, in graphics modes. */
#define VGA_ASSUME_CHAR_WIDTH_PX 8
/*
* VGA_TTY_HEIGHT, VGA_TTY_WIDTH, VGA_USE_WCS, & VGA_PERSNICKETY_STATUS are
* configuration knobs which can potentially be used to tweak the features
* to be compiled into our VGA teletypewriter support.
* VGA_USE_WCS, VGA_USE_BLINK, & VGA_PERSNICKETY_STATUS 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
@ -113,13 +112,22 @@ struct VgaTextCharCell {
};
struct Tty {
/**
* Cursor position. (y, x) = (0, 0) means the cursor is on the top left
* character cell of the terminal.
*/
unsigned short y, x;
#ifndef VGA_TTY_HEIGHT
unsigned short yn;
#endif
#ifndef VGA_TTY_WIDTH
unsigned short xn;
#endif
/** Height and width of terminal, in character units. */
unsigned short yn, xn;
/** Height and width of terminal, in pixels (if in graphics video mode). */
unsigned short yp, xp;
/**
* Number of bytes (NOTE) occupied by each row of pixels, including any
* invisible "pixels".
*/
unsigned short xs;
/** Type of video buffer (from mman::pc_video_type). */
unsigned char type;
uint32_t u8;
uint32_t n8;
uint32_t pr;
@ -155,8 +163,10 @@ 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 _TtyMoveLineCells(struct Tty *, size_t, size_t, size_t, size_t, size_t);
void _TtyMoveLines(struct Tty *, size_t, size_t, size_t);
void _TtyEraseLineCells(struct Tty *, size_t, size_t, size_t);
void _TtyEraseLines(struct Tty *, size_t, size_t);
void _TtySetY(struct Tty *, unsigned short);
void _TtySetX(struct Tty *, unsigned short);

View file

@ -32,12 +32,6 @@
#include "libc/runtime/pc.internal.h"
#include "libc/str/str.h"
#ifdef VGA_USE_WCS
static wchar_t vga_wcs[VGA_TTY_HEIGHT * VGA_TTY_WIDTH];
#else
static wchar_t * const vga_wcs = NULL;
#endif
struct Tty _vga_tty;
ssize_t sys_writev_vga(struct Fd *fd, const struct iovec *iov, int iovlen) {
@ -58,9 +52,194 @@ ssize_t sys_writev_vga(struct Fd *fd, const struct iovec *iov, int iovlen) {
return wrote;
}
static void _vga_init_test(void *vid_buf, unsigned char vid_type,
size_t stride) {
switch (vid_type) {
case PC_VIDEO_TEXT:
break;
case PC_VIDEO_BGR565:
{
char *row_buf = (char *)vid_buf + stride * 100;
uint16_t *row_pix = (uint16_t *)row_buf;
unsigned i;
row_pix[0] = 0x0000;
row_pix[1] = 0x0000;
row_pix[2] = 0x07fc;
row_pix[3] = 0x07fc;
row_pix[4] = 0x07fc;
row_pix[5] = 0x0000;
row_pix[6] = 0x0000;
row_pix[7] = 0x0000;
row_buf += stride;
row_pix = (uint16_t *)row_buf;
row_pix[0] = 0x0000;
row_pix[1] = 0x07fc;
row_pix[2] = 0x07fc;
row_pix[3] = 0x07fc;
row_pix[4] = 0x07fc;
row_pix[5] = 0x07fc;
row_pix[6] = 0x0000;
row_pix[7] = 0x0000;
row_buf += stride;
row_pix = (uint16_t *)row_buf;
row_pix[0] = 0x07fc;
row_pix[1] = 0x07fc;
row_pix[2] = 0x0000;
row_pix[3] = 0x0000;
row_pix[4] = 0x0000;
row_pix[5] = 0x07fc;
row_pix[6] = 0x07fc;
row_pix[7] = 0x0000;
row_buf += stride;
row_pix = (uint16_t *)row_buf;
row_pix[0] = 0x07fc;
row_pix[1] = 0x07fc;
row_pix[2] = 0x07fc;
row_pix[3] = 0x07fc;
row_pix[4] = 0x07fc;
row_pix[5] = 0x07fc;
row_pix[6] = 0x07fc;
row_pix[7] = 0x0000;
for (i = 0; i < 4; ++i) {
row_buf += stride;
row_pix = (uint16_t *)row_buf;
row_pix[0] = 0xf81f;
row_pix[1] = 0xf81f;
row_pix[2] = 0x0000;
row_pix[3] = 0x0000;
row_pix[4] = 0x0000;
row_pix[5] = 0xf81f;
row_pix[6] = 0xf81f;
row_pix[7] = 0x0000;
}
}
break;
case PC_VIDEO_BGR555:
{
char *row_buf = (char *)vid_buf + stride * 100;
uint16_t *row_pix = (uint16_t *)row_buf;
unsigned i;
row_pix[0] = 0x8000;
row_pix[1] = 0x8000;
row_pix[2] = 0x83fc;
row_pix[3] = 0x83fc;
row_pix[4] = 0x83fc;
row_pix[5] = 0x8000;
row_pix[6] = 0x8000;
row_pix[7] = 0x8000;
row_buf += stride;
row_pix = (uint16_t *)row_buf;
row_pix[0] = 0x8000;
row_pix[1] = 0x83fc;
row_pix[2] = 0x83fc;
row_pix[3] = 0x83fc;
row_pix[4] = 0x83fc;
row_pix[5] = 0x83fc;
row_pix[6] = 0x8000;
row_pix[7] = 0x8000;
row_buf += stride;
row_pix = (uint16_t *)row_buf;
row_pix[0] = 0x83fc;
row_pix[1] = 0x83fc;
row_pix[2] = 0x8000;
row_pix[3] = 0x8000;
row_pix[4] = 0x8000;
row_pix[5] = 0x83fc;
row_pix[6] = 0x83fc;
row_pix[7] = 0x8000;
row_buf += stride;
row_pix = (uint16_t *)row_buf;
row_pix[0] = 0x83fc;
row_pix[1] = 0x83fc;
row_pix[2] = 0x83fc;
row_pix[3] = 0x83fc;
row_pix[4] = 0x83fc;
row_pix[5] = 0x83fc;
row_pix[6] = 0x83fc;
row_pix[7] = 0x8000;
for (i = 0; i < 4; ++i) {
row_buf += stride;
row_pix = (uint16_t *)row_buf;
row_pix[0] = 0xfc1f;
row_pix[1] = 0xfc1f;
row_pix[2] = 0x8000;
row_pix[3] = 0x8000;
row_pix[4] = 0x8000;
row_pix[5] = 0xfc1f;
row_pix[6] = 0xfc1f;
row_pix[7] = 0x8000;
}
}
break;
default:
{
char *row_buf = (char *)vid_buf + stride * 100;
uint32_t *row_pix = (uint32_t *)row_buf;
unsigned i;
row_pix[0] = 0xff000000;
row_pix[1] = 0xff000000;
row_pix[2] = 0xff00ffe0;
row_pix[3] = 0xff00ffe0;
row_pix[4] = 0xff00ffe0;
row_pix[5] = 0xff000000;
row_pix[6] = 0xff000000;
row_pix[7] = 0xff000000;
row_buf += stride;
row_pix = (uint32_t *)row_buf;
row_pix[0] = 0xff000000;
row_pix[1] = 0xff00ffe0;
row_pix[2] = 0xff00ffe0;
row_pix[3] = 0xff00ffe0;
row_pix[4] = 0xff00ffe0;
row_pix[5] = 0xff00ffe0;
row_pix[6] = 0xff000000;
row_pix[7] = 0xff000000;
row_buf += stride;
row_pix = (uint32_t *)row_buf;
row_pix[0] = 0xff00ffe0;
row_pix[1] = 0xff00ffe0;
row_pix[2] = 0xff000000;
row_pix[3] = 0xff000000;
row_pix[4] = 0xff000000;
row_pix[5] = 0xff00ffe0;
row_pix[6] = 0xff00ffe0;
row_pix[7] = 0xff000000;
row_buf += stride;
row_pix = (uint32_t *)row_buf;
row_pix[0] = 0xff00ffe0;
row_pix[1] = 0xff00ffe0;
row_pix[2] = 0xff00ffe0;
row_pix[3] = 0xff00ffe0;
row_pix[4] = 0xff00ffe0;
row_pix[5] = 0xff00ffe0;
row_pix[6] = 0xff00ffe0;
row_pix[7] = 0xff000000;
for (i = 0; i < 4; ++i) {
row_buf += stride;
row_pix = (uint32_t *)row_buf;
row_pix[0] = 0xffff00ff;
row_pix[1] = 0xffff00ff;
row_pix[2] = 0xff000000;
row_pix[3] = 0xff000000;
row_pix[4] = 0xff000000;
row_pix[5] = 0xffff00ff;
row_pix[6] = 0xffff00ff;
row_pix[7] = 0xff000000;
}
}
break;
}
}
__attribute__((__constructor__)) static textstartup void _vga_init(void) {
if (IsMetal()) {
void * const vid_buf = (void *)(BANE + 0xb8000ull);
struct mman *mm = (struct mman *)(BANE + 0x0500);
unsigned char vid_type = mm->pc_video_type;
unsigned short height = mm->pc_video_height, width = mm->pc_video_width,
stride = mm->pc_video_stride;
uint64_t vid_buf_phy = mm->pc_video_framebuffer;
void *vid_buf = (void *)(BANE + vid_buf_phy);
size_t vid_buf_sz = mm->pc_video_framebuffer_size;
/*
* Get the initial cursor position from the BIOS data area. Also get
* the height (in scan lines) of each character; this is used to set the
@ -74,11 +253,17 @@ __attribute__((__constructor__)) static textstartup void _vga_init(void) {
chr_ht_hi = *(uint8_t *)(BANE + 0x0486ull);
if (chr_ht_hi != 0 || chr_ht > 32)
chr_ht = 32;
/* Make sure the video buffer is mapped into virtual memory. */
__invert_memory_area(mm, __get_pml4t(), vid_buf_phy, vid_buf_sz, PAGE_RW);
#if 1
/* Test video frame buffer output. */
_vga_init_test(vid_buf, vid_type, stride);
#endif
/*
* Initialize our tty structure from the current screen contents,
* current cursor position, & character height.
* Initialize our tty structure from the current screen geometry,
* screen contents, cursor position, & character height.
*/
_StartTty(&_vga_tty, VGA_TTY_HEIGHT, VGA_TTY_WIDTH, pos.row, pos.col,
chr_ht, vid_buf, vga_wcs);
_StartTty(&_vga_tty, height, width, pos.row, pos.col, chr_ht,
vid_buf, NULL);
}
}