X-Git-Url: http://git.mutantstargoat.com/user/nuclear/?p=ld45_start_nothing;a=blobdiff_plain;f=src%2Fboot%2Fboot2.asm;fp=src%2Fboot%2Fboot2.asm;h=d5884bcbccdbba4eb07e3fce01bef79e8a1d556d;hp=0000000000000000000000000000000000000000;hb=2cef53ae69622dc995fa0f4ac7a6d793684b4403;hpb=6a16a1c526937ee551f968901cb949d55e35ecda diff --git a/src/boot/boot2.asm b/src/boot/boot2.asm new file mode 100644 index 0000000..d5884bc --- /dev/null +++ b/src/boot/boot2.asm @@ -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: