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