converted to protected mode, not done
[ld45_start_nothing] / src / boot / boot2.asm
diff --git a/src/boot/boot2.asm b/src/boot/boot2.asm
new file mode 100644 (file)
index 0000000..d5884bc
--- /dev/null
@@ -0,0 +1,559 @@
+; 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: