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