--- /dev/null
+; vi:filetype=nasm ts=8 sts=8 sw=8:
+; second stage boot loader
+ bits 16
+ section .boot2
+
+LOADADDR equ 100000h
+DRIVENO_ADDR equ 7bf0h
+
+ extern _boot2_size
+ extern _main_size
+ extern sect_per_track
+ extern num_heads
+
+boot2_start:
+ cli
+
+ xor eax, eax
+ mov al, [DRIVENO_ADDR]
+
+ call setup_serial
+
+ ; enter unreal mode
+ call unreal
+
+ mov al, 10
+ call ser_putchar
+
+ ; enable A20 address line
+ call enable_a20
+
+ ; load program into memory starting at 1MB
+ call load_main
+
+ ; switch video mode, can't do that easily from protected mode
+ mov ax, 13h
+ int 10h
+
+ ; load GDT and IDT
+ lgdt [gdt_lim]
+ lidt [idt_lim]
+
+ ; enter protected mode
+ mov eax, cr0
+ or eax, 1
+ mov cr0, eax
+ ; inter-segment jump to set cs selector to segment 1
+ jmp 0x8:.pmode
+
+ bits 32
+.pmode: ; set all data selectors to segment 2
+ mov ax, 10h
+ mov ds, ax
+ mov ss, ax
+ mov es, ax
+ mov gs, ax
+ mov fs, ax
+
+ jmp LOADADDR
+
+ align 4
+gdt_lim: dw 23
+gdt_base: dd gdt
+
+ align 4
+idt_lim: dw 111
+idt_base: dd idt
+
+ align 8
+gdt: ; 0: null segment
+ dd 0
+ dd 0
+ ; 1: code - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:code/non-conf/rd
+ dd 0000ffffh
+ dd 00cf9a00h
+ ; 2: data - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:data/rw
+ dd 0000ffffh
+ dd 00cf9200h
+
+ align 8
+idt: times 104 db 0
+ ; trap gate 13: general protection fault
+ dw prot_fault
+ dw 8
+ dw 8f00h ; type: trap, present, default
+ dw 0
+
+gpf_msg: db "GP fault "
+
+prot_fault:
+ mov eax, [esp]
+ shr eax, 3
+ call print_num
+ mov al, ':'
+ call putchar
+ mov eax, [esp + 4]
+ call print_num
+ mov al, 10
+ call putchar
+ hlt
+
+
+ bits 16
+unreal:
+ ; use the same GDT as above, will use data seg: 2
+ lgdt [gdt_lim]
+
+ mov eax, cr0
+ or eax, 1
+ mov cr0, eax
+ jmp .pm
+
+.pm: mov ax, 10h
+ mov ds, ax
+ mov es, ax
+ mov fs, ax
+ mov gs, ax
+ mov ss, ax
+
+ mov eax, cr0
+ and ax, 0fffeh
+ mov cr0, eax
+
+ xor ax, ax
+ mov ds, ax
+ mov es, ax
+ mov fs, ax
+ mov gs, ax
+ mov ss, ax
+ ret
+
+mainsz_msg: db 'Program size: ',0
+mainsz_msg2: db ' (',0
+mainsz_msg3: db ' sectors)',10,0
+
+first_sect: dd 0
+sect_left: dd 0
+cur_track: dd 0
+trk_sect: dd 0
+dest_ptr: dd 0
+
+load_main:
+ mov dword [dest_ptr], LOADADDR
+
+ ; calculate first sector
+ mov eax, _boot2_size
+ add eax, 511
+ shr eax, 9
+ ; add 1 to account for the boot sector
+ inc eax
+ mov [first_sect], eax
+
+ ; calculate the first track (first_sect / sect_per_track)
+ movzx ecx, word [sect_per_track]
+ xor edx, edx
+ div ecx
+ mov [cur_track], eax
+ ; remainder is sector within track
+ mov [trk_sect], edx
+
+ mov esi, mainsz_msg
+ call putstr
+ mov eax, _main_size
+ mov ecx, eax
+ call print_num
+
+ mov esi, mainsz_msg2
+ call putstr
+
+ ; calculate sector count
+ add eax, 511
+ shr eax, 9
+ mov [sect_left], eax
+
+ call print_num
+ mov esi, mainsz_msg3
+ call putstr
+
+ ; read a whole track into the buffer (or partial first track)
+.ldloop:
+ movzx ecx, word [sect_per_track]
+ sub ecx, [trk_sect]
+ push ecx
+ call read_track
+
+ ; copy to high memory
+ mov esi, buffer
+ mov edi, [dest_ptr]
+ mov ecx, [esp]
+ shl ecx, 9
+ add [dest_ptr], ecx
+ shr ecx, 2
+ a32 rep movsd
+
+ inc dword [cur_track]
+ ; other than the first track which might be partial, all the rest start from 0
+ mov dword [trk_sect], 0
+
+ pop ecx
+ sub [sect_left], ecx
+ ja .ldloop
+
+ ; the BIOS might have enabled interrupts
+ cli
+
+ ; if we were loaded from floppy, turn all floppy motors off
+ mov bl, [DRIVENO_ADDR]
+ and bl, 80h
+ jnz .notfloppy
+ mov dx, 3f2h
+ in al, dx
+ and al, 0fh
+ out dx, al
+.notfloppy:
+
+ mov ax, 10
+ call putchar
+
+ ret
+
+rdtrk_msg: db 'Reading track: ',0
+rdcyl_msg: db ' - cyl: ',0
+rdhead_msg: db ' head: ',0
+rdsect_msg: db ' start sect: ',0
+rdlast_msg: db ' ... ',0
+rdok_msg: db 'OK',10,0
+rdfail_msg: db 'failed',10,0
+
+read_retries: dw 0
+
+read_track:
+ ; set es to the start of the destination buffer to allo readin in
+ ; full 64k chunks if necessary
+ mov bx, buffer
+ shr bx, 4
+ mov es, bx
+ xor ebx, ebx
+
+ mov word [read_retries], 3
+
+.try:
+ ; print_track
+ mov esi, rdtrk_msg
+ call putstr
+ mov eax, [cur_track]
+ call print_num
+ mov esi, rdcyl_msg
+ call putstr
+
+ ; calc cylinder (cur_track / num_heads) and head (cur_track % num_heads)
+ mov eax, [cur_track]
+ movzx ecx, word [num_heads]
+ xor edx, edx
+ div ecx
+
+ ; print cylinder
+ push eax
+ call print_num
+ ; print head
+ mov esi, rdhead_msg
+ call putstr
+ movzx eax, dx
+ call print_num
+ pop eax
+
+ ; head on dh
+ mov dh, dl
+
+ ; cylinder low byte at ch and high bits at cl[7, 6]
+ mov ch, al
+ mov cl, ah
+ and cl, 3
+ ror cl, 2
+
+ ; print start sector
+ mov esi, rdsect_msg
+ call putstr
+ mov eax, [trk_sect]
+ call print_num
+ mov esi, rdlast_msg
+ call putstr
+
+ ; start sector (1-based) in cl[0, 5]
+ mov al, [trk_sect]
+ inc al
+ and al, 3fh
+ or cl, al
+
+ ; number of sectors in al
+ mov ax, [esp + 2]
+ ; call number (2) in ah
+ mov ah, 2
+ ; drive number in dl
+ mov dl, [DRIVENO_ADDR]
+ int 13h
+ jnc .success
+
+ ; abort after 3 attempts
+ dec word [read_retries]
+ jz .failed
+
+ ; error, reset controller and retry
+ xor ah, ah
+ int 13h
+ jmp .try
+
+.failed:
+ mov esi, rdfail_msg
+ call putstr
+ jmp abort_read
+
+.success:
+ mov esi, rdok_msg
+ call putstr
+
+ ; reset es to 0 before returning
+ xor ax, ax
+ mov es, ax
+ ret
+
+str_read_error: db 'Read error while reading track: ',0
+
+abort_read:
+ mov esi, str_read_error
+ call putstr
+ mov eax, [cur_track]
+ call print_num
+ mov al, 10
+ call putchar
+
+ cli
+.hlt: hlt
+ jmp .hlt
+
+
+ ; print routines
+cursor_x: dd 0
+cursor_y: dd 0
+
+putchar:
+ o32 pusha
+ call ser_putchar
+
+ cmp al, 10
+ jnz .notlf
+ call video_newline
+ jmp .end
+
+.notlf: push eax
+ mov eax, [cursor_y]
+ mov ecx, 80
+ mul ecx
+ add eax, [cursor_x]
+ mov ebx, eax
+ pop eax
+
+ mov edx, 0b8000h
+
+ mov [ebx * 2 + edx], al
+ mov byte [ebx * 2 + edx + 1], 7
+ inc dword [cursor_x]
+ cmp dword [cursor_x], 80
+ jnz .end
+ call video_newline
+
+.end: o32 popa
+ ret
+
+
+ ; expects string pointer in esi
+putstr:
+ mov al, [esi]
+ cmp al, 0
+ jz .end
+ call putchar
+ inc esi
+ jmp putstr
+.end: ret
+
+ ; expects number in eax
+print_num:
+ ; save registers
+ o32 pusha
+
+ mov esi, numbuf + 16
+ mov byte [esi], 0
+ mov ebx, 10
+.convloop:
+ xor edx, edx
+ div ebx
+ add dl, 48
+ dec esi
+ mov [esi], dl
+ cmp eax, 0
+ jnz .convloop
+
+ call putstr
+
+ ; restore regs
+ o32 popa
+ ret
+
+
+video_newline:
+ mov dword [cursor_x], 0
+ inc dword [cursor_y]
+ cmp dword [cursor_y], 25
+ jnz .end
+ dec dword [cursor_y]
+.end: ret
+
+clearscr:
+ mov edi, 0b8000h
+ ; clear with white-on-black spaces
+ mov eax, 07200720h
+ mov ecx, 1000
+ a32 rep stosd
+ ret
+
+UART_DATA equ 3f8h
+UART_DIVLO equ 3f8h
+UART_DIVHI equ 3f9h
+UART_FIFO equ 3fah
+UART_LCTL equ 3fbh
+UART_MCTL equ 3fch
+UART_LSTAT equ 3fdh
+
+DIV_9600 equ (115200 / 9600)
+LCTL_8N1 equ 03h
+LCTL_DLAB equ 80h
+FIFO_ENABLE_CLEAR equ 07h
+MCTL_DTR_RTS_OUT2 equ 0bh
+LST_TREG_EMPTY equ 20h
+
+setup_serial:
+ ; set clock divisor
+ mov al, LCTL_DLAB
+ mov dx, UART_LCTL
+ out dx, al
+ mov ax, DIV_9600
+ mov dx, UART_DIVLO
+ out dx, al
+ shr ax, 8
+ mov dx, UART_DIVHI
+ out dx, al
+ ; set format 8n1
+ mov al, LCTL_8N1
+ mov dx, UART_LCTL
+ out dx, al
+ ; clear and enable fifo
+ mov al, FIFO_ENABLE_CLEAR
+ mov dx, UART_FIFO
+ out dx, al
+ ; assert RTS and DTR
+ mov al, MCTL_DTR_RTS_OUT2
+ mov dx, UART_MCTL
+ out dx, al
+ ret
+
+ser_putchar:
+ push dx
+ cmp al, 10
+ jnz .notlf
+ push ax
+ mov al, 13
+ call ser_putchar
+ pop ax
+
+.notlf: mov ah, al
+ ; wait until the transmit register is empty
+ mov dx, UART_LSTAT
+.wait: in al, dx
+ and al, LST_TREG_EMPTY
+ jz .wait
+ mov dx, UART_DATA
+ mov al, ah
+ out dx, al
+
+ pop dx
+ ret
+
+ena20_msg: db 'A20 line enabled',13,0
+
+enable_a20:
+ call test_a20
+ jnc .done
+ call enable_a20_kbd
+ call test_a20
+ jnc .done
+ call enable_a20_fast
+ call test_a20
+ jnc .done
+ ; keep trying...
+ jmp enable_a20
+.done:
+ mov esi, ena20_msg
+ call putstr
+ ret
+
+ ; CF = 1 if A20 test fails (not enabled)
+test_a20:
+ mov ebx, 07c000h
+ mov edx, 17c000h
+ mov dword [ebx], 0xbaadf00d
+ mov dword [edx], 0xaabbcc42
+ sub dword [ebx], 0xbaadf00d
+ ret
+
+ ; enable A20 line through port 0x92 (fast A20)
+enable_a20_fast:
+ mov esi, ena20_fast_msg
+ call putstr
+
+ in al, 92h
+ or al, 2
+ out 92h, al
+ ret
+
+ena20_fast_msg: db 'Attempting fast A20 enable',10,0
+
+ ; enable A20 line through the keyboard controller
+KBC_DATA_PORT equ 60h
+KBC_CMD_PORT equ 64h
+KBC_STATUS_PORT equ 64h
+KBC_CMD_RD_OUTPORT equ 0d0h
+KBC_CMD_WR_OUTPORT equ 0d1h
+
+KBC_STAT_OUT_RDY equ 01h
+KBC_STAT_IN_FULL equ 02h
+
+enable_a20_kbd:
+ mov esi, ena20_kbd_msg
+ call putstr
+
+ call kbc_wait_write
+ mov al, KBC_CMD_WR_OUTPORT
+ out KBC_CMD_PORT, al
+ call kbc_wait_write
+ mov al, 0dfh
+ out KBC_DATA_PORT, al
+ ret
+
+ena20_kbd_msg: db 'Attempting KBD A20 enable',10,0
+
+ ; wait until the keyboard controller is ready to accept another byte
+kbc_wait_write:
+ in al, KBC_STATUS_PORT
+ and al, KBC_STAT_IN_FULL
+ jnz kbc_wait_write
+ ret
+
+numbuf: resb 16
+
+
+ ; this part is placed at the very end of all boot sections
+ section .bootend
+
+ ; buffer used by the track loader
+ align 16
+buffer: