0ef217774b45332e97f0f84d3cc4239c38fc5fe1
[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         xor ax, ax
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         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         mov ax, 1606h
181         xor dx, dx
182         int 2fh
183
184 exit:   mov ax, 4c00h
185         int 21h
186
187 str_gemmis db 'Memory manager detected, trying to take control...',0
188 str_errvm86 db 'Error: memory manager running. Stop it and try again (e.g. emm386 off)',10,0
189 str_enterpm db 'Entering 32bit protected mode ...',10,0
190
191 vmswitch:
192 vmswitch_off dw 0
193 vmswitch_seg dw 0
194
195 %ifdef BUG_WARNING
196         ; warns the user about the experimental and buggy state of this
197         ; protected mode system, and allows aborting if having to reboot later
198         ; is not acceptable.
199 warning:
200         mov si, str_warnmsg
201         call printstr
202         WAITKEY
203         dec al
204         jz exit
205         ret
206
207 str_warnmsg:
208         db 'WARNING: this program uses an experimental protected mode kernel, which is ',10
209         db 'still in a very early stage of development, and will probably not be able to ',10
210         db 'return cleanly to DOS on exit. You might be forced to reboot after quitting!',10
211         db 'If this is not acceptable, press ESC now to abort.',10,10
212         db 'Press ESC to abort and return to DOS, any other key to continue...',10,10,0
213 %endif  ; BUG_WARNING
214
215
216 ; ---------------------- A20 address line enable -----------------------
217
218 enable_a20:
219         call test_a20
220         jnc .ret
221
222         mov si, .infomsg
223         call printstr           ; print "Enable A20 line ... "
224
225         call enable_a20_kbd
226         call test_a20
227         jnc .done
228         call enable_a20_fast
229         call test_a20
230         jnc .done
231         mov si, msg_failstr
232         call printstr
233         mov ax, 4c00h
234         int 21h
235 .done:  mov si, msg_okstr
236         call printstr
237 .ret:   ret
238
239 .infomsg db 'Enable A20 line:',0
240 msg_failstr db ' failed.',10,0
241 msg_okstr db ' success.',10,0
242
243         ; CF = 1 if A20 test fails (not enabled)
244 test_a20:
245         push ds
246         push es
247         xor ax, ax
248         mov ds, ax
249         not ax
250         mov es, ax
251         mov si, 420h
252         mov di, 430h
253         mov dl, [ds:si]
254         mov dh, [es:di]
255         mov [ds:si], al
256         not ax
257         mov [es:di], al
258         cmp al, [ds:si]
259         clc
260         jnz .done
261         stc
262 .done:  mov [ds:si], dl
263         mov [es:di], dh
264         pop es
265         pop ds
266         ret
267
268 enable_a20_fast:
269         mov si, .info
270         call printstr
271         in al, 92h
272         or al, 2
273         out 92h, al
274         ret
275 .info db ' fast ...',0
276
277 KBC_DATA_PORT equ 0x60
278 KBC_CMD_PORT equ 0x64
279 KBC_STATUS_PORT equ 0x64
280 KBC_CMD_WR_OUTPORT equ 0xd1
281
282 KBC_STAT_IN_FULL equ 2
283
284 enable_a20_kbd:
285         mov si, .info
286         call printstr
287         call kbc_wait_write
288         mov al, KBC_CMD_WR_OUTPORT
289         out KBC_CMD_PORT, al
290         call kbc_wait_write
291         mov al, 0xdf
292         out KBC_DATA_PORT, al
293         ret
294 .info db ' kbd ...',0
295
296 kbc_wait_write:
297         in al, KBC_STATUS_PORT
298         and al, KBC_STAT_IN_FULL
299         jnz kbc_wait_write
300         ret
301
302
303 ; ---------------------- memory detection -----------------------
304
305 detect_memory:
306         mov si, memdet_detram
307         call printstr
308         mov si, memdet_e820_msg
309         call printstr
310         call detect_mem_e820
311         jnc .done
312         mov si, str_fail
313         call printstr
314
315         mov si, memdet_detram
316         call printstr
317         mov si, memdet_e801_msg
318         call printstr
319         call detect_mem_e801
320         jnc .done
321         mov si, str_fail
322         call printstr
323
324         mov si, memdet_detram
325         call printstr
326         mov esi, memdet_88_msg
327         call printstr
328         call detect_mem_88
329         jnc .done
330         mov esi, str_fail
331         call printstr
332
333         mov si, memdet_detram
334         call printstr
335         mov esi, memdet_cmos_msg
336         call printstr
337         call detect_mem_cmos
338         jnc .done
339         mov esi, str_fail
340         call printstr
341
342         mov si, memdet_fail_msg
343         call printstr
344         jmp exit
345 .done:
346         mov si, str_ok
347         call printstr
348         ret
349
350 str_ok db 'OK',10,0
351 str_fail db 'failed',10,0
352 memdet_fail_msg db 'Failed to detect available memory!',10,0
353 memdet_detram   db 'Detecting RAM '
354 memdet_e820_msg db '(BIOS 15h/0xe820)... ',0
355 memdet_e801_msg db '(BIOS 15h/0xe801)... ',0
356 memdet_88_msg   db '(BIOS 15h/0x88, max 64mb)... ',0
357 memdet_cmos_msg db '(CMOS)...',0
358
359         ; detect extended memory using BIOS call 15h/e820
360 detect_mem_e820:
361         mov dword [mem_map_size], 0
362
363         mov edi, .buffer
364         xor ebx, ebx
365         mov edx, 534d4150h
366
367 .looptop:
368         mov eax, 0xe820
369         mov ecx, 24
370         int 15h
371         jc .fail
372         cmp eax, 534d4150h
373         jnz .fail
374
375         ; skip areas starting above 4GB as we won't be able to use them
376         cmp dword [di + 4], 0
377         jnz .skip
378
379         ; only care for type 1 (usable ram), otherwise ignore
380         cmp dword [di + 16], 1
381         jnz .skip
382
383         mov eax, [.buffer]
384         mov esi, mem_map
385         mov ebp, [mem_map_size]
386         mov [ebp * 8 + esi], eax
387
388         ; skip areas with 0 size (also clamp size to 4gb)
389         ; test high 32bits
390         cmp dword [edi + 12], 0
391         jz .highzero
392         ; high part is non-zero, make low part ffffffff
393         xor eax, eax
394         not eax
395         jmp .skiph0
396
397 .highzero:
398         ; if both high and low parts are zero, ignore
399         mov eax, [di + 8]
400         test eax, eax
401         jz .skip
402
403 .skiph0:mov [ebp * 8 + esi + 4], eax
404         inc dword [mem_map_size]
405
406 .skip:
407         ; terminate the loop if ebx was reset to 0
408         test ebx, ebx
409         jz .done
410         jmp .looptop
411 .done:
412         clc
413         ret
414
415 .fail:  ; if size > 0, then it's not a failure, just the end
416         cmp dword [mem_map_size], 0
417         jnz .done
418         stc
419         ret
420
421 .buffer times 32 db 0
422
423         ; detect extended memory using BIOS call 15h/e801
424 detect_mem_e801:
425         mov si, mem_map
426         mov ebp, [mem_map_size]
427         mov dword [ebp], 0
428
429         xor cx, cx
430         xor dx, dx
431         mov ax, 0xe801
432         int 15h
433         jc .fail
434
435         test cx, cx
436         jnz .foo1
437         test ax, ax
438         jz .fail
439         mov cx, ax
440         mov dx, bx
441
442 .foo1:  mov dword [si], 100000h
443         movzx eax, cx
444         ; first size is in KB, convert to bytes
445         shl eax, 10
446         jnc .foo2
447         ; overflow means it's >4GB, clamp to 4GB
448         mov eax, 0xffffffff
449 .foo2:  mov [si + 4], eax
450         inc dword [mem_map_size]
451         test dx, dx
452         jz .done
453         mov dword [si + 8], 1000000h
454         movzx eax, dx
455         ; second size is in 64kb blocks, convert to bytes
456         shl eax, 16
457         jnc .foo3
458         ; overflow means it's >4GB, clamp to 4GB
459         mov eax, 0xffffffff
460 .foo3:  mov [si + 12], eax
461         inc dword [mem_map_size]
462 .done:
463         clc
464         ret
465 .fail:
466         stc
467         ret
468
469 detect_mem_88:
470         ; reportedly some BIOS implementations fail to clear CF on success
471         clc
472         mov ah, 88h
473         int 15h
474         jc .fail
475
476         test ax, ax
477         jz .fail
478
479         ; ax has size in KB, convert to bytes in eax
480         and eax, 0xffff
481         shl eax, 10
482
483         mov esi, mem_map
484         mov dword [si], 100000h
485         mov [si + 4], eax
486
487         mov dword [mem_map_size], 1
488         clc
489         ret
490 .fail:  stc
491         ret
492
493 detect_mem_cmos:
494         mov al, 31h
495         out 70h, al
496         in al, 71h
497         mov ah, al
498         mov al, 30h
499         out 70h, al
500         in al, 71h
501
502         test ax, ax
503         jz .fail
504
505         ; ax has size in KB, convert to bytes in eax
506         and eax, 0xffff
507         shl eax, 10
508
509         mov esi, mem_map
510         mov dword [si], 100000h
511         mov [si + 4], eax
512         mov dword [mem_map_size], 1
513         clc
514         ret
515 .fail:  stc
516         ret
517
518
519         align 4
520 mem_map_size dd 0
521 mem_map times 128 db 0
522
523
524 ; ----------------------- serial console ------------------------
525
526 %ifdef CON_SERIAL
527 setup_serial:
528         ; set clock divisor
529         mov dx, UART_BASE + 3   ; LCTL
530         mov al, 80h             ; DLAB
531         out dx, al
532         mov dx, UART_BASE       ; DIVLO
533         mov al, UART_DIVISOR & 0xff
534         out dx, al
535         inc dx                  ; DIVHI
536         mov al, UART_DIVISOR >> 8
537         out dx, al
538         mov dx, UART_BASE + 3   ; LCTL
539         mov al, 3               ; 8n1
540         out dx, al
541         inc dx                  ; MCTL
542         mov al, 0xb             ; DTR/RTS/OUT2
543         out dx, al
544         ret
545
546 ser_putchar:
547         SER_PUTCHAR
548         ret
549
550 ser_printstr:
551         lodsb
552         test al, al
553         jz .end
554         cmp al, 10
555         jnz .nolf
556         push ax
557         mov al, 13
558         call ser_putchar
559         pop ax
560 .nolf:  call ser_putchar
561         jmp ser_printstr
562 .end:   ret
563
564 %endif ; def CON_SERIAL
565
566
567 printstr:
568         lodsb
569         test al, al
570         jz .end
571         cmp al, 10      ; check for line-feed and insert CR before it
572         jnz .nolf
573         push ax
574 %ifdef CON_SERIAL
575         mov al, 13
576         call ser_putchar
577 %endif
578         mov dl, 13
579         mov ah, 2
580         int 21h
581         pop ax
582 .nolf:
583 %ifdef CON_SERIAL
584         call ser_putchar
585 %endif
586         mov ah, 2
587         mov dl, al
588         int 21h
589         jmp printstr
590 .end:   ret
591
592
593         align 4
594 enterpm dd 0xbad00d     ; space for linear address for far jump to pmode
595 enterpm_sel dw 8        ; selector for far jump to protected mode
596         align 4
597 gdt_lim dw 47           ; GDT limit
598 gdt_base dd 0xbadf00d   ; space for GDT linear address
599
600         align 8
601 gdt:    ; 0: null segment
602         dd 0
603         dd 0
604         ; 1: code - 0/lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:code/non-conf/rd (sel: 8)
605         dd 0000ffffh
606         dd 00cf9a00h
607         ; 2: data - 0/lim:4g, G:4k, 32bit, avl, pres|app, dpl:0, type:data/rw (sel: 10h)
608         dd 0000ffffh
609         dd 00cf9200h
610         ; 3: tmp code (will set base before entering pmode) (sel: 18h)
611         dd 0000ffffh
612         dd 00cf9a00h
613         ; 4: tmp data (will set base before entering pmode) (sel: 20h)
614         dd 0000ffffh
615         dd 00cf9200h
616         ; 5: return to real-mode 16bit code segment (sel: 28h)
617         dd 0000ffffh
618         dd 00009a00h
619
620         ; pseudo IDTR descriptor for real-mode IVT at address 0
621         align 4
622         dw 0
623 rmidt:  dw 3ffh         ; IVT limit (1kb / 256 entries)
624         dd 0            ; IVT base 0
625
626 ; vi:set ts=8 sts=8 sw=8 ft=nasm: