diff --git a/boot/bios/stage2.S b/boot/bios/stage2.S index 118276a..cdeae7a 100644 --- a/boot/bios/stage2.S +++ b/boot/bios/stage2.S @@ -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: