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