/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ │ vi: set noet ft=asm ts=8 sw=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.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. // // @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 push %si # display brief message on magic key mov $REAL(str.inform),%si call .puts pop %si mov $0x8300,%ax # wait for the magic key for a short mov $(1000000>>16),%cx # period of time... mov $(1000000&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 call .done_inform stc .done: pop %ax pop %es ret .doit2: mov $0x8301,%ax # we got the magic key; cancel the int $0x15 # wait timer, & erase message call .done_inform .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 // Clear the informational message on the magic key. .done_inform: mov $0x0a00|' ',%ax mov $7,%bx mov $str.inform.end-str.inform-1,%cx int $0x10 ret // 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 many 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 cmpb $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 str.inform: #define SHGLYPH "\x7f" .ascii "\rinfo: press ",SHGLYPH,"Shift " .asciz "to switch video mode\r" str.inform.end: .endobj str.inform 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 .endobj .type_list .type_list_end: .previous