converted to protected mode, not done
[ld45_start_nothing] / src / boot / boot2.asm
1 ; vi:filetype=nasm ts=8 sts=8 sw=8:
2 ; second stage boot loader
3         bits 16
4         section .boot2
5
6 LOADADDR equ 100000h
7 DRIVENO_ADDR equ 7bf0h
8
9         extern _boot2_size
10         extern _main_size
11         extern sect_per_track
12         extern num_heads
13
14 boot2_start:
15         cli
16
17         xor eax, eax
18         mov al, [DRIVENO_ADDR]
19
20         call setup_serial
21
22         ; enter unreal mode
23         call unreal
24
25         mov al, 10
26         call ser_putchar
27
28         ; enable A20 address line
29         call enable_a20
30
31         ; load program into memory starting at 1MB
32         call load_main
33
34         ; switch video mode, can't do that easily from protected mode
35         mov ax, 13h
36         int 10h
37
38         ; load GDT and IDT
39         lgdt [gdt_lim]
40         lidt [idt_lim]
41
42         ; enter protected mode
43         mov eax, cr0
44         or eax, 1
45         mov cr0, eax
46         ; inter-segment jump to set cs selector to segment 1
47         jmp 0x8:.pmode
48
49         bits 32
50 .pmode: ; set all data selectors to segment 2
51         mov ax, 10h
52         mov ds, ax
53         mov ss, ax
54         mov es, ax
55         mov gs, ax
56         mov fs, ax
57
58         jmp LOADADDR
59
60         align 4
61 gdt_lim: dw 23
62 gdt_base: dd gdt
63
64         align 4
65 idt_lim: dw 111
66 idt_base: dd idt
67
68         align 8
69 gdt:    ; 0: null segment
70         dd 0
71         dd 0
72         ; 1: code - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:code/non-conf/rd
73         dd 0000ffffh
74         dd 00cf9a00h
75         ; 2: data - base:0, lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:data/rw
76         dd 0000ffffh
77         dd 00cf9200h
78
79         align 8
80 idt:    times 104 db 0
81         ; trap gate 13: general protection fault
82         dw prot_fault
83         dw 8
84         dw 8f00h    ; type: trap, present, default
85         dw 0
86
87 gpf_msg: db "GP fault "
88
89 prot_fault:
90         mov eax, [esp]
91         shr eax, 3
92         call print_num
93         mov al, ':'
94         call putchar
95         mov eax, [esp + 4]
96         call print_num
97         mov al, 10
98         call putchar
99         hlt
100
101
102         bits 16
103 unreal:
104         ; use the same GDT as above, will use data seg: 2
105         lgdt [gdt_lim]
106
107         mov eax, cr0
108         or eax, 1
109         mov cr0, eax
110         jmp .pm
111
112 .pm:    mov ax, 10h
113         mov ds, ax
114         mov es, ax
115         mov fs, ax
116         mov gs, ax
117         mov ss, ax
118
119         mov eax, cr0
120         and ax, 0fffeh
121         mov cr0, eax
122
123         xor ax, ax
124         mov ds, ax
125         mov es, ax
126         mov fs, ax
127         mov gs, ax
128         mov ss, ax
129         ret
130
131 mainsz_msg: db 'Program size: ',0
132 mainsz_msg2: db ' (',0
133 mainsz_msg3: db ' sectors)',10,0
134
135 first_sect: dd 0
136 sect_left: dd 0
137 cur_track: dd 0
138 trk_sect: dd 0
139 dest_ptr: dd 0
140
141 load_main:
142         mov dword [dest_ptr], LOADADDR
143
144         ; calculate first sector
145         mov eax, _boot2_size
146         add eax, 511
147         shr eax, 9
148         ; add 1 to account for the boot sector
149         inc eax
150         mov [first_sect], eax
151
152         ; calculate the first track (first_sect / sect_per_track)
153         movzx ecx, word [sect_per_track]
154         xor edx, edx
155         div ecx
156         mov [cur_track], eax
157         ; remainder is sector within track
158         mov [trk_sect], edx
159
160         mov esi, mainsz_msg
161         call putstr
162         mov eax, _main_size
163         mov ecx, eax
164         call print_num
165
166         mov esi, mainsz_msg2
167         call putstr
168
169         ; calculate sector count
170         add eax, 511
171         shr eax, 9
172         mov [sect_left], eax
173
174         call print_num
175         mov esi, mainsz_msg3
176         call putstr
177
178         ; read a whole track into the buffer (or partial first track)
179 .ldloop:
180         movzx ecx, word [sect_per_track]
181         sub ecx, [trk_sect]
182         push ecx
183         call read_track
184
185         ; copy to high memory
186         mov esi, buffer
187         mov edi, [dest_ptr]
188         mov ecx, [esp]
189         shl ecx, 9
190         add [dest_ptr], ecx
191         shr ecx, 2
192         a32 rep movsd
193
194         inc dword [cur_track]
195         ; other than the first track which might be partial, all the rest start from 0
196         mov dword [trk_sect], 0
197
198         pop ecx
199         sub [sect_left], ecx
200         ja .ldloop
201
202         ; the BIOS might have enabled interrupts
203         cli
204
205         ; if we were loaded from floppy, turn all floppy motors off
206         mov bl, [DRIVENO_ADDR]
207         and bl, 80h
208         jnz .notfloppy
209         mov dx, 3f2h
210         in al, dx
211         and al, 0fh
212         out dx, al
213 .notfloppy:
214
215         mov ax, 10
216         call putchar
217
218         ret
219
220 rdtrk_msg: db 'Reading track: ',0
221 rdcyl_msg: db ' - cyl: ',0
222 rdhead_msg: db ' head: ',0
223 rdsect_msg: db ' start sect: ',0
224 rdlast_msg: db ' ... ',0
225 rdok_msg: db 'OK',10,0
226 rdfail_msg: db 'failed',10,0
227
228 read_retries: dw 0
229
230 read_track:
231         ; set es to the start of the destination buffer to allo readin in
232         ; full 64k chunks if necessary
233         mov bx, buffer
234         shr bx, 4
235         mov es, bx
236         xor ebx, ebx
237
238         mov word [read_retries], 3
239
240 .try:
241         ; print_track
242         mov esi, rdtrk_msg
243         call putstr
244         mov eax, [cur_track]
245         call print_num
246         mov esi, rdcyl_msg
247         call putstr
248
249         ; calc cylinder (cur_track / num_heads) and head (cur_track % num_heads)
250         mov eax, [cur_track]
251         movzx ecx, word [num_heads]
252         xor edx, edx
253         div ecx
254
255         ; print cylinder
256         push eax
257         call print_num
258         ; print head
259         mov esi, rdhead_msg
260         call putstr
261         movzx eax, dx
262         call print_num
263         pop eax
264
265         ; head on dh
266         mov dh, dl
267
268         ; cylinder low byte at ch and high bits at cl[7, 6]
269         mov ch, al
270         mov cl, ah
271         and cl, 3
272         ror cl, 2
273
274         ; print start sector
275         mov esi, rdsect_msg
276         call putstr
277         mov eax, [trk_sect]
278         call print_num
279         mov esi, rdlast_msg
280         call putstr
281
282         ; start sector (1-based) in cl[0, 5]
283         mov al, [trk_sect]
284         inc al
285         and al, 3fh
286         or cl, al
287
288         ; number of sectors in al
289         mov ax, [esp + 2]
290         ; call number (2) in ah
291         mov ah, 2
292         ; drive number in dl
293         mov dl, [DRIVENO_ADDR]
294         int 13h
295         jnc .success
296
297         ; abort after 3 attempts
298         dec word [read_retries]
299         jz .failed
300
301         ; error, reset controller and retry
302         xor ah, ah
303         int 13h
304         jmp .try
305
306 .failed:
307         mov esi, rdfail_msg
308         call putstr
309         jmp abort_read
310
311 .success:
312         mov esi, rdok_msg
313         call putstr
314
315         ; reset es to 0 before returning
316         xor ax, ax
317         mov es, ax
318         ret
319
320 str_read_error: db 'Read error while reading track: ',0
321
322 abort_read:
323         mov esi, str_read_error
324         call putstr
325         mov eax, [cur_track]
326         call print_num
327         mov al, 10
328         call putchar
329
330         cli
331 .hlt:   hlt
332         jmp .hlt
333
334
335         ; print routines
336 cursor_x: dd 0
337 cursor_y: dd 0
338
339 putchar:
340         o32 pusha
341         call ser_putchar
342
343         cmp al, 10
344         jnz .notlf
345         call video_newline
346         jmp .end
347
348 .notlf: push eax
349         mov eax, [cursor_y]
350         mov ecx, 80
351         mul ecx
352         add eax, [cursor_x]
353         mov ebx, eax
354         pop eax
355
356         mov edx, 0b8000h
357
358         mov [ebx * 2 + edx], al
359         mov byte [ebx * 2 + edx + 1], 7
360         inc dword [cursor_x]
361         cmp dword [cursor_x], 80
362         jnz .end
363         call video_newline
364
365 .end:   o32 popa
366         ret
367
368
369         ; expects string pointer in esi
370 putstr:
371         mov al, [esi]
372         cmp al, 0
373         jz .end
374         call putchar
375         inc esi
376         jmp putstr
377 .end:   ret
378
379         ; expects number in eax
380 print_num:
381         ; save registers
382         o32 pusha
383
384         mov esi, numbuf + 16
385         mov byte [esi], 0
386         mov ebx, 10
387 .convloop:
388         xor edx, edx
389         div ebx
390         add dl, 48
391         dec esi
392         mov [esi], dl
393         cmp eax, 0
394         jnz .convloop
395
396         call putstr
397
398         ; restore regs
399         o32 popa
400         ret
401
402
403 video_newline:
404         mov dword [cursor_x], 0
405         inc dword [cursor_y]
406         cmp dword [cursor_y], 25
407         jnz .end
408         dec dword [cursor_y]
409 .end:   ret
410
411 clearscr:
412         mov edi, 0b8000h
413         ; clear with white-on-black spaces
414         mov eax, 07200720h
415         mov ecx, 1000
416         a32 rep stosd
417         ret
418
419 UART_DATA equ 3f8h
420 UART_DIVLO equ 3f8h
421 UART_DIVHI equ 3f9h
422 UART_FIFO equ 3fah
423 UART_LCTL equ 3fbh
424 UART_MCTL equ 3fch
425 UART_LSTAT equ 3fdh
426
427 DIV_9600 equ (115200 / 9600)
428 LCTL_8N1 equ 03h
429 LCTL_DLAB equ 80h
430 FIFO_ENABLE_CLEAR equ 07h
431 MCTL_DTR_RTS_OUT2 equ 0bh
432 LST_TREG_EMPTY equ 20h
433
434 setup_serial:
435         ; set clock divisor
436         mov al, LCTL_DLAB
437         mov dx, UART_LCTL
438         out dx, al
439         mov ax, DIV_9600
440         mov dx, UART_DIVLO
441         out dx, al
442         shr ax, 8
443         mov dx, UART_DIVHI
444         out dx, al
445         ; set format 8n1
446         mov al, LCTL_8N1
447         mov dx, UART_LCTL
448         out dx, al
449         ; clear and enable fifo
450         mov al, FIFO_ENABLE_CLEAR
451         mov dx, UART_FIFO
452         out dx, al
453         ; assert RTS and DTR
454         mov al, MCTL_DTR_RTS_OUT2
455         mov dx, UART_MCTL
456         out dx, al
457         ret
458
459 ser_putchar:
460         push dx
461         cmp al, 10
462         jnz .notlf
463         push ax
464         mov al, 13
465         call ser_putchar
466         pop ax
467
468 .notlf: mov ah, al
469         ; wait until the transmit register is empty
470         mov dx, UART_LSTAT
471 .wait:  in al, dx
472         and al, LST_TREG_EMPTY
473         jz .wait
474         mov dx, UART_DATA
475         mov al, ah
476         out dx, al
477
478         pop dx
479         ret
480
481 ena20_msg: db 'A20 line enabled',13,0
482
483 enable_a20:
484         call test_a20
485         jnc .done
486         call enable_a20_kbd
487         call test_a20
488         jnc .done
489         call enable_a20_fast
490         call test_a20
491         jnc .done
492         ; keep trying...
493         jmp enable_a20
494 .done:
495         mov esi, ena20_msg
496         call putstr
497         ret
498
499         ; CF = 1 if A20 test fails (not enabled)
500 test_a20:
501         mov ebx, 07c000h
502         mov edx, 17c000h
503         mov dword [ebx], 0xbaadf00d
504         mov dword [edx], 0xaabbcc42
505         sub dword [ebx], 0xbaadf00d
506         ret
507
508         ; enable A20 line through port 0x92 (fast A20)
509 enable_a20_fast:
510         mov esi, ena20_fast_msg
511         call putstr
512
513         in al, 92h
514         or al, 2
515         out 92h, al
516         ret
517
518 ena20_fast_msg: db 'Attempting fast A20 enable',10,0
519
520         ; enable A20 line through the keyboard controller
521 KBC_DATA_PORT equ 60h
522 KBC_CMD_PORT equ 64h
523 KBC_STATUS_PORT equ 64h
524 KBC_CMD_RD_OUTPORT equ 0d0h
525 KBC_CMD_WR_OUTPORT equ 0d1h
526
527 KBC_STAT_OUT_RDY equ 01h
528 KBC_STAT_IN_FULL equ 02h
529
530 enable_a20_kbd:
531         mov esi, ena20_kbd_msg
532         call putstr
533
534         call kbc_wait_write
535         mov al, KBC_CMD_WR_OUTPORT
536         out KBC_CMD_PORT, al
537         call kbc_wait_write
538         mov al, 0dfh
539         out KBC_DATA_PORT, al
540         ret
541
542 ena20_kbd_msg: db 'Attempting KBD A20 enable',10,0
543
544         ; wait until the keyboard controller is ready to accept another byte
545 kbc_wait_write:
546         in al, KBC_STATUS_PORT
547         and al, KBC_STAT_IN_FULL
548         jnz kbc_wait_write
549         ret
550
551 numbuf: resb 16
552
553
554         ; this part is placed at the very end of all boot sections
555         section .bootend
556
557         ; buffer used by the track loader
558         align 16
559 buffer: