diff --git a/libc/intrin/mman.greg.c b/libc/intrin/mman.greg.c index e85eb9784..b8f87c165 100644 --- a/libc/intrin/mman.greg.c +++ b/libc/intrin/mman.greg.c @@ -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); } diff --git a/libc/runtime/mman.internal.h b/libc/runtime/mman.internal.h index 5a9e2b642..9f96ed695 100644 --- a/libc/runtime/mman.internal.h +++ b/libc/runtime/mman.internal.h @@ -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_ */ diff --git a/libc/runtime/pc.internal.h b/libc/runtime/pc.internal.h index 6ff21bfdf..9ef963f11 100644 --- a/libc/runtime/pc.internal.h +++ b/libc/runtime/pc.internal.h @@ -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) { diff --git a/libc/vga/rlinit-vesa.S b/libc/vga/rlinit-vesa.S new file mode 100644 index 000000000..691c04c76 --- /dev/null +++ b/libc/vga/rlinit-vesa.S @@ -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: diff --git a/libc/vga/vga-init.S b/libc/vga/rlinit-vga.S similarity index 88% rename from libc/vga/vga-init.S rename to libc/vga/rlinit-vga.S index 7e332a5d7..ed74f4fe3 100644 --- a/libc/vga/vga-init.S +++ b/libc/vga/rlinit-vga.S @@ -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 diff --git a/libc/vga/tty.c b/libc/vga/tty.c index b15e027eb..23741ba99 100644 --- a/libc/vga/tty.c +++ b/libc/vga/tty.c @@ -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) { diff --git a/libc/vga/vga.internal.h b/libc/vga/vga.internal.h index 2d4e06e9f..b33806489 100644 --- a/libc/vga/vga.internal.h +++ b/libc/vga/vga.internal.h @@ -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); diff --git a/libc/vga/writev-vga.c b/libc/vga/writev-vga.c index d71cd445b..710820bad 100644 --- a/libc/vga/writev-vga.c +++ b/libc/vga/writev-vga.c @@ -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); } }