boot: enumerate VBE modes, so a resolution can be typed

Option 4 asks for a width and a height and then goes looking for a mode
that matches, instead of picking from three hardcoded numbers.

AX=4F00 returns a controller info block whose VideoModePtr is a far
pointer to a 0xFFFF-terminated list of mode numbers; each is asked what it
actually is with AX=4F01, and the first that reports the requested size at
32bpp wins. That is the fix for the table, not an addition to it -- the
table was wrong the first time it was written (0x146 was "1600x1200" and
is 320x200x8 here), and a table can only ever describe machines somebody
happened to test.

800x600 stays the floor: the console, the splash and Schcom all assume at
least that, and smaller would be a different set of assumptions rather
than a smaller number. Below it, or a size the BIOS cannot do, says so and
keeps 800x600 rather than failing to boot.

Scratch buffers at 0x31000 and 0x31200, clear of the mode info block at
0x30000 and everything else in that page -- the flags at +0x100, the e820
map at +0x208 and the RSDP slot at +0x600.

Verified over serial: 1280x800 comes up at 1280x800, which is in no table
anywhere and is the point; 640x480 is refused as below the minimum;
3000x2000 reports no such mode. Presets 1-3 and the no-key timeout are
unchanged. bootcheck 108 [ok]. stage2 is 20932 of 24576 bytes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-08-10 05:13:53 +02:00
commit 03a867f43e

View file

@ -202,6 +202,133 @@ anykey:
call serial_getc
ret
# Echo AL to both the screen and the serial line. Clobbers nothing.
echo_char:
pushw %ax
pushw %bx
movb $0x0E, %ah
movw $0x0007, %bx
int $0x10
popw %bx
popw %ax
call serial_putc
ret
# Read a decimal number, echoing as it goes. Returns it in AX.
# Ends on Enter; anything that is not a digit is ignored rather than aborting,
# so a stray keystroke does not silently truncate the number.
read_num:
pushw %bx
pushw %cx
pushw %dx
xorw %bx, %bx
.Lrn_loop:
call anykey
jnc .Lrn_loop
cmpb $0x0D, %al # Enter (both the keyboard and a serial line)
je .Lrn_done
cmpb $0x0A, %al
je .Lrn_done
cmpb $'0', %al
jb .Lrn_loop
cmpb $'9', %al
ja .Lrn_loop
call echo_char
subb $'0', %al
movzbw %al, %cx
movw %bx, %ax
movw $10, %dx
mulw %dx # DX:AX = AX * 10
addw %cx, %ax
movw %ax, %bx
jmp .Lrn_loop
.Lrn_done:
movw %bx, %ax
popw %dx
popw %cx
popw %bx
ret
# Find a VBE mode matching reqw x reqh at 32bpp. Returns CX = mode, or 0.
#
# This asks the BIOS for its own list rather than carrying a table of numbers.
# The table was wrong the first time it was written -- 0x146 was "1600x1200" and
# is 320x200x8 on this VBE -- and a table can only ever describe the machines
# somebody happened to test. AX=4F00 returns a controller info block whose
# VideoModePtr is a far pointer to a 0xFFFF-terminated list of mode numbers;
# each one is then asked what it actually is.
#
# Scratch buffers live at 0x31000 and 0x31200, clear of the mode info block at
# 0x30000, the flags at +0x100, the e820 map at +0x208 and the RSDP slot at
# +0x600.
find_mode:
pushw %ax
pushw %bx
pushw %dx
pushw %si
pushw %di
movw $0x3100, %ax
movw %ax, %es
xorw %di, %di
movw $0x4F00, %ax
int $0x10
cmpw $0x004F, %ax
jne .Lfm_none
# VideoModePtr: offset at +14, segment at +16.
movw %es:14(%di), %si
movw %es:16(%di), %ax
movw %ax, %fs
.Lfm_next:
movw %fs:(%si), %cx
addw $2, %si
cmpw $0xFFFF, %cx
je .Lfm_none
# CX and SI have to survive the BIOS call; VBE only promises AX back.
pushw %cx
pushw %si
movw $0x3120, %ax
movw %ax, %es
xorw %di, %di
movw $0x4F01, %ax
int $0x10
popw %si
popw %cx
cmpw $0x004F, %ax
jne .Lfm_next
movw $0x3120, %ax
movw %ax, %es
xorw %di, %di
cmpb $32, %es:25(%di) # bits per pixel
jne .Lfm_next
movw %es:18(%di), %ax # width
cmpw reqw, %ax
jne .Lfm_next
movw %es:20(%di), %ax # height
cmpw reqh, %ax
jne .Lfm_next
jmp .Lfm_done # CX is the answer
.Lfm_none:
xorw %cx, %cx
.Lfm_done:
popw %di
popw %si
popw %dx
popw %bx
popw %ax
ret
reqw:
.word 0
reqh:
.word 0
# Offer a resolution choice, briefly.
#
# In real mode because this is the only place it can be: setting a VBE mode is
@ -254,6 +381,8 @@ res_menu:
.Lrpick:
call anykey
jnc .Lrpick
cmpb $'4', %al
je .Lrcustom
cmpb $'1', %al
jb .Lrpick
cmpb $'3', %al
@ -278,6 +407,47 @@ res_menu:
call print16
ret
# Type a size. Enumerated rather than looked up, so anything the BIOS can
# actually do is available -- not just the three that happen to be listed.
.Lrcustom:
call echo_char
movw $reswidth, %si
call print16
call read_num
movw %ax, reqw
movw $resheight, %si
call print16
call read_num
movw %ax, reqh
movw $resok, %si
call print16
# 800x600 is the floor the system is built around: the console, the splash
# and Schcom all assume at least that much, and a smaller mode would be a
# different set of assumptions rather than a smaller number.
cmpw $800, reqw
jb .Lrcsmall
cmpw $600, reqh
jb .Lrcsmall
call find_mode
testw %cx, %cx
jz .Lrcnone
ret
.Lrcsmall:
movw $resmin, %si
call print16
movw $0x0143, %cx
ret
.Lrcnone:
movw $resnone, %si
call print16
movw $0x0143, %cx
ret
# 32-bit colour throughout: the framebuffer console, the fbdev ioctls and
# Schcom all assume 32bpp, and a 24bpp mode would be a different renderer
# rather than a different number here.
@ -301,9 +471,17 @@ resprompt:
restimeout:
.asciz "keeping 800x600\r\n"
resmenu:
.asciz "\r\n 1) 800x600 2) 1024x768 3) 1280x1024\r\n choice: "
.asciz "\r\n 1) 800x600 2) 1024x768 3) 1280x1024 4) type a size\r\n choice: "
resok:
.asciz "\r\n"
reswidth:
.asciz " width: "
resheight:
.asciz "\r\n height: "
resmin:
.asciz " below the 800x600 minimum, keeping 800x600\r\n"
resnone:
.asciz " no such 32bpp mode, keeping 800x600\r\n"
bootmsg:
.asciz "SchOS booting...\r\n"