boot: pick a resolution with R, instead of hardcoding 800x600

The VBE mode was the literal 0x143 in stage2.S, so 800x600 was the only
resolution the system could ever have. Real mode is the only place this
can be chosen: setting a mode is INT 10h, stage2main.c already runs in
protected mode, and the kernel has no modeset path -- so whatever is
picked here is what the framebuffer, the console and Schcom all get.

Press R at the prompt within two seconds for 800x600, 1024x768 or
1280x1024. It boots on its own otherwise: a loader that waits for a
keypress is a machine that does not come back up after a power cut.

The depth is checked before the mode is set, and that is not belt and
braces. 0x146 was in the table as 1600x1200x32 and this VBE returns
320x200x8 for it -- a mode the BIOS accepts, so an error return never
happens. It boots and then everything above renders garbage, because the
console, the fbdev ioctls and the compositor all assume 32bpp. Only
numbers verified to come back as the advertised size at 32bpp are listed
now, and anything else falls back to 800x600.

Verified by driving the monitor's sendkey: 1, 2 and 3 come up at
800x600, 1024x768 and 1280x1024 respectively, all reaching schinit, and
no key at all still gives 800x600. bootcheck 108 [ok], schcom-test
passes.

Typing an arbitrary size is the obvious next step and deliberately not
guessed at here: it wants enumerating the BIOS's own mode list
(INT 10h AX=4F00) and matching on width and height, which would replace
the magic numbers rather than add more of them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-08-10 04:47:27 +02:00
commit 8b52782b75

View file

@ -21,16 +21,46 @@ stage2start:
# clobber stage2's own tail end. 0x30000 is clear of stage2 (ends well
# under 0xA000), the multiboot kernel-search buffer (0x10000-0x14000),
# and the kernel's own load address (0x100000).
# Which mode. 0x143 is 800x600x32 and stays the default: it is the smallest
# the system is meant to run at, and the one every path has been tested
# against. Press R at the prompt to pick another.
movw $0x0143, %cx
call res_menu # may return a different mode in CX
movw $0x3000, %ax
movw %ax, %es
xorw %di, %di # ES:DI = 3000:0000 = flat 0x30000
movw $0x0143, %cx
movw $0x4F01, %ax
int $0x10
# Two ways this goes wrong, and only one of them is an error return.
#
# A mode number the BIOS does not know fails outright, which is easy. But a
# number it *does* know may be nothing like what was asked for: 0x146 was
# written here as 1600x1200x32 and this VBE returns 320x200x8 for it. That
# boots, and then the framebuffer console and Schcom both render garbage,
# because everything above assumes 32bpp. So the depth reported in the mode
# info block is checked before the mode is set, not after.
cmpw $0x004F, %ax
jne res_fallback
cmpb $32, %es:25(%di) # mode info +25: bits per pixel
je res_ok
res_fallback:
cmpw $0x0143, %cx
je e820 # the fallback itself failed; give up on VBE
movw $0x0143, %cx
xorw %di, %di
movw $0x4F01, %ax
int $0x10
cmpw $0x004F, %ax
jne e820
movw $0x4143, %bx
cmpb $32, %es:25(%di)
jne e820
res_ok:
movw %cx, %bx
orw $0x4000, %bx # bit 14: linear framebuffer
movw $0x4F02, %ax
int $0x10
@ -99,6 +129,97 @@ print16:
.Lp16done:
ret
# Offer a resolution choice, briefly.
#
# In real mode because this is the only place it can be: setting a VBE mode is
# INT 10h, and by the time stage2main.c runs we are in protected mode with no
# BIOS left to call. The kernel cannot change it either -- there is no modeset
# path, so whatever is chosen here is what the framebuffer, the console and the
# compositor all get.
#
# Two seconds, then it boots on its own. A loader that waits for a keypress is
# a machine that does not come back up after a power cut.
#
# In: CX = default mode. Out: CX = chosen mode. Clobbers AX, BX, DX, SI.
res_menu:
pushw %cx
movw $resprompt, %si
call print16
# Poll for ~2s. INT 1Ah tick count runs at 18.2 Hz, so 36 ticks is two
# seconds and needs no calibration against CPU speed -- a delay loop would be
# a different length on every machine.
movb $0x00, %ah
int $0x1A # CX:DX = ticks since midnight
movw %dx, %bx # BX = starting tick, low word
.Lrwait:
movb $0x01, %ah
int $0x16 # ZF=0 if a key is waiting
jnz .Lrkey
movb $0x00, %ah
int $0x1A
subw %bx, %dx
cmpw $36, %dx
jb .Lrwait
popw %cx # timed out: keep the default
ret
.Lrkey:
xorw %ax, %ax
int $0x16 # consume it
orb $0x20, %al # crude tolower: 'R' and 'r' both land on 'r'
cmpb $'r', %al
je .Lrshow
popw %cx # some other key: boot with the default
ret
.Lrshow:
popw %cx # default is no longer needed; a pick replaces it
movw $resmenu, %si
call print16
.Lrpick:
xorw %ax, %ax
int $0x16
cmpb $'1', %al
jb .Lrpick
cmpb $'3', %al
ja .Lrpick
# Index the table: (key - '1') * 2, because each entry is one 16-bit mode.
subb $'1', %al
movzbw %al, %bx
shlw $1, %bx
movw resmodes(%bx), %cx
movw $resok, %si
call print16
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.
#
# Numbers are the VESA/Bochs 0x140 block, and only ones actually verified to
# come back as the advertised size at 32bpp are listed. 0x146 was in here as
# 1600x1200 and returns 320x200x8, which is why the caller now checks the depth
# rather than trusting the table.
#
# Adding a preset means checking it the same way -- boot it and read the
# kernel's "VGA:" line. The real fix is to enumerate the BIOS's own mode list
# (INT 10h AX=4F00) and match on width/height, which would also allow typing an
# arbitrary size; that is the next step here, not a guess at more numbers.
resmodes:
.word 0x0143 # 800x600 verified
.word 0x0144 # 1024x768 verified
.word 0x0145 # 1280x1024 verified
resprompt:
.asciz "Press R for resolution... "
resmenu:
.asciz "\r\n 1) 800x600 2) 1024x768 3) 1280x1024\r\n choice: "
resok:
.asciz "\r\n"
bootmsg:
.asciz "SchOS booting...\r\n"