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
|
||||
|
||||
# Print null-terminated string at DS:SI via BIOS teletype (real mode only).
|
||||
# Clobbers AX, BX, SI.
|
||||
# Print null-terminated string at DS:SI, to the screen *and* the serial port.
|
||||
# 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:
|
||||
.Lp16loop:
|
||||
lodsb
|
||||
testb %al, %al
|
||||
jz .Lp16done
|
||||
pushw %ax
|
||||
movb $0x0E, %ah
|
||||
movw $0x0007, %bx
|
||||
int $0x10
|
||||
popw %ax
|
||||
call serial_putc
|
||||
jmp .Lp16loop
|
||||
.Lp16done:
|
||||
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.
|
||||
#
|
||||
# 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
|
||||
# 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.
|
||||
#
|
||||
# In: CX = default mode. Out: CX = chosen mode. Clobbers AX, BX, DX, SI.
|
||||
|
|
@ -146,30 +219,31 @@ res_menu:
|
|||
movw $resprompt, %si
|
||||
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
|
||||
# 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
|
||||
call anykey
|
||||
jc .Lrkey
|
||||
movb $0x00, %ah
|
||||
int $0x1A
|
||||
subw %bx, %dx
|
||||
cmpw $36, %dx
|
||||
cmpw $72, %dx
|
||||
jb .Lrwait
|
||||
movw $restimeout, %si
|
||||
call print16
|
||||
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
|
||||
movw $restimeout, %si
|
||||
call print16
|
||||
popw %cx # some other key: boot with the default
|
||||
ret
|
||||
|
||||
|
|
@ -178,13 +252,22 @@ res_menu:
|
|||
movw $resmenu, %si
|
||||
call print16
|
||||
.Lrpick:
|
||||
xorw %ax, %ax
|
||||
int $0x16
|
||||
call anykey
|
||||
jnc .Lrpick
|
||||
cmpb $'1', %al
|
||||
jb .Lrpick
|
||||
cmpb $'3', %al
|
||||
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.
|
||||
subb $'1', %al
|
||||
movzbw %al, %bx
|
||||
|
|
@ -214,7 +297,9 @@ resmodes:
|
|||
.word 0x0145 # 1280x1024 verified
|
||||
|
||||
resprompt:
|
||||
.asciz "Press R for resolution... "
|
||||
.asciz "Press R within 4s to choose a resolution... "
|
||||
restimeout:
|
||||
.asciz "keeping 800x600\r\n"
|
||||
resmenu:
|
||||
.asciz "\r\n 1) 800x600 2) 1024x768 3) 1280x1024\r\n choice: "
|
||||
resok:
|
||||
|
|
|
|||
Loading…
Reference in a new issue