9f0ca30579aaf71d9d9abc06d4ebbe5e54718aff
[metatoy] / src / loader.asm
1         section .loader
2
3         extern startup
4         extern _ldr_main_start
5         extern _main_start
6         extern _main_size
7         extern boot_mem_map
8         extern boot_mem_map_size
9
10 %include 'macros.inc'
11
12         [bits 16]
13         global _start
14 _start:
15         cli
16         mov ax, cs
17         mov ds, ax
18         mov es, ax
19         mov fs, ax      ; this will store the original real mode segment
20         mov ss, ax
21         ; modify the return to real mode jump segment
22         mov [.jmpcs16 + 3], ax
23
24         mov ax, 0xfffe
25         mov sp, ax
26
27 %ifdef BUG_WARNING
28         call warning    ; issue a warning and allow the user to abort
29 %endif
30
31         ; check for VM86 and abort
32         mov eax, cr0
33         test ax, 1
34         jz .notvm86
35
36         ; try to return to real mode
37         mov si, str_gemmis
38         call printstr
39
40         xor ax, ax
41         mov bx, ax
42         mov si, ax
43         mov es, ax
44         mov ds, ax
45         mov cx, ax
46         mov dx, ax
47         mov ax, 1605h
48         mov di, 30ah    ; pretend to be windows 3.1
49         int 2fh
50         test cx, cx
51         jnz .vm86abort
52         ; we got a function in ds:si
53         push cs
54         push ds
55         pop es  ; es <- func seg
56         pop ds  ; ds <- cs
57         mov word [vmswitch_seg], es
58         mov word [vmswitch_off], si
59         xor ax, ax      ; return to real mode
60         cli             ; just make sure nothing enabled intr behind out back
61         call far [vmswitch]
62         jc .vm86abort
63
64         ; success
65         mov ax, cs
66         mov ds, ax
67         mov es, ax
68         mov si, msg_okstr
69         call printstr
70         jmp .notvm86
71
72 .vm86abort:
73         push cs
74         pop ds
75         mov si, msg_failstr
76         call printstr
77         mov si, str_errvm86
78         call printstr
79         jmp exit
80
81 .notvm86:
82 %ifdef CON_SERIAL
83         call setup_serial
84 %endif
85         call enable_a20
86         call detect_memory
87
88         ; calculate GDT linear address
89         xor eax, eax
90         mov ax, cs
91         shl eax, 4
92         add eax, gdt
93         mov [gdt_base], eax
94
95         ; set tmp segment bases to match the linear address of our current seg
96         xor eax, eax
97         mov ax, cs
98         shl eax, 4
99         mov word [gdt + 18h + 2], ax    ; tmp pm code base
100         mov word [gdt + 20h + 2], ax    ; tmp pm data base
101         mov word [gdt + 28h + 2], ax    ; ret-to-realmode code
102         shr eax, 16
103         mov byte [gdt + 18h + 4], al
104         mov byte [gdt + 20h + 4], al
105         mov byte [gdt + 28h + 4], al
106
107         mov si, str_enterpm
108         call printstr
109
110         ; change video mode now, to avoid having to bring in all the int86 code
111         mov ax, 13h
112         int 10h
113
114         lgdt [gdt_lim]
115
116         mov eax, cr0
117         or eax, 1
118         mov cr0, eax
119
120         jmp 18h:.pm
121
122         [bits 32]
123 .pm:    mov ax, 20h     ; tmp data selector
124         mov ds, ax
125         mov ax, 10h     ; dest data selector
126         mov es, ax
127
128         ; copy main program high
129         cld
130         mov esi, _ldr_main_start
131         mov edi, _main_start
132         mov ecx, _main_size + 3
133         shr ecx, 2
134         rep movsd
135
136         ; copy memory map
137         mov eax, [mem_map_size]
138         mov [es:boot_mem_map_size], eax
139         mov esi, mem_map
140         mov edi, boot_mem_map
141         mov ecx, 32     ; 128 bytes
142         rep movsd
143
144         mov ax, 10h
145         mov ds, ax
146         mov ss, ax
147         mov esp, _main_start
148
149         call 8:startup
150         cli
151
152         ; return to real mode
153         jmp 28h:.rm
154
155         [bits 16]
156 .rm:    mov eax, cr0
157         and ax, 0xfffe
158         mov cr0, eax
159 .jmpcs16:
160         jmp 42h:.loadcs16       ; 42 seg is modifed at the start
161 .loadcs16:
162         mov ax, fs
163         mov ds, ax
164         mov es, ax
165         mov ss, ax
166
167         ; restore real-mode IVT
168         o32 lidt [rmidt]
169
170         ; switch back to text mode
171         mov ax, 3
172         int 10h
173
174         ; if we used GEMMIS to exit vm86, switch back to it
175         cmp dword [vmswitch], 0
176         jz exit
177         mov ax, 1
178         call far [vmswitch]
179         ; broadcast windows exit
180         xor ax, ax
181         mov bx, ax
182         mov si, ax
183         mov es, ax
184         mov ds, ax
185         mov cx, ax
186         mov dx, ax
187         mov ax, 1606h
188         int 2fh
189
190 exit:   mov ax, 4c00h
191         int 21h
192
193 str_gemmis db 'Memory manager detected, trying to take control...',0
194 str_errvm86 db 'Error: memory manager running. Stop it and try again (e.g. emm386 off)',10,0
195 str_enterpm db 'Entering 32bit protected mode ...',10,0
196
197         align 4
198 vmswitch:
199 vmswitch_off dw 0
200 vmswitch_seg dw 0
201
202 %ifdef BUG_WARNING
203         ; warns the user about the experimental and buggy state of this
204         ; protected mode system, and allows aborting if having to reboot later
205         ; is not acceptable.
206 warning:
207         mov si, str_warnmsg
208         call printstr
209         WAITKEY
210         dec al
211         jz exit
212         ret
213
214 str_warnmsg:
215         db 'WARNING: this program uses an experimental protected mode kernel, which is ',10
216         db 'still in a very early stage of development, and will probably not be able to ',10
217         db 'return cleanly to DOS on exit. You might be forced to reboot after quitting!',10
218         db 'If this is not acceptable, press ESC now to abort.',10,10
219         db 'Press ESC to abort and return to DOS, any other key to continue...',10,10,0
220 %endif  ; BUG_WARNING
221
222
223 ; ---------------------- A20 address line enable -----------------------
224
225 enable_a20:
226         call test_a20
227         jnc .ret
228
229         mov si, .infomsg
230         call printstr           ; print "Enable A20 line ... "
231
232         call enable_a20_kbd
233         call test_a20
234         jnc .done
235         call enable_a20_fast
236         call test_a20
237         jnc .done
238         mov si, msg_failstr
239         call printstr
240         mov ax, 4c00h
241         int 21h
242 .done:  mov si, msg_okstr
243         call printstr
244 .ret:   ret
245
246 .infomsg db 'Enable A20 line:',0
247 msg_failstr db ' failed.',10,0
248 msg_okstr db ' success.',10,0
249
250         ; CF = 1 if A20 test fails (not enabled)
251 test_a20:
252         push ds
253         push es
254         xor ax, ax
255         mov ds, ax
256         not ax
257         mov es, ax
258         mov si, 420h
259         mov di, 430h
260         mov dl, [ds:si]
261         mov dh, [es:di]
262         mov [ds:si], al
263         not ax
264         mov [es:di], al
265         cmp al, [ds:si]
266         clc
267         jnz .done
268         stc
269 .done:  mov [ds:si], dl
270         mov [es:di], dh
271         pop es
272         pop ds
273         ret
274
275 enable_a20_fast:
276         mov si, .info
277         call printstr
278         in al, 92h
279         or al, 2
280         out 92h, al
281         ret
282 .info db ' fast ...',0
283
284 KBC_DATA_PORT equ 0x60
285 KBC_CMD_PORT equ 0x64
286 KBC_STATUS_PORT equ 0x64
287 KBC_CMD_WR_OUTPORT equ 0xd1
288
289 KBC_STAT_IN_FULL equ 2
290
291 enable_a20_kbd:
292         mov si, .info
293         call printstr
294         call kbc_wait_write
295         mov al, KBC_CMD_WR_OUTPORT
296         out KBC_CMD_PORT, al
297         call kbc_wait_write
298         mov al, 0xdf
299         out KBC_DATA_PORT, al
300         ret
301 .info db ' kbd ...',0
302
303 kbc_wait_write:
304         in al, KBC_STATUS_PORT
305         and al, KBC_STAT_IN_FULL
306         jnz kbc_wait_write
307         ret
308
309
310 ; ---------------------- memory detection -----------------------
311
312 detect_memory:
313         mov si, memdet_detram
314         call printstr
315         mov si, memdet_e820_msg
316         call printstr
317         call detect_mem_e820
318         jnc .done
319         mov si, str_fail
320         call printstr
321
322         mov si, memdet_detram
323         call printstr
324         mov si, memdet_e801_msg
325         call printstr
326         call detect_mem_e801
327         jnc .done
328         mov si, str_fail
329         call printstr
330
331         mov si, memdet_detram
332         call printstr
333         mov esi, memdet_88_msg
334         call printstr
335         call detect_mem_88
336         jnc .done
337         mov esi, str_fail
338         call printstr
339
340         mov si, memdet_detram
341         call printstr
342         mov esi, memdet_cmos_msg
343         call printstr
344         call detect_mem_cmos
345         jnc .done
346         mov esi, str_fail
347         call printstr
348
349         mov si, memdet_fail_msg
350         call printstr
351         jmp exit
352 .done:
353         mov si, str_ok
354         call printstr
355         ret
356
357 str_ok db 'OK',10,0
358 str_fail db 'failed',10,0
359 memdet_fail_msg db 'Failed to detect available memory!',10,0
360 memdet_detram   db 'Detecting RAM ',0
361 memdet_e820_msg db '(BIOS 15h/0xe820)... ',0
362 memdet_e801_msg db '(BIOS 15h/0xe801)... ',0
363 memdet_88_msg   db '(BIOS 15h/0x88, max 64mb)... ',0
364 memdet_cmos_msg db '(CMOS)...',0
365
366         ; detect extended memory using BIOS call 15h/e820
367 detect_mem_e820:
368         mov dword [mem_map_size], 0
369
370         mov edi, .buffer
371         xor ebx, ebx
372         mov edx, 534d4150h
373
374 .looptop:
375         mov eax, 0xe820
376         mov ecx, 24
377         int 15h
378         jc .fail
379         cmp eax, 534d4150h
380         jnz .fail
381
382         ; skip areas starting above 4GB as we won't be able to use them
383         cmp dword [di + 4], 0
384         jnz .skip
385
386         ; only care for type 1 (usable ram), otherwise ignore
387         cmp dword [di + 16], 1
388         jnz .skip
389
390         mov eax, [.buffer]
391         mov esi, mem_map
392         mov ebp, [mem_map_size]
393         mov [ebp * 8 + esi], eax
394
395         ; skip areas with 0 size (also clamp size to 4gb)
396         ; test high 32bits
397         cmp dword [edi + 12], 0
398         jz .highzero
399         ; high part is non-zero, make low part ffffffff
400         xor eax, eax
401         not eax
402         jmp .skiph0
403
404 .highzero:
405         ; if both high and low parts are zero, ignore
406         mov eax, [di + 8]
407         test eax, eax
408         jz .skip
409
410 .skiph0:mov [ebp * 8 + esi + 4], eax
411         inc dword [mem_map_size]
412
413 .skip:
414         ; terminate the loop if ebx was reset to 0
415         test ebx, ebx
416         jz .done
417         jmp .looptop
418 .done:
419         clc
420         ret
421
422 .fail:  ; if size > 0, then it's not a failure, just the end
423         cmp dword [mem_map_size], 0
424         jnz .done
425         stc
426         ret
427
428 .buffer times 32 db 0
429
430         ; detect extended memory using BIOS call 15h/e801
431 detect_mem_e801:
432         mov si, mem_map
433         mov ebp, [mem_map_size]
434         mov dword [ebp], 0
435
436         xor cx, cx
437         xor dx, dx
438         mov ax, 0xe801
439         int 15h
440         jc .fail
441
442         test cx, cx
443         jnz .foo1
444         test ax, ax
445         jz .fail
446         mov cx, ax
447         mov dx, bx
448
449 .foo1:  mov dword [si], 100000h
450         movzx eax, cx
451         ; first size is in KB, convert to bytes
452         shl eax, 10
453         jnc .foo2
454         ; overflow means it's >4GB, clamp to 4GB
455         mov eax, 0xffffffff
456 .foo2:  mov [si + 4], eax
457         inc dword [mem_map_size]
458         test dx, dx
459         jz .done
460         mov dword [si + 8], 1000000h
461         movzx eax, dx
462         ; second size is in 64kb blocks, convert to bytes
463         shl eax, 16
464         jnc .foo3
465         ; overflow means it's >4GB, clamp to 4GB
466         mov eax, 0xffffffff
467 .foo3:  mov [si + 12], eax
468         inc dword [mem_map_size]
469 .done:
470         clc
471         ret
472 .fail:
473         stc
474         ret
475
476 detect_mem_88:
477         ; reportedly some BIOS implementations fail to clear CF on success
478         clc
479         mov ah, 88h
480         int 15h
481         jc .fail
482
483         test ax, ax
484         jz .fail
485
486         ; ax has size in KB, convert to bytes in eax
487         and eax, 0xffff
488         shl eax, 10
489
490         mov esi, mem_map
491         mov dword [si], 100000h
492         mov [si + 4], eax
493
494         mov dword [mem_map_size], 1
495         clc
496         ret
497 .fail:  stc
498         ret
499
500 detect_mem_cmos:
501         mov al, 31h
502         out 70h, al
503         in al, 71h
504         mov ah, al
505         mov al, 30h
506         out 70h, al
507         in al, 71h
508
509         test ax, ax
510         jz .fail
511
512         ; ax has size in KB, convert to bytes in eax
513         and eax, 0xffff
514         shl eax, 10
515
516         mov esi, mem_map
517         mov dword [si], 100000h
518         mov [si + 4], eax
519         mov dword [mem_map_size], 1
520         clc
521         ret
522 .fail:  stc
523         ret
524
525
526         align 4
527 mem_map_size dd 0
528 mem_map times 128 db 0
529
530
531 ; ----------------------- serial console ------------------------
532
533 %ifdef CON_SERIAL
534 setup_serial:
535         ; set clock divisor
536         mov dx, UART_BASE + 3   ; LCTL
537         mov al, 80h             ; DLAB
538         out dx, al
539         mov dx, UART_BASE       ; DIVLO
540         mov al, UART_DIVISOR & 0xff
541         out dx, al
542         inc dx                  ; DIVHI
543         mov al, UART_DIVISOR >> 8
544         out dx, al
545         mov dx, UART_BASE + 3   ; LCTL
546         mov al, 3               ; 8n1
547         out dx, al
548         inc dx                  ; MCTL
549         mov al, 0xb             ; DTR/RTS/OUT2
550         out dx, al
551         ret
552
553 ser_putchar:
554         SER_PUTCHAR
555         ret
556
557 ser_printstr:
558         lodsb
559         test al, al
560         jz .end
561         cmp al, 10
562         jnz .nolf
563         push ax
564         mov al, 13
565         call ser_putchar
566         pop ax
567 .nolf:  call ser_putchar
568         jmp ser_printstr
569 .end:   ret
570
571 %endif ; def CON_SERIAL
572
573
574 printstr:
575         lodsb
576         test al, al
577         jz .end
578         cmp al, 10      ; check for line-feed and insert CR before it
579         jnz .nolf
580         push ax
581 %ifdef CON_SERIAL
582         mov al, 13
583         call ser_putchar
584 %endif
585         mov dl, 13
586         mov ah, 2
587         int 21h
588         pop ax
589 .nolf:
590 %ifdef CON_SERIAL
591         call ser_putchar
592 %endif
593         mov ah, 2
594         mov dl, al
595         int 21h
596         jmp printstr
597 .end:   ret
598
599
600         align 4
601 enterpm dd 0xbad00d     ; space for linear address for far jump to pmode
602 enterpm_sel dw 8        ; selector for far jump to protected mode
603         align 4
604 gdt_lim dw 47           ; GDT limit
605 gdt_base dd 0xbadf00d   ; space for GDT linear address
606
607         align 8
608 gdt:    ; 0: null segment
609         dd 0
610         dd 0
611         ; 1: code - 0/lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:code/non-conf/rd (sel: 8)
612         dd 0000ffffh
613         dd 00cf9a00h
614         ; 2: data - 0/lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:data/rw (sel: 10h)
615         dd 0000ffffh
616         dd 00cf9200h
617         ; 3: tmp code (will set base before entering pmode) (sel: 18h)
618         dd 0000ffffh
619         dd 00cf9a00h
620         ; 4: tmp data (will set base before entering pmode) (sel: 20h)
621         dd 0000ffffh
622         dd 00cf9200h
623         ; 5: return to real-mode 16bit code segment (sel: 28h)
624         dd 0000ffffh
625         dd 00009a00h
626
627         ; pseudo IDTR descriptor for real-mode IVT at address 0
628         align 4
629         dw 0
630 rmidt:  dw 3ffh         ; IVT limit (1kb / 256 entries)
631         dd 0            ; IVT base 0
632
633
634 ; --- debug ---
635 newline:
636         push ax
637         mov al, 13
638         call ser_putchar
639         mov al, 10
640         call ser_putchar
641         pop ax
642         ret
643
644 printhex:
645         rol ax, 4
646         call print_hexdigit
647         rol ax, 4
648         call print_hexdigit
649         rol ax, 4
650         call print_hexdigit
651         rol ax, 4
652         call print_hexdigit
653         ret
654
655 print_hexdigit:
656         push ax
657         and ax, 0xf
658         cmp al, 0xa
659         jae .hexdig
660         add al, '0'
661         call ser_putchar
662         pop ax
663         ret
664 .hexdig:add al, 'a' - 10
665         call ser_putchar
666         pop ax
667         ret
668
669 ; vi:set ts=8 sts=8 sw=8 ft=nasm: