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