boot: make the resolution prompt reachable from the serial console
The menu shipped working only for someone driving the emulated keyboard, which is nobody. `make qemu` runs -serial mon:stdio, so the terminal the developer is sitting in front of is the serial line -- and the menu read INT 16h, which is the PS/2 keyboard. Keys went nowhere. The prompt was invisible for a second reason: BIOS teletype writes to the VGA text screen, and `make qemu` attaches virtio-gpu alongside stdvga and shows that one, so the text landed on a display nobody was looking at. So print16 now writes to both the screen and COM1, and the menu accepts a key from either source. The transmitter wait is bounded, because a machine with no serial port reads 0xFF from the status register forever and would otherwise hang the loader on exactly the hardware that cannot say why. Also: four seconds rather than two, which is not long when the prompt has to be read first; the chosen digit is echoed, because a serial line has no local echo and the menu otherwise looks like it ignored the key; and the timeout says "keeping 800x600" instead of silently moving on. Verified three ways: typing r then 2 over serial gives 1024x768, sendkey r then 3 on the keyboard gives 1280x1024, and no key at all prints the timeout and boots 800x600. bootcheck 108 [ok]. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
8b52782b75
commit
8757846a9b
1 changed files with 98 additions and 13 deletions
|
|
@ -115,20 +115,93 @@ seta20.2:
|
||||||
|
|
||||||
ljmp $(SEG_KCODE<<3), $start32
|
ljmp $(SEG_KCODE<<3), $start32
|
||||||
|
|
||||||
# Print null-terminated string at DS:SI via BIOS teletype (real mode only).
|
# Print null-terminated string at DS:SI, to the screen *and* the serial port.
|
||||||
# Clobbers AX, BX, SI.
|
# Clobbers AX, BX, DX, SI.
|
||||||
|
#
|
||||||
|
# Both, because neither one alone is where the person is looking. BIOS teletype
|
||||||
|
# goes to the VGA text screen -- but `make qemu` attaches virtio-gpu alongside
|
||||||
|
# stdvga and shows that one, so the text lands on a display nobody sees. And
|
||||||
|
# `make qemu` puts the serial line on stdio, which is the terminal the developer
|
||||||
|
# is actually sitting in front of.
|
||||||
print16:
|
print16:
|
||||||
.Lp16loop:
|
.Lp16loop:
|
||||||
lodsb
|
lodsb
|
||||||
testb %al, %al
|
testb %al, %al
|
||||||
jz .Lp16done
|
jz .Lp16done
|
||||||
|
pushw %ax
|
||||||
movb $0x0E, %ah
|
movb $0x0E, %ah
|
||||||
movw $0x0007, %bx
|
movw $0x0007, %bx
|
||||||
int $0x10
|
int $0x10
|
||||||
|
popw %ax
|
||||||
|
call serial_putc
|
||||||
jmp .Lp16loop
|
jmp .Lp16loop
|
||||||
.Lp16done:
|
.Lp16done:
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
# Write AL to COM1, preserving AX.
|
||||||
|
#
|
||||||
|
# The wait for the transmitter is bounded. A machine with no serial port reads
|
||||||
|
# 0xFF from the status register forever, and an unbounded wait there would hang
|
||||||
|
# the loader on exactly the hardware that cannot tell you why.
|
||||||
|
serial_putc:
|
||||||
|
pushw %ax
|
||||||
|
pushw %cx
|
||||||
|
pushw %dx
|
||||||
|
movb %al, %ah # stash the character; AL is about to be an input
|
||||||
|
movw $0xFFFF, %cx
|
||||||
|
movw $0x3FD, %dx # LSR
|
||||||
|
.Lspw:
|
||||||
|
inb %dx, %al
|
||||||
|
testb $0x20, %al # transmitter holding register empty
|
||||||
|
jnz .Lspgo
|
||||||
|
loop .Lspw
|
||||||
|
jmp .Lspdone # never drained: no port, or nothing listening
|
||||||
|
.Lspgo:
|
||||||
|
movb %ah, %al
|
||||||
|
movw $0x3F8, %dx
|
||||||
|
outb %al, %dx
|
||||||
|
.Lspdone:
|
||||||
|
popw %dx
|
||||||
|
popw %cx
|
||||||
|
popw %ax
|
||||||
|
ret
|
||||||
|
|
||||||
|
# CF=1 and AL=byte if COM1 has one waiting, CF=0 otherwise. Clobbers AX, DX.
|
||||||
|
serial_getc:
|
||||||
|
pushw %dx
|
||||||
|
movw $0x3FD, %dx # LSR
|
||||||
|
inb %dx, %al
|
||||||
|
testb $0x01, %al # data ready
|
||||||
|
jz .Lsgnone
|
||||||
|
movw $0x3F8, %dx
|
||||||
|
inb %dx, %al
|
||||||
|
popw %dx
|
||||||
|
stc
|
||||||
|
ret
|
||||||
|
.Lsgnone:
|
||||||
|
popw %dx
|
||||||
|
clc
|
||||||
|
ret
|
||||||
|
|
||||||
|
# CF=1 and AL=key from either the BIOS keyboard or the serial line.
|
||||||
|
# Clobbers AX, DX.
|
||||||
|
#
|
||||||
|
# Both, because the menu has to answer whoever is typing. Under `make qemu` the
|
||||||
|
# serial line is stdio -- the terminal the developer is in -- while a real
|
||||||
|
# machine, or a QEMU window, has a keyboard and no serial at all. Listening to
|
||||||
|
# only one is how this shipped "working" and did nothing for the person using it.
|
||||||
|
anykey:
|
||||||
|
movb $0x01, %ah
|
||||||
|
int $0x16 # ZF=0 if the keyboard has one
|
||||||
|
jz .Lakserial
|
||||||
|
xorw %ax, %ax
|
||||||
|
int $0x16 # consume it
|
||||||
|
stc
|
||||||
|
ret
|
||||||
|
.Lakserial:
|
||||||
|
call serial_getc
|
||||||
|
ret
|
||||||
|
|
||||||
# Offer a resolution choice, briefly.
|
# Offer a resolution choice, briefly.
|
||||||
#
|
#
|
||||||
# In real mode because this is the only place it can be: setting a VBE mode is
|
# In real mode because this is the only place it can be: setting a VBE mode is
|
||||||
|
|
@ -137,7 +210,7 @@ print16:
|
||||||
# path, so whatever is chosen here is what the framebuffer, the console and the
|
# path, so whatever is chosen here is what the framebuffer, the console and the
|
||||||
# compositor all get.
|
# compositor all get.
|
||||||
#
|
#
|
||||||
# Two seconds, then it boots on its own. A loader that waits for a keypress is
|
# Four 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.
|
# 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.
|
# In: CX = default mode. Out: CX = chosen mode. Clobbers AX, BX, DX, SI.
|
||||||
|
|
@ -146,30 +219,31 @@ res_menu:
|
||||||
movw $resprompt, %si
|
movw $resprompt, %si
|
||||||
call print16
|
call print16
|
||||||
|
|
||||||
# Poll for ~2s. INT 1Ah tick count runs at 18.2 Hz, so 36 ticks is two
|
# Poll for ~4s. INT 1Ah tick count runs at 18.2 Hz, so 72 ticks is four
|
||||||
# seconds and needs no calibration against CPU speed -- a delay loop would be
|
# seconds and needs no calibration against CPU speed -- a delay loop would be
|
||||||
# a different length on every machine.
|
# a different length on every machine.
|
||||||
movb $0x00, %ah
|
movb $0x00, %ah
|
||||||
int $0x1A # CX:DX = ticks since midnight
|
int $0x1A # CX:DX = ticks since midnight
|
||||||
movw %dx, %bx # BX = starting tick, low word
|
movw %dx, %bx # BX = starting tick, low word
|
||||||
.Lrwait:
|
.Lrwait:
|
||||||
movb $0x01, %ah
|
call anykey
|
||||||
int $0x16 # ZF=0 if a key is waiting
|
jc .Lrkey
|
||||||
jnz .Lrkey
|
|
||||||
movb $0x00, %ah
|
movb $0x00, %ah
|
||||||
int $0x1A
|
int $0x1A
|
||||||
subw %bx, %dx
|
subw %bx, %dx
|
||||||
cmpw $36, %dx
|
cmpw $72, %dx
|
||||||
jb .Lrwait
|
jb .Lrwait
|
||||||
|
movw $restimeout, %si
|
||||||
|
call print16
|
||||||
popw %cx # timed out: keep the default
|
popw %cx # timed out: keep the default
|
||||||
ret
|
ret
|
||||||
|
|
||||||
.Lrkey:
|
.Lrkey:
|
||||||
xorw %ax, %ax
|
|
||||||
int $0x16 # consume it
|
|
||||||
orb $0x20, %al # crude tolower: 'R' and 'r' both land on 'r'
|
orb $0x20, %al # crude tolower: 'R' and 'r' both land on 'r'
|
||||||
cmpb $'r', %al
|
cmpb $'r', %al
|
||||||
je .Lrshow
|
je .Lrshow
|
||||||
|
movw $restimeout, %si
|
||||||
|
call print16
|
||||||
popw %cx # some other key: boot with the default
|
popw %cx # some other key: boot with the default
|
||||||
ret
|
ret
|
||||||
|
|
||||||
|
|
@ -178,13 +252,22 @@ res_menu:
|
||||||
movw $resmenu, %si
|
movw $resmenu, %si
|
||||||
call print16
|
call print16
|
||||||
.Lrpick:
|
.Lrpick:
|
||||||
xorw %ax, %ax
|
call anykey
|
||||||
int $0x16
|
jnc .Lrpick
|
||||||
cmpb $'1', %al
|
cmpb $'1', %al
|
||||||
jb .Lrpick
|
jb .Lrpick
|
||||||
cmpb $'3', %al
|
cmpb $'3', %al
|
||||||
ja .Lrpick
|
ja .Lrpick
|
||||||
|
|
||||||
|
# Echo the digit: over serial there is no local echo, so without this the
|
||||||
|
# menu looks like it ignored the key.
|
||||||
|
call serial_putc
|
||||||
|
pushw %ax
|
||||||
|
movb $0x0E, %ah
|
||||||
|
movw $0x0007, %bx
|
||||||
|
int $0x10
|
||||||
|
popw %ax
|
||||||
|
|
||||||
# Index the table: (key - '1') * 2, because each entry is one 16-bit mode.
|
# Index the table: (key - '1') * 2, because each entry is one 16-bit mode.
|
||||||
subb $'1', %al
|
subb $'1', %al
|
||||||
movzbw %al, %bx
|
movzbw %al, %bx
|
||||||
|
|
@ -214,7 +297,9 @@ resmodes:
|
||||||
.word 0x0145 # 1280x1024 verified
|
.word 0x0145 # 1280x1024 verified
|
||||||
|
|
||||||
resprompt:
|
resprompt:
|
||||||
.asciz "Press R for resolution... "
|
.asciz "Press R within 4s to choose a resolution... "
|
||||||
|
restimeout:
|
||||||
|
.asciz "keeping 800x600\r\n"
|
||||||
resmenu:
|
resmenu:
|
||||||
.asciz "\r\n 1) 800x600 2) 1024x768 3) 1280x1024\r\n choice: "
|
.asciz "\r\n 1) 800x600 2) 1024x768 3) 1280x1024\r\n choice: "
|
||||||
resok:
|
resok:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue